mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-16 14:50:15 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
499cda86b3 | ||
|
|
ca03a8eff3 | ||
|
|
3a60e04a84 | ||
|
|
8b33e3a08f | ||
|
|
b021ceec4b | ||
|
|
d318579745 | ||
|
|
8d17ed0ecd | ||
|
|
3199a99b29 | ||
|
|
39008f7170 | ||
|
|
8bf33d539f | ||
|
|
42f6720acd | ||
|
|
0a3cb12c0b | ||
|
|
202823e731 | ||
|
|
ed088fe923 | ||
|
|
df21c3396a | ||
|
|
90ccdc6432 | ||
|
|
dba371a320 | ||
|
|
d96cd8f0d2 | ||
|
|
bd6e7d48f3 | ||
|
|
8b41e4530f | ||
|
|
a555f1e759 | ||
|
|
fdaa40e431 | ||
|
|
4c2f9d92e5 | ||
|
|
a92011eee6 | ||
|
|
61a7757a0e | ||
|
|
a53ca3592f | ||
|
|
9e682617e6 | ||
|
|
16dda810d7 | ||
|
|
6042f16342 | ||
|
|
0afdd06c80 | ||
|
|
3158aebca4 | ||
|
|
99866fdc12 | ||
|
|
a58eb59a32 | ||
|
|
eda1f8720f | ||
|
|
f54f377344 | ||
|
|
81a9be17b8 |
@@ -9,8 +9,6 @@ RUN set -eux; \
|
||||
apt-get update && \
|
||||
apt-get dist-upgrade -y --no-install-recommends && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
# curl required for historical reasons, see https://github.com/adoptium/containers/issues/255
|
||||
curl \
|
||||
# java.lang.UnsatisfiedLinkError: libfontmanager.so: libfreetype.so.6: cannot open shared object file: No such file or directory
|
||||
# java.lang.NoClassDefFoundError: Could not initialize class sun.awt.X11FontManager
|
||||
# https://github.com/docker-library/openjdk/pull/235#issuecomment-424466077
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-Name: Cloudbeaver Web Model
|
||||
Bundle-SymbolicName: io.cloudbeaver.model.cli;singleton:=true
|
||||
Bundle-Version: 1.0.15.qualifier
|
||||
Bundle-Release-Date: 20260803
|
||||
Bundle-Version: 1.0.16.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: org.jkiss.dbeaver.model.cli;visibility:=reexport
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.model.cli</artifactId>
|
||||
<version>1.0.15-SNAPSHOT</version>
|
||||
<version>1.0.16-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-Name: Cloudbeaver Web Model
|
||||
Bundle-SymbolicName: io.cloudbeaver.model;singleton:=true
|
||||
Bundle-Version: 1.0.106.qualifier
|
||||
Bundle-Release-Date: 20260803
|
||||
Bundle-Version: 1.0.107.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: org.jkiss.dbeaver.data.gis;visibility:=reexport,
|
||||
|
||||
@@ -13,7 +13,7 @@ meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.smDbProductName.
|
||||
meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.smDbProductVersion.name=Security manager database product version
|
||||
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.readOnly.name = Read-only connection
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.name = Auto-commit
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommitMode.name = Auto-commit mode
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = Keep-alive interval (seconds)
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultSchema.name = Default schema
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultCatalog.name = Default catalog
|
||||
@@ -1,5 +1,5 @@
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.readOnly.name = Только для чтения
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.name = Авто-коммит
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommitMode.name = Авто-коммит
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = Интервал Keep-alive (в секундах)
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultSchema.name = Схема по умолчанию
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultCatalog.name = Каталог по умолчанию
|
||||
@@ -1,4 +1,4 @@
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.readOnly.name = Kết nối chỉ đọc
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.name = Tự động commit
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommitMode.name = Tự động commit
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = Giữ kết nối (tính bằng giây)
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.description = Không tự động ngắt kết nối
|
||||
@@ -1,3 +1,3 @@
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.name = 自动提交
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommitMode.name = 自动提交
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = 保持连接 (秒)
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.description = 无自动断开连接
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.model</artifactId>
|
||||
<version>1.0.106-SNAPSHOT</version>
|
||||
<version>1.0.107-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
+1
-3
@@ -77,9 +77,7 @@ public class WebConnectionConfigInputHandler<T extends WebConnectionConfig, C ex
|
||||
}
|
||||
|
||||
dataSource.setFolder(input.getFolder() != null ? registry.getFolder(input.getFolder()) : null);
|
||||
if (input.isDefaultAutoCommit() != null) {
|
||||
dataSource.getConnectionConfiguration().getBootstrap().setDefaultAutoCommit(input.isDefaultAutoCommit());
|
||||
}
|
||||
dataSource.getConnectionConfiguration().getBootstrap().setDefaultAutoCommit(input.isDefaultAutoCommit());
|
||||
WebDataSourceUtils.setConnectionConfiguration(
|
||||
dataSource.getDriver(),
|
||||
dataSource.getConnectionConfiguration(),
|
||||
|
||||
@@ -103,7 +103,7 @@ public class WebSessionProjectImpl extends WebProjectImpl implements DBPAdaptabl
|
||||
return projectSettings;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@NotNull
|
||||
@Override
|
||||
public DBNModel getNavigatorModel() {
|
||||
return webSession.getNavigatorModel();
|
||||
|
||||
+7
@@ -69,6 +69,13 @@ public interface SMAuthProviderExternal<AUTH_SESSION extends SMSession> extends
|
||||
@NotNull WebUser user,
|
||||
boolean selfIdentity) throws DBException;
|
||||
|
||||
/**
|
||||
* Whether a user that is not found should be provisioned (auto-created) on external login.
|
||||
*/
|
||||
default boolean isAutoUserProvisioningEnabled(@Nullable SMAuthProviderCustomConfiguration providerConfig) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make some post authentication actions
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.model;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
public enum AutoCommitMode {
|
||||
DEFAULT("Default", null),
|
||||
AUTO_COMMIT("Auto commit", true),
|
||||
MANUAL_COMMIT("Manual commit", false);
|
||||
|
||||
private final String title;
|
||||
private final Boolean value;
|
||||
|
||||
AutoCommitMode(@NotNull String title, @Nullable Boolean value) {
|
||||
this.title = title;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return title;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Boolean getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static AutoCommitMode fromValue(@Nullable Boolean value) {
|
||||
if (value == null) {
|
||||
return DEFAULT;
|
||||
}
|
||||
return value ? AUTO_COMMIT : MANUAL_COMMIT;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Boolean from(@Nullable String mode) {
|
||||
if (CommonUtils.isEmpty(mode)) {
|
||||
return null;
|
||||
}
|
||||
return valueOf(mode).getValue();
|
||||
}
|
||||
}
|
||||
+8
-2
@@ -103,8 +103,14 @@ public class WebConnectionConfig {
|
||||
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_KEEP_ALIVE_INTERVAL, -1);
|
||||
readOnly = JSONUtils.getBoolean(
|
||||
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_READ_ONLY);
|
||||
defaultAutoCommit = JSONUtils.getBoolean(
|
||||
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_AUTO_COMMIT, true);
|
||||
|
||||
if (expertSettingsValues != null) {
|
||||
defaultAutoCommit = AutoCommitMode.from(
|
||||
JSONUtils.getString(expertSettingsValues, WebExpertSettingsProperties.PROP_AUTO_COMMIT_MODE));
|
||||
} else {
|
||||
defaultAutoCommit = JSONUtils.getBoolean(params, WebExpertSettingsProperties.PROP_AUTO_COMMIT, true);
|
||||
}
|
||||
|
||||
defaultCatalogName = JSONUtils.getString(
|
||||
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_DEFAULT_CATALOG);
|
||||
defaultSchemaName = JSONUtils.getString(
|
||||
|
||||
+13
-7
@@ -467,7 +467,7 @@ public class WebConnectionInfo {
|
||||
@Property
|
||||
public Map<String, Object> getExpertSettingsValues() {
|
||||
Map<String, Object> expertSettings = new LinkedHashMap<>();
|
||||
expertSettings.put(WebExpertSettingsProperties.PROP_AUTO_COMMIT, isAutocommit());
|
||||
expertSettings.put(WebExpertSettingsProperties.PROP_AUTO_COMMIT_MODE, AutoCommitMode.fromValue(isAutocommit()));
|
||||
expertSettings.put(WebExpertSettingsProperties.PROP_KEEP_ALIVE_INTERVAL, getKeepAliveInterval());
|
||||
expertSettings.put(WebExpertSettingsProperties.PROP_READ_ONLY, isReadOnly());
|
||||
expertSettings.put(WebExpertSettingsProperties.PROP_DEFAULT_CATALOG, getDefaultCatalogName());
|
||||
@@ -541,12 +541,9 @@ public class WebConnectionInfo {
|
||||
}
|
||||
|
||||
@Property
|
||||
public boolean isAutocommit() {
|
||||
Boolean isAutoCommit = dataSourceContainer.getConnectionConfiguration().getBootstrap().getDefaultAutoCommit();
|
||||
if (isAutoCommit == null) {
|
||||
return true;
|
||||
}
|
||||
return isAutoCommit;
|
||||
@Nullable
|
||||
public Boolean isAutocommit() {
|
||||
return dataSourceContainer.getConnectionConfiguration().getBootstrap().getDefaultAutoCommit();
|
||||
}
|
||||
|
||||
@Property
|
||||
@@ -598,4 +595,13 @@ public class WebConnectionInfo {
|
||||
this.credentialsSavedInSession = credentialsSavedInSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* Database-dependent driver configuration used to drive data import
|
||||
*/
|
||||
@Property
|
||||
@NotNull
|
||||
public WebDriverConfiguration getDriverConfiguration() {
|
||||
return new WebDriverConfiguration(dataSourceContainer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.model;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
|
||||
/**
|
||||
* Generic id/name/description dictionary value.
|
||||
*/
|
||||
public class WebDictionaryValueInfo {
|
||||
|
||||
@NotNull
|
||||
private final String id;
|
||||
@NotNull
|
||||
private final String name;
|
||||
@Nullable
|
||||
private final String description;
|
||||
|
||||
public WebDictionaryValueInfo(@NotNull String id, @NotNull String name, @Nullable String description) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Property
|
||||
@NotNull
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Property
|
||||
@NotNull
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Property
|
||||
@Nullable
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.model;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.DBPDataSource;
|
||||
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
|
||||
import org.jkiss.dbeaver.model.DBUtils;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
import org.jkiss.dbeaver.model.sql.SQLDialectInsertReplaceMethod;
|
||||
import org.jkiss.dbeaver.model.sql.SQLDialectMetadata;
|
||||
import org.jkiss.dbeaver.model.struct.DBSDataBulkLoader;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class WebDriverConfiguration {
|
||||
|
||||
@NotNull
|
||||
private final DBPDataSourceContainer dataSourceContainer;
|
||||
|
||||
public WebDriverConfiguration(@NotNull DBPDataSourceContainer dataSourceContainer) {
|
||||
this.dataSourceContainer = dataSourceContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 'On duplicate key' replace methods supported by this connection's SQL dialect
|
||||
*/
|
||||
@Property
|
||||
@NotNull
|
||||
public List<WebDictionaryValueInfo> getSupportedInsertReplaceMethods() {
|
||||
SQLDialectMetadata dialect = dataSourceContainer.getDriver().getScriptDialect();
|
||||
List<SQLDialectInsertReplaceMethod> replaceMethods = dialect.getSupportedInsertReplaceMethodsDescriptors();
|
||||
if (CommonUtils.isEmpty(replaceMethods)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return replaceMethods.stream()
|
||||
.map(method -> new WebDictionaryValueInfo(method.getId(), method.getLabel(), method.getDescription()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the connection's database supports bulk load during data import
|
||||
*/
|
||||
@Property
|
||||
public boolean isSupportsBulkLoad() {
|
||||
DBPDataSource dataSource = dataSourceContainer.getDataSource();
|
||||
return dataSource != null && DBUtils.getAdapter(DBSDataBulkLoader.class, dataSource) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the connection's database supports transaction
|
||||
*/
|
||||
@Property
|
||||
public boolean isSupportsTransactions() {
|
||||
DBPDataSource dataSource = dataSourceContainer.getDataSource();
|
||||
return dataSource != null && dataSource.getInfo().supportsTransactions();
|
||||
}
|
||||
}
|
||||
+43
-3
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.model;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
@@ -5,6 +21,7 @@ import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.DBPObject;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDriver;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDriverConstants;
|
||||
import org.jkiss.dbeaver.model.meta.IPropertyValueListProvider;
|
||||
import org.jkiss.dbeaver.model.meta.IPropertyValueValidator;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
@@ -15,6 +32,7 @@ import org.jkiss.utils.CommonUtils;
|
||||
public class WebExpertSettingsProperties implements DBPObject {
|
||||
public static final String PROP_READ_ONLY = "readOnly";
|
||||
public static final String PROP_AUTO_COMMIT = "autocommit";
|
||||
public static final String PROP_AUTO_COMMIT_MODE = "autoCommitMode";
|
||||
public static final String PROP_KEEP_ALIVE_INTERVAL = "keepAliveInterval";
|
||||
public static final String PROP_DEFAULT_CATALOG = "defaultCatalogName";
|
||||
public static final String PROP_DEFAULT_SCHEMA = "defaultSchemaName";
|
||||
@@ -30,9 +48,15 @@ public class WebExpertSettingsProperties implements DBPObject {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Property(order = 2, id = PROP_AUTO_COMMIT, visibleIf = AutoCommitFieldValidator.class)
|
||||
public boolean isAutoCommit() {
|
||||
return true;
|
||||
@NotNull
|
||||
@Property(
|
||||
order = 2,
|
||||
id = PROP_AUTO_COMMIT_MODE,
|
||||
visibleIf = AutoCommitFieldValidator.class,
|
||||
listProvider = AutoCommitListProvider.class
|
||||
)
|
||||
public AutoCommitMode isAutoCommitMode() {
|
||||
return AutoCommitMode.DEFAULT;
|
||||
}
|
||||
|
||||
@Property(order = 3, id = PROP_READ_ONLY, visibleIf = ReadOnlyFieldValidator.class)
|
||||
@@ -40,11 +64,13 @@ public class WebExpertSettingsProperties implements DBPObject {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Property(order = 4, id = PROP_DEFAULT_CATALOG, visibleIf = DefaultCatalogFieldVisibleValidator.class)
|
||||
public String getDefaultCatalog() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Property(order = 5, id = PROP_DEFAULT_SCHEMA, visibleIf = DefaultSchemaFieldVisibleValidator.class)
|
||||
public String getDefaultSchema() {
|
||||
return null;
|
||||
@@ -85,4 +111,18 @@ public class WebExpertSettingsProperties implements DBPObject {
|
||||
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_SCHEMA_SELECTION), true);
|
||||
}
|
||||
}
|
||||
|
||||
public static class AutoCommitListProvider implements IPropertyValueListProvider<WebExpertSettingsProperties> {
|
||||
|
||||
@Override
|
||||
public boolean allowCustomValue() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object[] getPossibleValues(@Nullable WebExpertSettingsProperties object) {
|
||||
return AutoCommitMode.values();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,13 +192,16 @@ public class ServletAppUtils {
|
||||
@NotNull String serviceId,
|
||||
@NotNull String origin
|
||||
) throws DBException {
|
||||
String finalOrigin = webAuthApplication.modifyOrigin(origin);
|
||||
String finalOrigin = ServletAppUtils.substringBeforeRootURI(webAuthApplication.modifyOrigin(origin));
|
||||
String serviceUriSegment = removeSideSlashes(webAuthApplication.getAuthServiceUriSegment());
|
||||
String rootUri = removeSideSlashes(getServletApplication().getRootURI());
|
||||
if (finalOrigin.endsWith("/" + rootUri) && serviceUriSegment.startsWith(rootUri + "/")) {
|
||||
finalOrigin = finalOrigin.substring(0, finalOrigin.length() - rootUri.length());
|
||||
}
|
||||
StringBuilder authUriBuilder = new StringBuilder(removeSideSlashes(finalOrigin));
|
||||
if (CommonUtils.isNotEmpty(rootUri)) {
|
||||
authUriBuilder.append("/").append(rootUri);
|
||||
}
|
||||
if (CommonUtils.isNotEmpty(serviceUriSegment)) {
|
||||
authUriBuilder.append("/").append(serviceUriSegment);
|
||||
}
|
||||
@@ -297,6 +300,11 @@ public class ServletAppUtils {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getHostOriginFromRequest(@NotNull HttpServletRequest request) {
|
||||
return substringBeforeRootURI(getOriginFromRequest(request));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getOriginFromRequest(@NotNull HttpServletRequest request) {
|
||||
String origin = request.getHeader(HEADER_ORIGIN);
|
||||
|
||||
+11
-6
@@ -57,9 +57,12 @@ public class WebDataSourceUtils {
|
||||
private WebDataSourceUtils() {
|
||||
}
|
||||
|
||||
public static void saveCredentialsInDataSource(WebConnectionInfo webConnectionInfo, DBPDataSourceContainer dataSourceContainer, DBPConnectionConfiguration configuration) {
|
||||
public static void saveCredentialsInDataSource(
|
||||
@NotNull WebConnectionInfo webConnectionInfo,
|
||||
@NotNull DBPDataSourceContainer dataSourceContainer,
|
||||
@NotNull DBPConnectionConfiguration configuration
|
||||
) {
|
||||
// Properties passed from web
|
||||
// webConnectionInfo may be null in some cases (e.g. connection test when no actual connection exist yet)
|
||||
Map<String, Object> authProperties = webConnectionInfo.getSavedAuthProperties();
|
||||
if (authProperties != null) {
|
||||
authProperties.forEach((s, o) -> configuration.setAuthProperty(s, CommonUtils.toString(o)));
|
||||
@@ -307,9 +310,7 @@ public class WebDataSourceUtils {
|
||||
if (config.getKeepAliveInterval() >= 0) {
|
||||
dsConfig.setKeepAliveInterval(config.getKeepAliveInterval());
|
||||
}
|
||||
if (config.isDefaultAutoCommit() != null) {
|
||||
dsConfig.getBootstrap().setDefaultAutoCommit(config.isDefaultAutoCommit());
|
||||
}
|
||||
dsConfig.getBootstrap().setDefaultAutoCommit(config.isDefaultAutoCommit());
|
||||
dsConfig.getBootstrap().setDefaultCatalogName(config.getDefaultCatalogName());
|
||||
dsConfig.getBootstrap().setDefaultSchemaName(config.getDefaultSchemaName());
|
||||
// Save provider props
|
||||
@@ -330,7 +331,11 @@ public class WebDataSourceUtils {
|
||||
}
|
||||
|
||||
if (CommonUtils.isEmpty(config.getUrl())) {
|
||||
dsConfig.setUrl(driver.getConnectionURL(dsConfig));
|
||||
try {
|
||||
dsConfig.setUrl(driver.getConnectionURL(dsConfig));
|
||||
} catch (DBException e) {
|
||||
log.error("Error preparing connection URL", e);
|
||||
}
|
||||
}
|
||||
// Save network handlers
|
||||
if (config.getNetworkHandlersConfig() != null) {
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-Name: Cloudbeaver Community Product
|
||||
Bundle-SymbolicName: io.cloudbeaver.product.ce;singleton:=true
|
||||
Bundle-Version: 26.1.4.qualifier
|
||||
Bundle-Release-Date: 20260803
|
||||
Bundle-Version: 26.1.5.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: io.cloudbeaver.server.ce
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
</product>
|
||||
</extension>
|
||||
|
||||
<extension point="io.cloudbeaver.feature">
|
||||
<feature id="ai" label="AI Integration" description="Enables AI Chat in the application."/>
|
||||
</extension>
|
||||
|
||||
<extension point="org.jkiss.dbeaver.application">
|
||||
<application
|
||||
id="cloudbeaver-ce"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.product.ce</artifactId>
|
||||
<version>26.1.4-SNAPSHOT</version>
|
||||
<version>26.1.5-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -2,8 +2,8 @@ Manifest-Version: 1.0
|
||||
Bundle-ManifestVersion: 2
|
||||
Bundle-Name: Cloudbeaver Base JDBC drivers
|
||||
Bundle-SymbolicName: io.cloudbeaver.resources.drivers.base;singleton:=true
|
||||
Bundle-Version: 1.0.151.qualifier
|
||||
Bundle-Release-Date: 20260803
|
||||
Bundle-Version: 1.0.152.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Automatic-Module-Name: io.cloudbeaver.resources.drivers.base
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.resources.drivers.base</artifactId>
|
||||
<version>1.0.151-SNAPSHOT</version>
|
||||
<version>1.0.152-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
</project>
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-Name: CloudBeaver Community
|
||||
Bundle-SymbolicName: io.cloudbeaver.server.ce;singleton:=true
|
||||
Bundle-Version: 26.1.4.qualifier
|
||||
Bundle-Release-Date: 20260803
|
||||
Bundle-Version: 26.1.5.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Bundle-Activator: io.cloudbeaver.server.CBPlatformActivator
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.server.ce</artifactId>
|
||||
<version>26.1.4-SNAPSHOT</version>
|
||||
<version>26.1.5-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
####################################################
|
||||
# Core API
|
||||
####################################################
|
||||
|
||||
extend type ServerConfig {
|
||||
serverURL: String! @deprecated
|
||||
rootURI: String!
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-Name: Cloudbeaver Web Server
|
||||
Bundle-SymbolicName: io.cloudbeaver.server;singleton:=true
|
||||
Bundle-Version: 26.1.4.qualifier
|
||||
Bundle-Release-Date: 20260803
|
||||
Bundle-Version: 26.1.5.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: org.jkiss.dbeaver.model.sql;visibility:=reexport,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.server</artifactId>
|
||||
<version>26.1.4-SNAPSHOT</version>
|
||||
<version>26.1.5-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
# Any object (JSON)
|
||||
####################################################
|
||||
# Base schema
|
||||
####################################################
|
||||
|
||||
"Any object (JSON)"
|
||||
scalar Object
|
||||
# Date/Time
|
||||
"Date/Time"
|
||||
scalar DateTime
|
||||
scalar Date
|
||||
|
||||
|
||||
@@ -556,7 +556,7 @@ type ConnectionInfo {
|
||||
supportedDataFormats: [ ResultDataFormat! ]!
|
||||
configurationType: DriverConfigurationType
|
||||
|
||||
#Access properties
|
||||
"Access properties"
|
||||
canViewSettings: Boolean!
|
||||
canEdit: Boolean!
|
||||
canDelete: Boolean!
|
||||
@@ -572,6 +572,29 @@ type ConnectionInfo {
|
||||
tools: [String!]! @since(version: "24.1.3")
|
||||
|
||||
connectionType: ID! @since(version: "26.1.2")
|
||||
|
||||
"Driver configuration"
|
||||
driverConfiguration: DriverConfiguration! @since(version: "26.1.2")
|
||||
}
|
||||
|
||||
"Database-dependent metadata describing features supported by the driver"
|
||||
type DriverConfiguration {
|
||||
"Supported 'on duplicate key' replace methods for this connection's SQL dialect"
|
||||
supportedInsertReplaceMethods: [DictionaryValueInfo!]!
|
||||
"Whether the connection's database supports bulk load during data import"
|
||||
supportsBulkLoad: Boolean!
|
||||
"Whether the connection's database supports transactions"
|
||||
supportsTransactions: Boolean!
|
||||
}
|
||||
|
||||
"Generic dictionary value: an identifier with a human readable name and an optional description"
|
||||
type DictionaryValueInfo {
|
||||
"Value ID"
|
||||
id: ID!
|
||||
"Human readable value name"
|
||||
name: String!
|
||||
"Value description"
|
||||
description: String
|
||||
}
|
||||
|
||||
type ConnectionFolderInfo {
|
||||
@@ -714,7 +737,7 @@ input ConnectionConfig {
|
||||
"ID of database driver"
|
||||
driverId: ID
|
||||
|
||||
# Custom connection parameters (all optional)
|
||||
"Custom connection parameters (all optional)"
|
||||
host: String
|
||||
port: String
|
||||
serverName: String
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
####################################################
|
||||
# Datasource events
|
||||
####################################################
|
||||
|
||||
extend enum CBServerEventId {
|
||||
|
||||
"Datasource created"
|
||||
|
||||
@@ -31,6 +31,10 @@ enum CBServerEventId {
|
||||
|
||||
"Open URL request in client"
|
||||
cb_open_url @since(version: "25.3.1")
|
||||
"User-specific object settings updated"
|
||||
cb_object_settings_updated @since(version: "26.1.4")
|
||||
"User-specific object settings deleted"
|
||||
cb_object_settings_deleted @since(version: "26.1.4")
|
||||
}
|
||||
|
||||
"Events sent by client"
|
||||
@@ -57,6 +61,7 @@ enum CBEventTopic {
|
||||
cb_workspace_configuration @since(version: "25.1.1")
|
||||
cb_notification @since(version: "25.2.1")
|
||||
cb_session_action @since(version: "25.3.1")
|
||||
cb_object_settings @since(version: "26.1.4")
|
||||
}
|
||||
|
||||
"Base server event interface"
|
||||
@@ -178,6 +183,26 @@ type WSOpenUrlEvent implements CBServerEvent @since(version: "25.3.1") {
|
||||
actionId: String!
|
||||
}
|
||||
|
||||
"Type of the object that owns user-specific settings"
|
||||
enum WSObjectSettingsType @since(version: "26.1.4") {
|
||||
datasource
|
||||
project
|
||||
}
|
||||
|
||||
"User-specific object settings change event (e.g. navigator view)"
|
||||
type WSObjectSettingsEvent implements CBServerEvent @since(version: "26.1.4") {
|
||||
id: CBServerEventId!
|
||||
topicId: CBEventTopic!
|
||||
"Type of the object the settings belong to"
|
||||
smObjectType: WSObjectSettingsType!
|
||||
"Project of the object"
|
||||
projectId: String
|
||||
"Id of the object"
|
||||
objectId: String!
|
||||
"Ids of the settings that were changed"
|
||||
settingIds: [String!]!
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
emptyEvent: Boolean
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
####################################################
|
||||
# SQL events
|
||||
####################################################
|
||||
|
||||
extend enum CBServerEventId {
|
||||
"Database output log updated (e.g., DBMS in Oracle)"
|
||||
cb_database_output_log_updated,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
####################################################
|
||||
# Nodes, objects and properties
|
||||
####################################################
|
||||
|
||||
type ObjectDescriptor {
|
||||
id: Int
|
||||
@@ -143,7 +145,7 @@ extend type Query {
|
||||
"Refreshes node based on the node path"
|
||||
navRefreshNode( nodePath: ID! ): Boolean @deprecated # Use navReloadNode method from Mutation (24.2.4)
|
||||
|
||||
# contextId currently not using
|
||||
"contextId currently not using"
|
||||
navGetStructContainers( projectId: ID, connectionId: ID!, contextId: ID, catalog: ID ): DatabaseStructContainers!
|
||||
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ type SQLCompletionProposal {
|
||||
# Data read / SQL execution
|
||||
####################################################
|
||||
|
||||
# SQL context must be created for each SQL editor
|
||||
"SQL context must be created for each SQL editor"
|
||||
type SQLContextInfo {
|
||||
|
||||
id: ID!
|
||||
@@ -153,10 +153,12 @@ type SQLResultSet {
|
||||
"Returns list of rows in the result set. Each row contains data and metadata"
|
||||
rowsWithMetaData: [SQLResultRowMetaData] @since(version: "23.3.5")
|
||||
|
||||
# True means that resultset was generated by single entity query
|
||||
# New rows can be added, old rows can be deleted
|
||||
"""
|
||||
True means that resultset was generated by single entity query
|
||||
New rows can be added, old rows can be deleted
|
||||
"""
|
||||
singleEntity: Boolean!
|
||||
# server always returns hasMoreData = false
|
||||
"server always returns hasMoreData = false"
|
||||
hasMoreData: Boolean!
|
||||
"Identifies if result set has row identifier. If true then it is possible update data or load LOB files"
|
||||
hasRowIdentifier: Boolean! @deprecated (reason: "use rowIdentifierState (25.3.4)")
|
||||
@@ -213,7 +215,7 @@ type SQLQueryResults {
|
||||
updateRowCount: Float
|
||||
sourceQuery: String
|
||||
|
||||
# Actual data format of this result
|
||||
"Actual data format of this result"
|
||||
dataFormat: ResultDataFormat
|
||||
|
||||
resultSet: SQLResultSet
|
||||
@@ -221,17 +223,17 @@ type SQLQueryResults {
|
||||
|
||||
type SQLExecuteInfo {
|
||||
|
||||
# Status message
|
||||
"Status message"
|
||||
statusMessage: String
|
||||
# Execute time (ms)
|
||||
"Execute time (ms)"
|
||||
duration: Int!
|
||||
# Actual conditions applied to query
|
||||
"Actual conditions applied to query"
|
||||
filterText: String
|
||||
# Full query that was executed, contains all used filters
|
||||
"Full query that was executed, contains all used filters"
|
||||
fullQuery: String
|
||||
# Original query that was executed, without modifications
|
||||
"Original query that was executed, without modifications"
|
||||
originalQuery: String
|
||||
# Results
|
||||
"Results"
|
||||
results: [ SQLQueryResults! ]!
|
||||
}
|
||||
|
||||
@@ -296,6 +298,8 @@ type SQLQueryGenerator {
|
||||
input SQLQueryGeneratorOptions {
|
||||
useFullyQualifiedNames: Boolean!
|
||||
compactSql: Boolean!
|
||||
"Include nested objects, comments and permissions in generated DDL (since 26.0.4)"
|
||||
showFullDdl: Boolean
|
||||
}
|
||||
|
||||
####################################################
|
||||
@@ -397,7 +401,7 @@ extend type Query {
|
||||
options: Object @deprecated(reason: "use generatorOptions (26.0.3)")
|
||||
nodePathList: [String!]!,
|
||||
generatorOptions: SQLQueryGeneratorOptions
|
||||
): String!
|
||||
): String! @deprecated(reason: "use asyncSqlGenerateEntityQuery")
|
||||
|
||||
"""
|
||||
Returns an auto-generated SQL query based on provided rows data.
|
||||
@@ -593,6 +597,13 @@ extend type Mutation {
|
||||
"Returns row count for async SQL row data count task"
|
||||
asyncSqlRowDataCountResult(taskId: ID!): Int!
|
||||
|
||||
"Creates async task to generate entity SQL. Generated SQL is returned in the taskResult field"
|
||||
asyncSqlGenerateEntityQuery(
|
||||
generatorId: String!,
|
||||
nodePathList: [String!]!,
|
||||
generatorOptions: SQLQueryGeneratorOptions
|
||||
): AsyncTaskInfo!
|
||||
|
||||
"Reads dynamic trace from provided database results"
|
||||
sqlGetDynamicTrace(
|
||||
projectId: ID,
|
||||
|
||||
+22
-7
@@ -21,8 +21,10 @@ import io.cloudbeaver.WebServiceUtils;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.server.CBConstants;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBConstants;
|
||||
import org.jkiss.dbeaver.model.DatabaseURL;
|
||||
import org.jkiss.dbeaver.model.connection.*;
|
||||
import org.jkiss.dbeaver.model.impl.auth.AuthModelDatabaseNative;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
@@ -180,13 +182,26 @@ public class WebDatabaseDriverInfo {
|
||||
@NotNull
|
||||
@Property
|
||||
public WebPropertyInfo[] getDriverProperties() throws DBWebException {
|
||||
DBPConnectionConfiguration cfg = new DBPConnectionConfiguration();
|
||||
cfg.setUrl(CommonUtils.notEmpty(driver.getSampleURL()));
|
||||
cfg.setHostName(CBConstants.HOST_LOCALHOST);
|
||||
cfg.setHostPort(driver.getDefaultPort());
|
||||
cfg.setDatabaseName(driver.getDefaultDatabase());
|
||||
cfg.setUrl(driver.getConnectionURL(cfg));
|
||||
return WebServiceUtils.getDriverProperties(webSession, driver, null, cfg);
|
||||
try {
|
||||
DBPConnectionConfiguration cfg = new DBPConnectionConfiguration();
|
||||
cfg.setUrl(CommonUtils.notEmpty(driver.getSampleURL()));
|
||||
DatabaseURL.Pattern dbUrlPattern = DatabaseURL.getUrlPattern(CommonUtils.notEmpty(driver.getSampleURL()));
|
||||
for (String propName : dbUrlPattern.getMandatoryPropertyNames()) {
|
||||
switch (propName) {
|
||||
case DBConstants.PROP_HOST -> cfg.setHostName(CBConstants.HOST_LOCALHOST);
|
||||
case DBConstants.PROP_PORT -> cfg.setHostPort(CommonUtils.notNull(driver.getDefaultPort(), "0"));
|
||||
case DBConstants.PROP_SERVER -> cfg.setServerName(CommonUtils.notNull(driver.getDefaultServer(), propName));
|
||||
case DBConstants.PROP_USER -> cfg.setUserName(CommonUtils.notNull(driver.getDefaultUser(), propName));
|
||||
case DBConstants.PROP_DATABASE, DBConstants.PROP_FOLDER, DBConstants.PROP_FILE ->
|
||||
cfg.setDatabaseName(CommonUtils.notNull(driver.getDefaultDatabase(), propName));
|
||||
default -> log.debug("Unexpected mandatory URL property " + propName);
|
||||
}
|
||||
}
|
||||
cfg.setUrl(driver.getConnectionURL(cfg));
|
||||
return WebServiceUtils.getDriverProperties(webSession, driver, null, cfg);
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error obtaining driver properties", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Property
|
||||
|
||||
+10
-2
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2024 DBeaver Corp and others
|
||||
* Copyright (C) 2010-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -44,12 +44,17 @@ public class CBEventsLongPolling implements CBWebSessionEventHandler {
|
||||
private final BlockingQueue<WSEvent> queue = new LinkedBlockingQueue<>(QUEUE_CAPACITY);
|
||||
private final CBClientEventProcessor processor;
|
||||
private volatile long lastPoll;
|
||||
private final CBEventsLongPollingServlet servlet;
|
||||
|
||||
public CBEventsLongPolling(@NotNull BaseWebSession webSession) {
|
||||
public CBEventsLongPolling(
|
||||
@NotNull BaseWebSession webSession,
|
||||
@NotNull CBEventsLongPollingServlet owner
|
||||
) {
|
||||
this.webSession = webSession;
|
||||
this.lastPoll = System.currentTimeMillis();
|
||||
this.webSession.addEventHandler(this);
|
||||
this.processor = new CBClientEventProcessor(this.webSession);
|
||||
this.servlet = owner;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -90,8 +95,10 @@ public class CBEventsLongPolling implements CBWebSessionEventHandler {
|
||||
|
||||
@Override
|
||||
public void migrateToSession(@NotNull BaseWebSession newSession) {
|
||||
String oldSid = this.webSession.getSessionId();
|
||||
this.webSession = newSession;
|
||||
this.processor.setWebSession(newSession);
|
||||
servlet.rekeySession(oldSid, newSession.getSessionId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -112,6 +119,7 @@ public class CBEventsLongPolling implements CBWebSessionEventHandler {
|
||||
queue.clear();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CBEventsLongPolling{" +
|
||||
|
||||
+16
-18
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2025 DBeaver Corp and others
|
||||
* Copyright (C) 2010-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -83,7 +83,7 @@ public class CBEventsLongPollingServlet extends HttpServlet {
|
||||
return;
|
||||
}
|
||||
|
||||
CBEventsLongPolling ps = getOrCreatePollSession(ws);
|
||||
CBEventsLongPolling ps = getOrCreatePollSession(ws, resp);
|
||||
ps.onPoll();
|
||||
|
||||
List<WSEvent> events = ps.pollEvents(POLL_TIMEOUT_SEC);
|
||||
@@ -111,7 +111,7 @@ public class CBEventsLongPollingServlet extends HttpServlet {
|
||||
return;
|
||||
}
|
||||
|
||||
CBEventsLongPolling ps = getOrCreatePollSession(ws);
|
||||
CBEventsLongPolling ps = getOrCreatePollSession(ws, resp);
|
||||
ps.onUserActivity();
|
||||
|
||||
String json = new String(req.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
|
||||
@@ -163,18 +163,7 @@ public class CBEventsLongPollingServlet extends HttpServlet {
|
||||
) {
|
||||
|
||||
try {
|
||||
BaseWebSession ws = resolveSession(req);
|
||||
|
||||
resp.setHeader(WSConstants.WS_SESSION_HEADER, ws.getSessionId());
|
||||
ServletAppUtils.addResponseCookie(
|
||||
req,
|
||||
resp,
|
||||
CBConstants.CB_SESSION_COOKIE_NAME,
|
||||
ws.getSessionId(),
|
||||
-1
|
||||
);
|
||||
|
||||
return ws;
|
||||
return resolveSession(req);
|
||||
} catch (SMException e) {
|
||||
log.debug("LP request rejected: access token expired");
|
||||
sendError(resp, HttpConstants.CODE_TOKEN_EXPIRED, e.getMessage());
|
||||
@@ -214,7 +203,7 @@ public class CBEventsLongPollingServlet extends HttpServlet {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private CBEventsLongPolling getOrCreatePollSession(@NotNull BaseWebSession ws) {
|
||||
private CBEventsLongPolling getOrCreatePollSession(@NotNull BaseWebSession ws, @NotNull HttpServletResponse resp) {
|
||||
final String sid = ws.getSessionId();
|
||||
|
||||
return sessions.compute(sid, (key, existing) -> {
|
||||
@@ -222,9 +211,10 @@ public class CBEventsLongPollingServlet extends HttpServlet {
|
||||
return existing;
|
||||
}
|
||||
|
||||
CBEventsLongPolling ps = new CBEventsLongPolling(ws);
|
||||
CBEventsLongPolling ps = new CBEventsLongPolling(ws, this);
|
||||
|
||||
ps.handleWebSessionEvent(new WSSocketConnectedEvent(ws.getApplication().getApplicationRunId()));
|
||||
resp.setHeader(WSConstants.WS_SESSION_HEADER, sid);
|
||||
log.debug("HTTP Long-Poll channel opened for session " + sid);
|
||||
|
||||
return ps;
|
||||
@@ -253,7 +243,8 @@ public class CBEventsLongPollingServlet extends HttpServlet {
|
||||
|
||||
@NotNull
|
||||
protected static WebHttpRequestInfo createRequestInfo(
|
||||
@Nullable String sessionId, @NotNull HttpServletRequest req
|
||||
@Nullable String sessionId,
|
||||
@NotNull HttpServletRequest req
|
||||
) {
|
||||
return new WebHttpRequestInfo(
|
||||
sessionId,
|
||||
@@ -263,6 +254,13 @@ public class CBEventsLongPollingServlet extends HttpServlet {
|
||||
);
|
||||
}
|
||||
|
||||
void rekeySession(@NotNull String oldSid, @NotNull String newSid) {
|
||||
CBEventsLongPolling moved = sessions.get(oldSid);
|
||||
if (moved != null) {
|
||||
sessions.put(newSid, moved);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a {@link BaseWebSession} from an incoming HTTP request.
|
||||
* <p>
|
||||
|
||||
+24
-7
@@ -33,7 +33,6 @@ import org.jkiss.dbeaver.model.struct.rdb.DBSSchema;
|
||||
import org.jkiss.dbeaver.model.struct.rdb.DBSTable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -58,6 +57,7 @@ public class WebDatabaseObjectInfo {
|
||||
public static final String OBJECT_FEATURE_SCHEMA = "schema";
|
||||
public static final String OBJECT_FEATURE_SCRIPT = "script";
|
||||
public static final String OBJECT_FEATURE_SCRIPT_EXTENDED = "scriptExtended";
|
||||
public static final String OBJECT_FEATURE_SUPPORTS_FULL_DDL = "supportsFullDdl";
|
||||
|
||||
private final WebSession session;
|
||||
private final DBSObject object;
|
||||
@@ -168,10 +168,21 @@ public class WebDatabaseObjectInfo {
|
||||
return features.toArray(new String[0]);
|
||||
}
|
||||
|
||||
private void getObjectFeatures(DBSObject object, List<String> features) {
|
||||
private void getObjectFeatures(@NotNull DBSObject object, @NotNull List<String> features) {
|
||||
if (object instanceof DBPScriptObject) {
|
||||
features.add(OBJECT_FEATURE_SCRIPT);
|
||||
}
|
||||
if (object instanceof DBPScriptObjectExt) {
|
||||
features.add(OBJECT_FEATURE_SCRIPT_EXTENDED);
|
||||
}
|
||||
if (object instanceof DBPScriptObjectExt2 scriptObjectExt2 && (
|
||||
scriptObjectExt2.supportsObjectDefinitionOption(DBPScriptObject.OPTION_INCLUDE_NESTED_OBJECTS)
|
||||
|| scriptObjectExt2.supportsObjectDefinitionOption(DBPScriptObject.OPTION_INCLUDE_COMMENTS)
|
||||
|| scriptObjectExt2.supportsObjectDefinitionOption(DBPScriptObject.OPTION_INCLUDE_PERMISSIONS))
|
||||
) {
|
||||
features.add(OBJECT_FEATURE_SUPPORTS_FULL_DDL);
|
||||
}
|
||||
boolean isDiagramSupported = true;
|
||||
if (object instanceof DBPScriptObject) features.add(OBJECT_FEATURE_SCRIPT);
|
||||
if (object instanceof DBPScriptObjectExt) features.add(OBJECT_FEATURE_SCRIPT_EXTENDED);
|
||||
if (object instanceof DBSDataContainer) {
|
||||
features.add(OBJECT_FEATURE_DATA_CONTAINER);
|
||||
if (((DBSDataContainer) object).isFeatureSupported(DBSDataContainer.FEATURE_DATA_FILTER)) {
|
||||
@@ -181,7 +192,9 @@ public class WebDatabaseObjectInfo {
|
||||
isDiagramSupported = false;
|
||||
}
|
||||
}
|
||||
if (object instanceof DBSDataManipulator) features.add(OBJECT_FEATURE_DATA_MANIPULATOR);
|
||||
if (object instanceof DBSDataManipulator) {
|
||||
features.add(OBJECT_FEATURE_DATA_MANIPULATOR);
|
||||
}
|
||||
if (object instanceof DBSEntity) {
|
||||
features.add(OBJECT_FEATURE_ENTITY);
|
||||
if (object instanceof DBSDataType
|
||||
@@ -192,8 +205,12 @@ public class WebDatabaseObjectInfo {
|
||||
features.add(OBJECT_FEATURE_RELATIONAL_ENTITY);
|
||||
}
|
||||
}
|
||||
if (object instanceof DBSSchema) features.add(OBJECT_FEATURE_SCHEMA);
|
||||
if (object instanceof DBSCatalog) features.add(OBJECT_FEATURE_CATALOG);
|
||||
if (object instanceof DBSSchema) {
|
||||
features.add(OBJECT_FEATURE_SCHEMA);
|
||||
}
|
||||
if (object instanceof DBSCatalog) {
|
||||
features.add(OBJECT_FEATURE_CATALOG);
|
||||
}
|
||||
if (object instanceof DBSObjectContainer objectContainer) {
|
||||
features.add(OBJECT_FEATURE_OBJECT_CONTAINER);
|
||||
try {
|
||||
|
||||
@@ -81,6 +81,15 @@ public interface DBWServiceSQL extends DBWService {
|
||||
@NotNull WebSQLGeneratorOptions options
|
||||
) throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
@WebAction
|
||||
WebAsyncTaskInfo asyncGenerateEntityQuery(
|
||||
@NotNull WebSession session,
|
||||
@NotNull String generatorId,
|
||||
@NotNull List<String> nodePathList,
|
||||
@NotNull WebSQLGeneratorOptions options
|
||||
) throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
@WebAction
|
||||
String sqlGenerateResultSetQuery(
|
||||
|
||||
+2
-1
@@ -18,6 +18,7 @@ package io.cloudbeaver.service.sql;
|
||||
|
||||
public record WebSQLGeneratorOptions(
|
||||
boolean useFullyQualifiedNames,
|
||||
boolean compactSql
|
||||
boolean compactSql,
|
||||
boolean showFullDdl
|
||||
) {
|
||||
}
|
||||
|
||||
+10
-2
@@ -244,6 +244,13 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL>
|
||||
getService(env).asyncGetQueryResults(
|
||||
getWebSession(env), getArgumentVal(env, "taskId")
|
||||
))
|
||||
.dataFetcher("asyncSqlGenerateEntityQuery", env ->
|
||||
getService(env).asyncGenerateEntityQuery(
|
||||
getWebSession(env),
|
||||
getArgumentVal(env, "generatorId"),
|
||||
getArgumentVal(env, "nodePathList"),
|
||||
getGeneratorOptions(env)
|
||||
))
|
||||
.dataFetcher("asyncSqlExplainExecutionPlan", env ->
|
||||
getService(env).asyncSqlExplainExecutionPlan(
|
||||
getSQLContext(env),
|
||||
@@ -429,11 +436,12 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL>
|
||||
private static WebSQLGeneratorOptions getGeneratorOptions(@NotNull DataFetchingEnvironment env) {
|
||||
Map<String, Object> optionsMap = getArgument(env, "generatorOptions");
|
||||
if (optionsMap == null) {
|
||||
return new WebSQLGeneratorOptions(true, false);
|
||||
return new WebSQLGeneratorOptions(true, false, false);
|
||||
}
|
||||
return new WebSQLGeneratorOptions(
|
||||
CommonUtils.toBoolean(optionsMap.get("useFullyQualifiedNames")),
|
||||
CommonUtils.toBoolean(optionsMap.get("compactSql"))
|
||||
CommonUtils.toBoolean(optionsMap.get("compactSql")),
|
||||
CommonUtils.toBoolean(optionsMap.get("showFullDdl"))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+61
-15
@@ -252,10 +252,33 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
@NotNull WebSQLGeneratorOptions options
|
||||
) throws DBWebException {
|
||||
List<DBSObject> objectList = getObjectListFromNodeIds(session, nodePathList);
|
||||
return createAndRunGenerator(
|
||||
session, generatorId, objectList,
|
||||
options.useFullyQualifiedNames(), options.compactSql()
|
||||
);
|
||||
return createAndRunGenerator(session, generatorId, objectList, options);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public WebAsyncTaskInfo asyncGenerateEntityQuery(
|
||||
@NotNull WebSession session,
|
||||
@NotNull String generatorId,
|
||||
@NotNull List<String> nodePathList,
|
||||
@NotNull WebSQLGeneratorOptions options
|
||||
) throws DBWebException {
|
||||
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<>() {
|
||||
@Override
|
||||
public void run(@NotNull DBRProgressMonitor monitor) throws InvocationTargetException {
|
||||
try {
|
||||
monitor.beginTask("Generate SQL", 1);
|
||||
monitor.subTask("Generate '" + generatorId + "' SQL");
|
||||
List<DBSObject> objectList = getObjectListFromNodeIds(session, monitor, nodePathList);
|
||||
this.result = createAndRunGenerator(monitor, generatorId, objectList, options);
|
||||
} catch (Throwable e) {
|
||||
throw new InvocationTargetException(e);
|
||||
} finally {
|
||||
monitor.done();
|
||||
}
|
||||
}
|
||||
};
|
||||
return session.createAndRunAsyncTask("Generate SQL", runnable);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -270,10 +293,7 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
) throws DBWebException {
|
||||
checkAndFillTruncatedData(sqlContext, resultsId, selectedRows);
|
||||
WebDBDResultSetDataProvider dataProvider = new WebDBDResultSetDataProvider(resultsId, sqlContext, selectedRows);
|
||||
return createAndRunGenerator(
|
||||
webSession, generatorId, Collections.singletonList(dataProvider),
|
||||
options.useFullyQualifiedNames(), options.compactSql()
|
||||
);
|
||||
return createAndRunGenerator(webSession, generatorId, Collections.singletonList(dataProvider), options);
|
||||
}
|
||||
|
||||
private void checkAndFillTruncatedData(
|
||||
@@ -308,8 +328,17 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
@NotNull WebSession session,
|
||||
@NotNull String generatorId,
|
||||
@NotNull List<DBSObject> objectList,
|
||||
boolean useFullyQualifiedNames,
|
||||
boolean compactSql
|
||||
@NotNull WebSQLGeneratorOptions options
|
||||
) throws DBWebException {
|
||||
return createAndRunGenerator(session.getProgressMonitor(), generatorId, objectList, options);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String createAndRunGenerator(
|
||||
@NotNull DBRProgressMonitor monitor,
|
||||
@NotNull String generatorId,
|
||||
@NotNull List<DBSObject> objectList,
|
||||
@NotNull WebSQLGeneratorOptions options
|
||||
) throws DBWebException {
|
||||
SQLGeneratorDescriptor generator = SQLGeneratorConfigurationRegistry.getInstance().getGenerator(generatorId);
|
||||
if (generator == null) {
|
||||
@@ -317,9 +346,14 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
}
|
||||
try {
|
||||
SQLGenerator<DBSObject> generatorInstance = generator.createGenerator(objectList);
|
||||
generatorInstance.setFullyQualifiedNames(useFullyQualifiedNames);
|
||||
generatorInstance.setCompactSQL(compactSql);
|
||||
generatorInstance.run(session.getProgressMonitor());
|
||||
generatorInstance.setFullyQualifiedNames(options.useFullyQualifiedNames());
|
||||
generatorInstance.setCompactSQL(options.compactSql());
|
||||
if (options.showFullDdl()) {
|
||||
generatorInstance.setShowFullDdl(true);
|
||||
generatorInstance.setShowComments(true);
|
||||
generatorInstance.setShowPermissions(true);
|
||||
}
|
||||
generatorInstance.run(monitor);
|
||||
return generatorInstance.getResult();
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error creating SQL generator", e);
|
||||
@@ -331,12 +365,24 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<DBSObject> getObjectListFromNodeIds(@NotNull WebSession session, @NotNull List<String> nodePathList) throws DBWebException {
|
||||
private List<DBSObject> getObjectListFromNodeIds(
|
||||
@NotNull WebSession session,
|
||||
@NotNull List<String> nodePathList
|
||||
) throws DBWebException {
|
||||
return getObjectListFromNodeIds(session, session.getProgressMonitor(), nodePathList);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<DBSObject> getObjectListFromNodeIds(
|
||||
@NotNull WebSession session,
|
||||
@NotNull DBRProgressMonitor monitor,
|
||||
@NotNull List<String> nodePathList
|
||||
) throws DBWebException {
|
||||
try {
|
||||
List<DBSObject> objectList = new ArrayList<>(nodePathList.size());
|
||||
DBNModel navigatorModel = session.getNavigatorModelOrThrow();
|
||||
for (String nodePath : nodePathList) {
|
||||
DBNNode node = navigatorModel.getNodeByPath(session.getProgressMonitor(), nodePath);
|
||||
DBNNode node = navigatorModel.getNodeByPath(monitor, nodePath);
|
||||
if (node == null) {
|
||||
throw new DBException("Node '" + nodePath + "' not found");
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-Name: Cloudbeaver Web Service - Administration
|
||||
Bundle-SymbolicName: io.cloudbeaver.service.admin;singleton:=true
|
||||
Bundle-Version: 1.0.150.qualifier
|
||||
Bundle-Release-Date: 20260803
|
||||
Bundle-Version: 1.0.151.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: io.cloudbeaver.server.ce,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.service.admin</artifactId>
|
||||
<version>1.0.150-SNAPSHOT</version>
|
||||
<version>1.0.151-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
####################################################
|
||||
# Administration API
|
||||
####################################################
|
||||
|
||||
"Describes the type of the administrative subject (user or team)"
|
||||
enum AdminSubjectType {
|
||||
"User"
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
Manifest-Version: 1.0
|
||||
Bundle-ManifestVersion: 2
|
||||
Bundle-Name: Cloudbeaver AI Integration
|
||||
Bundle-SymbolicName: io.cloudbeaver.service.ai;singleton:=true
|
||||
Bundle-Version: 1.0.1.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Automatic-Module-Name: io.cloudbeaver.service.ai
|
||||
Require-Bundle: org.jkiss.dbeaver.model.ai,
|
||||
io.cloudbeaver.model,
|
||||
io.cloudbeaver.server.ce
|
||||
Export-Package: io.cloudbeaver.service.ai,
|
||||
io.cloudbeaver.service.ai.model,
|
||||
io.cloudbeaver.service.ai.model.inputs
|
||||
@@ -0,0 +1,6 @@
|
||||
source..=src/
|
||||
output..=target/classes/
|
||||
bin.includes=.,\
|
||||
META-INF/,\
|
||||
schema/,\
|
||||
plugin.xml
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<plugin>
|
||||
<extension point="io.cloudbeaver.service">
|
||||
<service id="ai" label="AI Service" description="AI service"
|
||||
class="io.cloudbeaver.service.ai.gql.WebServiceBindingAI"/>
|
||||
<service id="ai.configurator" label="AI Configurator" description="AI configurator"
|
||||
class="io.cloudbeaver.service.ai.gql.WebServiceAIConfigurator">
|
||||
</service>
|
||||
</extension>
|
||||
|
||||
<extension point="io.cloudbeaver.object.feature.provider">
|
||||
<featureProvider id="AIFeatureProvider" class="io.cloudbeaver.service.ai.WebAIFeatureProvider"/>
|
||||
</extension>
|
||||
|
||||
<extension point="org.jkiss.dbeaver.ws.event">
|
||||
<clientEvent id="cb_client_ai_function_call_confirmation"
|
||||
class="io.cloudbeaver.service.ai.model.events.WSAiFunctionCallConfirmationClientEvent"
|
||||
handler="io.cloudbeaver.service.ai.model.events.WSAiFunctionCallConfirmationHandler"/>
|
||||
</extension>
|
||||
</plugin>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>io.cloudbeaver</groupId>
|
||||
<artifactId>bundles</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.service.ai</artifactId>
|
||||
<version>1.0.1-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
</project>
|
||||
@@ -0,0 +1,62 @@
|
||||
####################################################
|
||||
# AI events
|
||||
####################################################
|
||||
|
||||
extend enum CBServerEventId {
|
||||
cb_ai_chat_message_chunk @since(version: "25.1.1")
|
||||
cb_ai_chat_message_error @since(version: "25.1.1")
|
||||
cb_ai_chat_message @since(version: "25.3.5")
|
||||
}
|
||||
|
||||
extend enum CBClientEventId {
|
||||
cb_client_ai_function_call_confirmation @since(version: "26.0.3")
|
||||
}
|
||||
|
||||
extend enum CBEventTopic {
|
||||
cb_ai @since(version: "25.1.1")
|
||||
}
|
||||
|
||||
type WSAIChatMessageChunkEvent implements CBServerEvent @since(version: "25.1.1") {
|
||||
id: CBServerEventId!
|
||||
topicId: CBEventTopic
|
||||
conversationId: ID!
|
||||
messageId: ID!
|
||||
chunk: String
|
||||
completed: Boolean!
|
||||
}
|
||||
|
||||
type WSAIChatMessageErrorEvent implements CBServerEvent @since(version: "25.1.1") {
|
||||
id: CBServerEventId!
|
||||
topicId: CBEventTopic
|
||||
conversationId: ID!
|
||||
messageId: ID!
|
||||
errorMessage: String
|
||||
}
|
||||
|
||||
type WSAIChatMessageEvent implements CBServerEvent @since(version: "25.3.5") {
|
||||
id: CBServerEventId!
|
||||
topicId: CBEventTopic
|
||||
conversationId: ID!
|
||||
messageId: ID!
|
||||
role: String!
|
||||
content: String!
|
||||
displayMessage: String!
|
||||
time: String!
|
||||
"Present only with CONFIRMATION role"
|
||||
functionConfirmation: AIFunctionConfirmation
|
||||
"Present only with FUNCTION role"
|
||||
functionCall: AIFunctionCall
|
||||
"Present only with FUNCTION role"
|
||||
functionResult: AIFunctionResult
|
||||
}
|
||||
|
||||
type WSAIFunctionCallConfirmationEvent implements CBClientEvent @since(version: "26.0.3") {
|
||||
id: CBClientEventId!
|
||||
topicId: CBEventTopic
|
||||
conversationId: ID!
|
||||
messageId: ID!
|
||||
"Returns a list of confirmed functions"
|
||||
confirmedFunctionCalls: [ID!]
|
||||
"Returns a list of declined functions"
|
||||
declinedFunctionCalls: [ID!]
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
####################################################
|
||||
# AI admin queries
|
||||
####################################################
|
||||
|
||||
enum AIMessageType {
|
||||
SYSTEM,
|
||||
USER,
|
||||
ASSISTANT,
|
||||
ERROR,
|
||||
WARNING,
|
||||
CONFIRMATION,
|
||||
ATTACHMENT,
|
||||
FUNCTION
|
||||
}
|
||||
|
||||
"""
|
||||
Scope of the AI chat conversation.
|
||||
Can be a database connection, a current database, a current schema or a custom scope.
|
||||
"""
|
||||
enum AIDatabaseScope @since(version: "25.1.1") {
|
||||
CURRENT_SCHEMA
|
||||
CURRENT_DATABASE
|
||||
CURRENT_DATASOURCE
|
||||
CUSTOM
|
||||
}
|
||||
|
||||
type AISettingsInfo @since(version: "23.2.2") {
|
||||
activeEngine: ID!
|
||||
defaultConfiguration: ID
|
||||
language: String
|
||||
}
|
||||
|
||||
type AIEngineInfo @since(version: "23.2.2") {
|
||||
"Unique identifier of the AI engine."
|
||||
id: ID!
|
||||
"Name of the AI engine."
|
||||
name: String!
|
||||
}
|
||||
|
||||
type AIChatConversationInfo @since(version: "25.1.1") {
|
||||
dataSourceId: DataSourceId
|
||||
id: ID!
|
||||
promptGeneratorId: ID
|
||||
caption: String!
|
||||
"Last message creation time."
|
||||
time: DateTime!
|
||||
messages: [AIMessage!]!
|
||||
settings: AIChatConversationSettings
|
||||
waitingForResponse: Boolean! @since(version: "25.1.5")
|
||||
metrics: AIChatConversationMetrics @since(version: "25.3.5")
|
||||
"ID of the AI engine configuration profile used for this conversation."
|
||||
profile: ID @since(version: "26.1.3")
|
||||
}
|
||||
|
||||
type AIChatConversationMetrics @since(version: "25.3.5") {
|
||||
totalInputTokens: Int!
|
||||
totalOutputTokens: Int!
|
||||
}
|
||||
|
||||
type AIChatConversationSettings {
|
||||
metaTransferConfirmed: Boolean!
|
||||
scope: AIDatabaseScope
|
||||
"If the scope is set to CUSTOM, this field is required."
|
||||
customObjectIds: [ID!]
|
||||
}
|
||||
|
||||
type AIMessage {
|
||||
conversationId: ID!
|
||||
id: ID!
|
||||
role: AIMessageType!
|
||||
content: String!
|
||||
displayMessage: String!
|
||||
time: DateTime!
|
||||
"Present only with CONFIRMATION role"
|
||||
functionConfirmation: AIFunctionConfirmation
|
||||
"Present only with FUNCTION role"
|
||||
functionCall: AIFunctionCall
|
||||
"Present only with FUNCTION role"
|
||||
functionResult: AIFunctionResult
|
||||
}
|
||||
|
||||
type AISendChatMessageInfo @since(version: "25.1.1") {
|
||||
conversation: AIChatConversationInfo!
|
||||
userMessage: AIMessage!
|
||||
"Message that would be received via websockets."
|
||||
assistantMessage: AIMessage!
|
||||
}
|
||||
|
||||
type AIDataSourceSettingsInfo @since(version: "25.3.3") {
|
||||
"Indicates whether user confirmed meta transfer to AI for the current data source."
|
||||
metaTransferConfirmed: Boolean!
|
||||
"Default scope for AI operations in this data source."
|
||||
scope: AIDatabaseScope
|
||||
"If the scope is set to CUSTOM, this field is required."
|
||||
customObjectIds: [ID!]
|
||||
}
|
||||
|
||||
type DataSourceId @since(version: "25.1.1") {
|
||||
projectId: ID!
|
||||
connectionId: ID!
|
||||
}
|
||||
|
||||
type AIConfigurationProfileInfo @since(version: "26.1.3") {
|
||||
"Unique identifier of the AI engine profile."
|
||||
id: ID!
|
||||
"Name of the AI engine profile."
|
||||
name: String!
|
||||
"ID of the AI engine that this profile belongs to."
|
||||
engineId: ID!
|
||||
}
|
||||
|
||||
type AIAdminConfigurationProfileInfo @since(version: "26.1.3") {
|
||||
"Unique identifier of the AI engine profile."
|
||||
id: ID!
|
||||
"Name of the AI engine profile."
|
||||
name: String!
|
||||
"ID of the AI engine that this profile belongs to."
|
||||
engineId: ID!
|
||||
"Configuration of the AI engine profile."
|
||||
configuration: [ObjectPropertyInfo!]!
|
||||
}
|
||||
|
||||
type AIFunction @since(version: "26.0.4") {
|
||||
"Unique identifier of the function (combines toolbox ID and function ID)"
|
||||
id: ID!
|
||||
"Human-readable name of the function"
|
||||
name: String!
|
||||
"Detailed description of what the function does"
|
||||
description: String
|
||||
"URL or path to the function icon"
|
||||
icon: String
|
||||
"Indicates whether the function is a system function (can be auto-confirmed when required)"
|
||||
system: Boolean!
|
||||
}
|
||||
|
||||
type AIFunctionCall @since(version: "26.0.3") {
|
||||
id: ID!
|
||||
functionName: String!
|
||||
arguments: Object
|
||||
hint: String
|
||||
}
|
||||
|
||||
type AIFunctionConfirmation @since(version: "26.0.3") {
|
||||
functionCalls: [AIFunctionCall!]!
|
||||
}
|
||||
|
||||
enum AIFunctionType {
|
||||
ACTION
|
||||
INFORMATION
|
||||
}
|
||||
|
||||
type AIFunctionResult @since(version: "26.0.3") {
|
||||
type: AIFunctionType!
|
||||
value: Object!
|
||||
}
|
||||
|
||||
input AISettingsConfig @since(version: "23.2.2") {
|
||||
"Default configuration profile ID. May be null if AI wasn't configured"
|
||||
defaultConfiguration: ID
|
||||
"Language to be used as a default for AI responses"
|
||||
language: String
|
||||
}
|
||||
|
||||
input AIEngineConfig @since(version: "23.2.2") {
|
||||
properties: Object
|
||||
}
|
||||
|
||||
input AIDatabaseScopeInput {
|
||||
id: AIDatabaseScope!
|
||||
"If custom scope is selected, this field is required."
|
||||
customScopes: [ID!]
|
||||
}
|
||||
|
||||
input DataSourceIdInput @since(version: "25.1.1") {
|
||||
projectId: ID!
|
||||
connectionId: ID!
|
||||
}
|
||||
|
||||
input AIChatConversationInput {
|
||||
dataSourceId: DataSourceIdInput
|
||||
caption: String
|
||||
settings: AIChatConversationSettingsInput
|
||||
"ID of the prompt generator to use for this conversation (sql - by default)."
|
||||
promptGeneratorId: ID
|
||||
}
|
||||
|
||||
input AIChatConversationSettingsInput {
|
||||
metaTransferConfirmed: Boolean
|
||||
scope: AIDatabaseScope
|
||||
profile: ID
|
||||
customObjectIds: [ID!]
|
||||
}
|
||||
|
||||
input AIDataSourceSettingsInput @since(version: "25.3.3") {
|
||||
"Flag indicating whether MCP integration is enabled for the current data source."
|
||||
mcpEnabled: Boolean
|
||||
"Flag indicating whether user confirmed meta transfer to AI."
|
||||
metaTransferConfirmed: Boolean
|
||||
"Default scope for AI operations in this data source."
|
||||
scope: AIDatabaseScope
|
||||
"If custom scope is selected, this field is required."
|
||||
customObjectIds: [ID!]
|
||||
}
|
||||
|
||||
input AIConfigurationProfileInput @since(version: "26.1.3") {
|
||||
"Unique identifier of the AI engine profile."
|
||||
profileId: ID!
|
||||
"Name of the AI engine profile."
|
||||
profileName: String
|
||||
"ID of the AI engine that this profile belongs to."
|
||||
engineId: ID
|
||||
"Configuration of the AI engine profile."
|
||||
configuration: AIEngineConfig
|
||||
}
|
||||
|
||||
extend type Query @since(version: "23.2.2") {
|
||||
"Returns the list of available AI engines."
|
||||
aiListEngines: [AIEngineInfo!]
|
||||
"Returns the global AI settings."
|
||||
aiSettings: AISettingsInfo!
|
||||
"Returns the properties of the specified AI engine for displaying it in the UI. In case settings are passed, fills it with defaults and returns back."
|
||||
aiListEngineProperties(engineId: ID!, profileId: ID, settings : AIEngineConfig): [ObjectPropertyInfo!]!
|
||||
|
||||
"Return the list of available AI functions."
|
||||
aiListFunctions: [AIFunction!] @since(version: "26.0.4")
|
||||
|
||||
"Returns the list of available AI engine configuration profiles."
|
||||
aiListProfiles: [AIConfigurationProfileInfo!]! @since(version: "26.1.3")
|
||||
|
||||
"Returns AI chat conversation info for the specified conversation ID."
|
||||
aiChatConversationInfo(conversationId: ID!, loadMetrics: Boolean): AIChatConversationInfo! @since(version: "25.1.1")
|
||||
"Returns list of AI chat conversations for the specified connection."
|
||||
aiListChatConversations(dataSourceId: DataSourceIdInput): [AIChatConversationInfo!]! @since(version: "25.1.1")
|
||||
|
||||
"Returns AI settings for the specified connection."
|
||||
aiDataSourceSettings(dataSourceId: DataSourceIdInput!): AIDataSourceSettingsInfo! @since(version: "25.3.3")
|
||||
}
|
||||
|
||||
extend type Mutation @since(version: "23.2.2") {
|
||||
"Saves the global AI settings (active AI engine)."
|
||||
aiSaveSettings(settings: AISettingsConfig!): AISettingsInfo!
|
||||
"Saves the configuration of the specified AI engine."
|
||||
aiSaveEngineConfiguration(profileId: ID!, settings : AIEngineConfig!): Boolean! @deprecated(reason: "use profiles api")
|
||||
"Creates an anync task to perform AI query completion."
|
||||
asyncAiPerformQueryCompletion(projectId: ID, connectionId: ID!, contextId: ID!, request: String!): AsyncTaskInfo!
|
||||
"Retrieves the result of an async AI query completion task."
|
||||
asyncAiPerformQueryCompletionResult(taskId: ID!): String
|
||||
|
||||
"Creates a new AI engine configuration profile."
|
||||
aiCreateProfile(config: AIConfigurationProfileInput!): AIAdminConfigurationProfileInfo! @since(version: "26.1.3")
|
||||
"Updates the configuration of the specified AI engine configuration profile."
|
||||
aiUpdateProfile(config: AIConfigurationProfileInput!): AIAdminConfigurationProfileInfo! @since(version: "26.1.3")
|
||||
"Deletes the specified AI engine configuration profile."
|
||||
aiDeleteProfile(profileId: ID!): Boolean! @since(version: "26.1.3")
|
||||
|
||||
"""
|
||||
Creates a new AI chat conversation.
|
||||
Conversation is created in the context of a project and connection.
|
||||
If the connection and project are not specified, the conversation will be created without a context.
|
||||
"""
|
||||
aiCreateChatConversation(config: AIChatConversationInput!): AIChatConversationInfo! @since(version: "25.1.1")
|
||||
|
||||
"Updates the caption of the AI chat conversation"
|
||||
aiUpdateChatConversation(conversationId: ID!, config: AIChatConversationInput!): AIChatConversationInfo! @since(version: "25.1.1")
|
||||
|
||||
"Deletes the specified AI chat conversation"
|
||||
aiDeleteChatConversation(conversationId: ID!): Boolean! @since(version: "25.1.1")
|
||||
|
||||
"Sends a chat message to the AI chat conversation and returns a message ID from response. The response will be received partially via websockets."
|
||||
aiSendChatMessage(conversationId: ID!, prompt: String!): AISendChatMessageInfo! @since(version: "25.1.1")
|
||||
|
||||
"Clears the chat messages in the AI chat conversation. The messages will be removed from the conversation."
|
||||
aiClearLastChatMessages(conversationId: ID!, messageId: ID!): Boolean! @since(version: "25.1.1")
|
||||
|
||||
"Saves AI settings (e.g. supporting confirming metadata transfer) for the specified connection."
|
||||
aiSaveDataSourceSettings(dataSourceId: DataSourceIdInput!, settings: AIDataSourceSettingsInput!): AIDataSourceSettingsInfo! @since(version: "25.3.3")
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2024 DBeaver Corp
|
||||
*
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* NOTICE: All information contained herein is, and remains
|
||||
* the property of DBeaver Corp and its suppliers, if any.
|
||||
* The intellectual and technical concepts contained
|
||||
* herein are proprietary to DBeaver Corp and its suppliers
|
||||
* and may be covered by U.S. and Foreign Patents,
|
||||
* patents in process, and are protected by trade secret or copyright law.
|
||||
* Dissemination of this information or reproduction of this material
|
||||
* is strictly forbidden unless prior written permission is obtained
|
||||
* from DBeaver Corp.
|
||||
*/
|
||||
package io.cloudbeaver.service.ai;
|
||||
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.service.navigator.DBWFeatureProvider;
|
||||
import io.cloudbeaver.utils.ServletAppUtils;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.ai.utils.AIUtils;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNDatabaseNode;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class WebAIFeatureProvider implements DBWFeatureProvider {
|
||||
public static final String AI_FEATURE_ID = "ai";
|
||||
private static final String FEATURE_CAN_DESCRIBE_OBJECT = "canDescribeObject";
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<String> getNodeFeatures(@NotNull WebSession webSession, @NotNull DBNNode node) {
|
||||
List<String> features = new ArrayList<>();
|
||||
if (!ServletAppUtils.getServletApplication().getAppConfiguration().isFeatureEnabled(AI_FEATURE_ID)) {
|
||||
return features;
|
||||
}
|
||||
if (node instanceof DBNDatabaseNode element && AIUtils.isEligible(element.getObject())) {
|
||||
features.add(FEATURE_CAN_DESCRIBE_OBJECT);
|
||||
}
|
||||
return features;
|
||||
}
|
||||
}
|
||||
+299
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.service.ai;
|
||||
|
||||
import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.service.ai.model.WebAIChatConversation;
|
||||
import io.cloudbeaver.service.ai.model.WebAIMessage;
|
||||
import io.cloudbeaver.service.ai.model.WebAISendChatMessageInfo;
|
||||
import io.cloudbeaver.service.ai.model.WebAiChatResponseConsumer;
|
||||
import io.cloudbeaver.service.ai.model.events.WSAiChatMessageEvent;
|
||||
import io.cloudbeaver.utils.ServletAppUtils;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
|
||||
import org.jkiss.dbeaver.model.DBUtils;
|
||||
import org.jkiss.dbeaver.model.ai.*;
|
||||
import org.jkiss.dbeaver.model.ai.qm.AIChatStorage;
|
||||
import org.jkiss.dbeaver.model.ai.quota.UserTokenQuotaService;
|
||||
import org.jkiss.dbeaver.model.ai.registry.AIAssistantRegistry;
|
||||
import org.jkiss.dbeaver.model.ai.utils.AIUtils;
|
||||
import org.jkiss.dbeaver.model.app.DBPProject;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNDatabaseNode;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNNode;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNUtils;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.struct.DBSObject;
|
||||
import org.jkiss.dbeaver.utils.RuntimeUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class WebAIUtils {
|
||||
private static final Log log = Log.getLog(WebAIUtils.class);
|
||||
private static final String AI_WAITING_ATTR = "ai.waiting.";
|
||||
public static final String AI_CHAT_ATTR = "ai_chat";
|
||||
public static final String AI_CHAT_USER_ATTR = "ai_chat_user";
|
||||
|
||||
@NotNull
|
||||
public static List<String> convertObjectIdsToNodePaths(
|
||||
@NotNull DBRProgressMonitor monitor,
|
||||
@NotNull DBPProject project,
|
||||
@NotNull String[] objectIds
|
||||
) {
|
||||
List<String> customObjectIdList = new ArrayList<>();
|
||||
for (String id : objectIds) {
|
||||
if (id != null && !id.isEmpty()) {
|
||||
try {
|
||||
DBSObject dbpObject = DBUtils.findObjectById(monitor, project, id);
|
||||
if (dbpObject != null) {
|
||||
DBNDatabaseNode dbNode = DBNUtils.getNodeByObject(monitor, dbpObject, false);
|
||||
if (dbNode != null) {
|
||||
customObjectIdList.add(dbNode.getNodeUri());
|
||||
}
|
||||
}
|
||||
} catch (DBException e) {
|
||||
log.error("Error finding object by ID: " + id, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return customObjectIdList;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String[] convertNodePathsToObjectIds(
|
||||
@NotNull WebSession session,
|
||||
@NotNull DBPProject project,
|
||||
@NotNull String[] nodePaths
|
||||
) {
|
||||
List<DBNNode> nodes = new ArrayList<>();
|
||||
for (String nodePath : nodePaths) {
|
||||
if (nodePath != null && !nodePath.isEmpty()) {
|
||||
try {
|
||||
DBNNode node = session.getNavigatorModelOrThrow().getNodeByPath(session.getProgressMonitor(), project, nodePath);
|
||||
if (node != null) {
|
||||
nodes.add(node);
|
||||
}
|
||||
} catch (DBException e) {
|
||||
log.error("Error finding object by ID: " + nodePath, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return nodes.stream()
|
||||
.map(DBNDatabaseNode.class::cast)
|
||||
.map(DBNDatabaseNode::getValueObject)
|
||||
.map(DBSObject.class::cast)
|
||||
.map(DBUtils::getObjectFullId)
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static AIChatSession findAiChatSession(@NotNull WebSession webSession) {
|
||||
return webSession.getAttribute(AI_CHAT_ATTR);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static AIContextSettingsChatConversation getConversationSettings(
|
||||
@NotNull AIChatSession chatSession,
|
||||
@NotNull AIChatConversation conversation
|
||||
) {
|
||||
DBPDataSourceContainer container = conversation.getDataSource();
|
||||
if (container == null) {
|
||||
return null;
|
||||
}
|
||||
AIContextSettingsChatConversation chatContextSettings = conversation.getCustomSettings();
|
||||
if (chatContextSettings == null) {
|
||||
chatContextSettings = new AIContextSettingsChatConversation(chatSession, conversation);
|
||||
conversation.setCustomSettings(chatContextSettings);
|
||||
}
|
||||
chatContextSettings.loadDataSourceDefaults();
|
||||
chatSession.updateScopeSettingsIfNeeded(chatContextSettings, container);
|
||||
return chatContextSettings;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static CompletableFuture<AIChatConversation> scheduleConversationSubmission(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull AIChatSession aiChatSession,
|
||||
@NotNull AIChatConversation conversation,
|
||||
@Nullable AIConfirmation confirmation,
|
||||
@NotNull String jobName
|
||||
) {
|
||||
webSession.setAttribute(getWaitingAttr(conversation), true);
|
||||
CompletableFuture<AIChatConversation> result = new CompletableFuture<>();
|
||||
RuntimeUtils.scheduleJob(
|
||||
jobName, monitor -> {
|
||||
try {
|
||||
AIChatResponseConsumer subscriber = new WebAiChatResponseConsumer(conversation, webSession, aiChatSession);
|
||||
aiChatSession.processAICompletion(
|
||||
monitor,
|
||||
conversation,
|
||||
subscriber,
|
||||
WebAIUtils.getConversationSettings(aiChatSession, conversation),
|
||||
confirmation
|
||||
).whenComplete((submittedConversation, error) -> {
|
||||
if (error != null) {
|
||||
result.completeExceptionally(error);
|
||||
} else {
|
||||
result.complete(submittedConversation);
|
||||
}
|
||||
});
|
||||
} catch (DBException e) {
|
||||
log.error("Error processing AI completion", e);
|
||||
var errorMessage = conversation.addMessage(AIMessage.errorMessage(e));
|
||||
webSession.addSessionEvent(new WSAiChatMessageEvent(new WebAIMessage(errorMessage, conversation)));
|
||||
aiChatSession.notifyMessageAdd(conversation, errorMessage);
|
||||
} finally {
|
||||
webSession.removeAttribute(getWaitingAttr(conversation));
|
||||
}
|
||||
}
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getWaitingAttr(@NotNull AIChatConversation conversation) {
|
||||
return AI_WAITING_ATTR + conversation.getId();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static AIChatSession getAiChatSession(@NotNull WebSession webSession) throws DBWebException {
|
||||
validateAiPluginEnabled();
|
||||
String aiChatSessionUser = webSession.getAttribute(AI_CHAT_USER_ATTR);
|
||||
boolean sameUser = webSession.isAuthorizedInSecurityManager() &&
|
||||
CommonUtils.equalObjects(webSession.getUserId(), aiChatSessionUser);
|
||||
if (sameUser || webSession.getUserId() == null) {
|
||||
AIChatSession aiChatSession = webSession.getAttribute(AI_CHAT_ATTR);
|
||||
if (aiChatSession != null) {
|
||||
return aiChatSession;
|
||||
}
|
||||
}
|
||||
return createChatSession(webSession);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static AIChatSession createChatSession(@NotNull WebSession webSession) {
|
||||
AIAssistant assistant = AIAssistantRegistry.getInstance().getAssistant(webSession.getWorkspace());
|
||||
AIChatStorage qmChatStorage = assistant.createChatStorage();
|
||||
|
||||
AIChatSession aiChatSession = new AIChatSession(
|
||||
webSession.getWorkspace(),
|
||||
dataSourceContainer -> DBUtils.getDefaultContext(dataSourceContainer, false),
|
||||
qmChatStorage,
|
||||
assistant.getChatSessionProvider(),
|
||||
new UserTokenQuotaService(Clock.systemUTC(), qmChatStorage)
|
||||
);
|
||||
aiChatSession.addListener(new AIChatListener() {
|
||||
@Override
|
||||
public void messageAdded(@NotNull AIChatConversation conversation, @NotNull AIChatMessage message) {
|
||||
if (message.message().getConfirmation() instanceof AIFunctionCallConfirmation fcc) {
|
||||
for (AIFunctionCall fc : fcc.getFunctionCalls()) {
|
||||
// functions are not resolved at the current step, we need to resolve them first
|
||||
// otherwise no information about a function will be sent
|
||||
AIFunctionDescriptor function = fc.getOrResolveFunction(aiChatSession.getAssistant().getToolboxManager());
|
||||
if (function == null) {
|
||||
log.warn("Function is not found for function call " + fc.getFunctionName());
|
||||
}
|
||||
}
|
||||
WebAIMessage confirmationMessage = new WebAIMessage(message, conversation);
|
||||
WSAiChatMessageEvent event = new WSAiChatMessageEvent(confirmationMessage);
|
||||
webSession.addSessionEvent(event);
|
||||
}
|
||||
}
|
||||
});
|
||||
webSession.setAttribute(AI_CHAT_ATTR, aiChatSession);
|
||||
if (webSession.getUser() != null) {
|
||||
String userId = webSession.getUserId();
|
||||
webSession.setAttribute(AI_CHAT_USER_ATTR, userId);
|
||||
}
|
||||
return aiChatSession;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static AIChatConversation getAiChatConversation(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String conversationId
|
||||
) throws DBWebException {
|
||||
try {
|
||||
AIChatSession aiChatSession = getAiChatSession(webSession);
|
||||
AIChatConversation conversation = aiChatSession.getConversation(conversationId);
|
||||
if (conversation == null) {
|
||||
throw new DBWebException("Invalid chat conversation ID " + conversationId);
|
||||
}
|
||||
return conversation;
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static WebAISendChatMessageInfo submitPrompt(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull AIChatSession aiChatSession,
|
||||
@NotNull AIChatConversation conversation,
|
||||
@NotNull AIMessage message
|
||||
) throws DBException {
|
||||
if (!isMetaTransferConfirmed(conversation)) {
|
||||
throw new DBWebException("AI services restricted for '%s'. Please contact your administrator if you need it.".formatted(
|
||||
conversation.getDataSource()));
|
||||
}
|
||||
String caption = conversation.getCaption();
|
||||
AIChatMessage promptMessage = conversation.addMessage(message);
|
||||
webSession.addSessionEvent(new WSAiChatMessageEvent(new WebAIMessage(promptMessage, conversation)));
|
||||
aiChatSession.notifyMessageAdd(conversation, promptMessage);
|
||||
if (!CommonUtils.equalObjects(caption, conversation.getCaption())) {
|
||||
aiChatSession.notifyConversationRenamed(conversation, conversation.getCaption());
|
||||
}
|
||||
if (!AIUtils.hasValidConfiguration()) {
|
||||
throw new DBWebException("Invalid AI configuration");
|
||||
}
|
||||
if (webSession.getAttribute(WebAIUtils.getWaitingAttr(conversation)) != null) {
|
||||
throw new DBWebException("Conversation is already waiting for response");
|
||||
}
|
||||
AIChatMessage result = new AIChatMessage(conversation.getNextMessageId(), AIMessage.assistantMessage("", null));
|
||||
WebAIUtils.scheduleConversationSubmission(webSession, aiChatSession, conversation, null, "AI completion");
|
||||
return new WebAISendChatMessageInfo(
|
||||
new WebAIChatConversation(webSession, conversation),
|
||||
new WebAIMessage(promptMessage, conversation),
|
||||
new WebAIMessage(result, conversation)
|
||||
);
|
||||
}
|
||||
|
||||
private static boolean isMetaTransferConfirmed(@NotNull AIChatConversation conversation) {
|
||||
DBPDataSourceContainer dataSourceContainer = conversation.getDataSource();
|
||||
if (dataSourceContainer == null) {
|
||||
return true;
|
||||
}
|
||||
AIContextSettingsDataSource dsSettings = new AIContextSettingsDataSource(dataSourceContainer);
|
||||
return dsSettings.isMetaTransferConfirmed();
|
||||
}
|
||||
|
||||
|
||||
public static void validateAiPluginEnabled() throws DBWebException {
|
||||
var isAiEnabled = ServletAppUtils.getServletApplication().getAppConfiguration()
|
||||
.isFeatureEnabled(WebAIFeatureProvider.AI_FEATURE_ID);
|
||||
if (!isAiEnabled) {
|
||||
throw new DBWebException("AI feature is disabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.service.ai;
|
||||
|
||||
import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.service.ai.gql.WebAISettingsConfig;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.ai.AIConstants;
|
||||
import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
|
||||
public class WebServiceAIManager {
|
||||
|
||||
public WebAISettingsConfig getAiSettings() throws DBWebException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getAiLanguage() {
|
||||
return DBWorkbench.getPlatform().getPreferenceStore()
|
||||
.getString(AIConstants.AI_RESPONSE_LANGUAGE);
|
||||
}
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.service.ai.gql;
|
||||
|
||||
import io.cloudbeaver.*;
|
||||
import io.cloudbeaver.model.WebAsyncTaskInfo;
|
||||
import io.cloudbeaver.model.WebPropertyInfo;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.service.DBWService;
|
||||
import io.cloudbeaver.service.ai.model.*;
|
||||
import io.cloudbeaver.service.ai.model.inputs.DataSourceId;
|
||||
import io.cloudbeaver.service.ai.model.inputs.WebAIChatConversationInput;
|
||||
import io.cloudbeaver.service.ai.model.inputs.WebAIConfigurationProfileInput;
|
||||
import io.cloudbeaver.service.ai.model.inputs.WebAiChatCompletionSettingsInput;
|
||||
import io.cloudbeaver.service.sql.WebSQLContextInfo;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.rm.RMConstants;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Service for executing GraphQL functions for AI module.
|
||||
*/
|
||||
public interface DBWServiceAI extends DBWService {
|
||||
|
||||
@NotNull
|
||||
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
|
||||
WebAISettingsConfig getAiSettings() throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
|
||||
List<WebAIEngine> getEngineConfigurations() throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
|
||||
List<WebPropertyInfo> getEngineConfigurationParameters(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String engineId,
|
||||
@Nullable String profileId,
|
||||
@Nullable Map<String, Object> engineSettings
|
||||
) throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
@WebAction
|
||||
List<WebAIFunctionInfo> getFunctions(@NotNull WebSession webSession) throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
@WebAction
|
||||
List<WebAIConfigurationProfile> getProfiles(@NotNull WebSession webSession) throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
|
||||
WebAISettingsConfig saveAiSettings(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull WebAISettingsConfig config
|
||||
) throws DBWebException;
|
||||
|
||||
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
|
||||
boolean saveEngineConfiguration(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String profileId,
|
||||
@WebParameterSecure @NotNull Map<String, Object> engineSettings
|
||||
) throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
@WebAction
|
||||
WebAsyncTaskInfo performQueryCompletion(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull WebSQLContextInfo contextInfo,
|
||||
@NotNull String request
|
||||
) throws DBWebException;
|
||||
|
||||
@Nullable
|
||||
@WebAction
|
||||
String performQueryCompletionResult(@NotNull WebSession webSession, @NotNull String taskId) throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
@WebAction
|
||||
WebAIChatConversation createChat(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull WebAIChatConversationInput conversationInput
|
||||
) throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
@WebAction
|
||||
WebAIChatConversation updateChatConversation(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String conversationId,
|
||||
@NotNull WebAIChatConversationInput conversationInput
|
||||
) throws DBWebException;
|
||||
|
||||
@WebAction
|
||||
boolean deleteChatConversation(@NotNull WebSession webSession, @NotNull String conversationId) throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
@WebAction
|
||||
List<WebAIChatConversation> getChatConversations(
|
||||
@NotNull WebSession webSession,
|
||||
@Nullable DataSourceId dataSourceId
|
||||
) throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
@WebAction
|
||||
WebAIChatConversation getChatConversationInfo(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String conversationId,
|
||||
@Nullable Boolean loadMetrics
|
||||
) throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
@WebAction
|
||||
WebAISendChatMessageInfo asyncSendChatMessage(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String conversationId,
|
||||
@NotNull String prompt
|
||||
) throws DBWebException;
|
||||
|
||||
@WebAction
|
||||
boolean setLastChatMessage(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String conversationId,
|
||||
@NotNull String messageId
|
||||
) throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
@WebAction
|
||||
WebAIDataSourceSettings getDataSourceAiSettings(
|
||||
@NotNull HttpServletRequest request,
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull DataSourceId dataSourceId
|
||||
) throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
@WebProjectAction(requireProjectPermissions = RMConstants.PERMISSION_PROJECT_DATASOURCES_EDIT)
|
||||
WebAIDataSourceSettings saveDataSourceAiSettings(
|
||||
@NotNull HttpServletRequest request,
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull @WebObjectId String projectId,
|
||||
@NotNull DataSourceId dataSourceId,
|
||||
@NotNull WebAiChatCompletionSettingsInput settings
|
||||
) throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
|
||||
WebAIConfigurationProfile createProfile(
|
||||
@NotNull WebSession webSession,
|
||||
@WebParameterSecure @NotNull WebAIConfigurationProfileInput config
|
||||
) throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
|
||||
WebAIConfigurationProfile updateProfile(
|
||||
@NotNull WebSession webSession,
|
||||
@WebParameterSecure @NotNull WebAIConfigurationProfileInput config
|
||||
) throws DBWebException;
|
||||
|
||||
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
|
||||
boolean deleteProfile(@NotNull WebSession webSession, @NotNull String profileId) throws DBWebException;
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2024 DBeaver Corp
|
||||
*
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* NOTICE: All information contained herein is, and remains
|
||||
* the property of DBeaver Corp and its suppliers, if any.
|
||||
* The intellectual and technical concepts contained
|
||||
* herein are proprietary to DBeaver Corp and its suppliers
|
||||
* and may be covered by U.S. and Foreign Patents,
|
||||
* patents in process, and are protected by trade secret or copyright law.
|
||||
* Dissemination of this information or reproduction of this material
|
||||
* is strictly forbidden unless prior written permission is obtained
|
||||
* from DBeaver Corp.
|
||||
*/
|
||||
package io.cloudbeaver.service.ai.gql;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.ai.registry.AIEngineDescriptor;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
|
||||
public class WebAIEngine {
|
||||
|
||||
@NotNull
|
||||
private final AIEngineDescriptor descriptor;
|
||||
|
||||
public WebAIEngine(@NotNull AIEngineDescriptor descriptor) {
|
||||
this.descriptor = descriptor;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public String getId() {
|
||||
return descriptor.getId();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public String getName() {
|
||||
return descriptor.getLabel();
|
||||
}
|
||||
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.service.ai.gql;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
|
||||
public class WebAISettingsConfig {
|
||||
|
||||
private final String activeEngine;
|
||||
private final String defaultConfiguration;
|
||||
private final String language;
|
||||
|
||||
public WebAISettingsConfig(
|
||||
@NotNull String engineId,
|
||||
@Nullable String defaultConfiguration,
|
||||
@Nullable String language
|
||||
) {
|
||||
this.defaultConfiguration = defaultConfiguration;
|
||||
this.activeEngine = engineId;
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getActiveEngine() {
|
||||
return activeEngine;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getDefaultConfiguration() {
|
||||
return defaultConfiguration;
|
||||
}
|
||||
|
||||
public String getLanguage() {
|
||||
return language;
|
||||
}
|
||||
}
|
||||
+679
@@ -0,0 +1,679 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.service.ai.gql;
|
||||
|
||||
import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.WebServiceUtils;
|
||||
import io.cloudbeaver.model.WebAsyncTaskInfo;
|
||||
import io.cloudbeaver.model.WebPropertyInfo;
|
||||
import io.cloudbeaver.model.session.WebAsyncTaskProcessor;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.server.CBApplication;
|
||||
import io.cloudbeaver.service.ai.WebAIUtils;
|
||||
import io.cloudbeaver.service.ai.model.*;
|
||||
import io.cloudbeaver.service.ai.model.inputs.DataSourceId;
|
||||
import io.cloudbeaver.service.ai.model.inputs.WebAIChatConversationInput;
|
||||
import io.cloudbeaver.service.ai.model.inputs.WebAIConfigurationProfileInput;
|
||||
import io.cloudbeaver.service.ai.model.inputs.WebAiChatCompletionSettingsInput;
|
||||
import io.cloudbeaver.service.sql.WebSQLContextInfo;
|
||||
import io.cloudbeaver.service.sql.WebSQLProcessor;
|
||||
import io.cloudbeaver.utils.ServletAppUtils;
|
||||
import io.cloudbeaver.utils.WebDataSourceUtils;
|
||||
import io.cloudbeaver.utils.WebEventUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.NotNullWhen;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
|
||||
import org.jkiss.dbeaver.model.ai.*;
|
||||
import org.jkiss.dbeaver.model.ai.engine.AIDatabaseContext;
|
||||
import org.jkiss.dbeaver.model.ai.engine.AIEngine;
|
||||
import org.jkiss.dbeaver.model.ai.engine.AIEngineProperties;
|
||||
import org.jkiss.dbeaver.model.ai.engine.AIModel;
|
||||
import org.jkiss.dbeaver.model.ai.prompt.AIPromptGenerateSql;
|
||||
import org.jkiss.dbeaver.model.ai.registry.*;
|
||||
import org.jkiss.dbeaver.model.app.DBPProject;
|
||||
import org.jkiss.dbeaver.model.data.json.JSONUtils;
|
||||
import org.jkiss.dbeaver.model.logical.DBSLogicalDataSource;
|
||||
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSWorkspaceConfigurationChangedEvent;
|
||||
import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
import org.jkiss.dbeaver.runtime.properties.PropertySourceEditable;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class WebServiceAI implements DBWServiceAI {
|
||||
|
||||
private static final Log log = Log.getLog(WebServiceAI.class);
|
||||
public static final String AI_WAITING_ATTR = "ai.waiting.";
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public WebAISettingsConfig getAiSettings() throws DBWebException {
|
||||
WebAIUtils.validateAiPluginEnabled();
|
||||
AISettings settings = AISettingsManager.getInstance().getSettings();
|
||||
try {
|
||||
AIConfigurationProfile configuration = settings.getDefaultConfiguration();
|
||||
return new WebAISettingsConfig(
|
||||
configuration.getEngineId(),
|
||||
configuration.getProfileId(),
|
||||
getAiLanguage()
|
||||
);
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<WebAIEngine> getEngineConfigurations() throws DBWebException {
|
||||
WebAIUtils.validateAiPluginEnabled();
|
||||
List<WebAIEngine> result = new ArrayList<>();
|
||||
AIEngineRegistry.getInstance().getCompletionEngines().forEach(
|
||||
engineDescriptor -> result.add(new WebAIEngine(engineDescriptor))
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<WebPropertyInfo> getEngineConfigurationParameters(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String engineId,
|
||||
@Nullable String profileId,
|
||||
@Nullable Map<String, Object> settingsInput
|
||||
) throws DBWebException {
|
||||
WebAIUtils.validateAiPluginEnabled();
|
||||
try {
|
||||
AIConfigurationProfile profile = getDefaultConfiguration(engineId, profileId);
|
||||
|
||||
AIEngineProperties engineConfiguration;
|
||||
if (settingsInput != null) {
|
||||
engineConfiguration = toEngineConfiguration(
|
||||
webSession.getProgressMonitor(),
|
||||
profile,
|
||||
fillModelDefaults(webSession.getProgressMonitor(), profile, settingsInput)
|
||||
);
|
||||
} else {
|
||||
engineConfiguration = profile.getConfiguration();
|
||||
}
|
||||
|
||||
return List.of(
|
||||
WebServiceUtils.getObjectFilteredProperties(webSession, engineConfiguration, null)
|
||||
);
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error getting engine configuration parameters", e);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private AIConfigurationProfile getDefaultConfiguration(@NotNull String engineId, @Nullable String profileId) throws DBException {
|
||||
AISettings aiSettings = AISettingsManager.getInstance().getSettings();
|
||||
if (profileId != null) {
|
||||
return aiSettings.getConfiguration(profileId);
|
||||
}
|
||||
|
||||
AIConfigurationProfile profile = new AIConfigurationProfile();
|
||||
profile.setEngineId(engineId);
|
||||
profile.getEngineDescriptor(); // validate engine descriptor
|
||||
return profile;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<WebAIFunctionInfo> getFunctions(@NotNull WebSession webSession) throws DBWebException {
|
||||
AIAssistant assistant = AIAssistantRegistry.getInstance().getAssistant(webSession.getWorkspace());
|
||||
return assistant.getToolboxManager().getAllFunctions(AIFunctionPurpose.TOOL).stream()
|
||||
.map(WebAIFunctionInfo::new)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<WebAIConfigurationProfile> getProfiles(@NotNull WebSession webSession) {
|
||||
return Arrays.stream(AISettingsManager.getInstance().getSettings().getConfigurations())
|
||||
.map(profile -> new WebAIConfigurationProfile(webSession, profile))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public WebAISettingsConfig saveAiSettings(@NotNull WebSession webSession, @NotNull WebAISettingsConfig config) throws DBWebException {
|
||||
WebAIUtils.validateAiPluginEnabled();
|
||||
|
||||
try {
|
||||
setAiLanguage(webSession, config.getLanguage());
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error when saving AI language", e);
|
||||
}
|
||||
AISettings aiSettings = AISettingsManager.getInstance().getSettings();
|
||||
AIConfigurationProfile profile = aiSettings.getConfigurationOrNull(config.getDefaultConfiguration());
|
||||
aiSettings.setDefaultConfiguration(profile);
|
||||
AISettingsManager.getInstance().saveSettings();
|
||||
|
||||
addAISettingsChangedEvent(webSession);
|
||||
return new WebAISettingsConfig(
|
||||
profile == null ? "" : profile.getEngineId(),
|
||||
profile == null ? null : profile.getProfileId(),
|
||||
getAiLanguage()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveEngineConfiguration(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String profileId,
|
||||
@NotNull Map<String, Object> engineSettingsInput
|
||||
) throws DBWebException {
|
||||
WebAIUtils.validateAiPluginEnabled();
|
||||
try {
|
||||
AISettings settings = AISettingsManager.getInstance().getSettings();
|
||||
AIConfigurationProfile profile = settings.getConfiguration(profileId);
|
||||
profile.setConfiguration(toEngineConfiguration(webSession.getProgressMonitor(), profile, engineSettingsInput));
|
||||
AISettingsManager.getInstance().saveSettings();
|
||||
addAISettingsChangedEvent(webSession);
|
||||
return true;
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error saving AI configuration " + profileId);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public WebAsyncTaskInfo performQueryCompletion(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull WebSQLContextInfo contextInfo,
|
||||
@NotNull String request
|
||||
) throws DBWebException {
|
||||
WebAIUtils.validateAiPluginEnabled();
|
||||
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<>() {
|
||||
@Override
|
||||
public void run(DBRProgressMonitor monitor) throws InvocationTargetException {
|
||||
monitor.beginTask("Perform query completion", 1);
|
||||
monitor.subTask("Perform query completion for " + request);
|
||||
try {
|
||||
WebSQLProcessor processor = contextInfo.getProcessor();
|
||||
DBSLogicalDataSource lDataSource = new DBSLogicalDataSource(
|
||||
processor.getConnection().getDataSourceContainer(),
|
||||
"AI logical wrapper",
|
||||
null
|
||||
);
|
||||
AIDatabaseContext dbContext = new AIDatabaseContext.Builder(lDataSource)
|
||||
.setExecutionContext(processor.getExecutionContext())
|
||||
.build();
|
||||
|
||||
AIAssistant assistant = AIAssistantRegistry.getInstance().getAssistant(webSession.getWorkspace());
|
||||
|
||||
AIFunctionContext fc = new AIFunctionContext(monitor, dbContext, new AIPromptGenerateSql());
|
||||
AIMessage userMessage = AIMessage.userMessage(request);
|
||||
AIAssistantResponse result = assistant.generateText(
|
||||
monitor,
|
||||
AISettingsManager.getStaticSettings().getDefaultConfiguration(),
|
||||
fc,
|
||||
List.of(userMessage)
|
||||
);
|
||||
|
||||
if (result.isText()) {
|
||||
this.result = AITextUtils.extractGeneratedSqlQuery(
|
||||
monitor, dbContext, userMessage, result.getText());
|
||||
} else {
|
||||
this.result = "";
|
||||
}
|
||||
} catch (DBException e) {
|
||||
throw new InvocationTargetException(e);
|
||||
} finally {
|
||||
monitor.done();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return webSession.createAndRunAsyncTask("AI perform completion query", runnable);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String performQueryCompletionResult(@NotNull WebSession webSession, @NotNull String taskId) throws DBWebException {
|
||||
WebAIUtils.validateAiPluginEnabled();
|
||||
WebAsyncTaskInfo taskStatus = webSession.asyncTaskStatus(taskId, false);
|
||||
return CommonUtils.toString(taskStatus.getResult());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public WebAIChatConversation createChat(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull WebAIChatConversationInput conversationInput
|
||||
) throws DBWebException {
|
||||
AIChatSession aiChatSession = WebAIUtils.getAiChatSession(webSession);
|
||||
DBPDataSourceContainer dataSource = getDataSource(webSession, conversationInput.dataSourceId());
|
||||
try {
|
||||
String promptGeneratorId = conversationInput.promptGeneratorId();
|
||||
if (promptGeneratorId == null) {
|
||||
// by default use SQL generator
|
||||
promptGeneratorId = AIPromptGenerateSql.SQL_GENERATOR_ID;
|
||||
}
|
||||
AIPromptGeneratorDescriptor descriptor = AIPromptGeneratorRegistry.getInstance().getPromptGenerator(promptGeneratorId);
|
||||
if (descriptor == null) {
|
||||
throw new DBWebException("Invalid prompt generator ID " + promptGeneratorId);
|
||||
}
|
||||
AIChatConversation newConversation = new AIChatConversation(
|
||||
conversationInput.caption() != null ? conversationInput.caption() : "New conversation",
|
||||
descriptor.createGenerator(),
|
||||
dataSource
|
||||
);
|
||||
aiChatSession.addConversation(newConversation);
|
||||
saveCompletionSettings(webSession, aiChatSession, newConversation, conversationInput.settings());
|
||||
return new WebAIChatConversation(webSession, newConversation);
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error creating conversation", e);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public WebAIChatConversation updateChatConversation(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String conversationId,
|
||||
@NotNull WebAIChatConversationInput input
|
||||
) throws DBWebException {
|
||||
WebAIUtils.validateAiPluginEnabled();
|
||||
AIChatSession aiChatSession = WebAIUtils.getAiChatSession(webSession);
|
||||
AIChatConversation conversation = WebAIUtils.getAiChatConversation(webSession, conversationId);
|
||||
String caption = input.caption();
|
||||
if (caption != null) {
|
||||
renameConversation(aiChatSession, conversation, caption);
|
||||
}
|
||||
saveCompletionSettings(webSession, aiChatSession, conversation, input.settings());
|
||||
return new WebAIChatConversation(webSession, conversation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteChatConversation(@NotNull WebSession webSession, @NotNull String conversationId) throws DBWebException {
|
||||
try {
|
||||
AIChatSession aiChatSession = WebAIUtils.getAiChatSession(webSession);
|
||||
AIChatConversation conversation = aiChatSession.getConversation(conversationId);
|
||||
if (conversation == null) {
|
||||
throw new DBWebException("Invalid conversation ID " + conversationId);
|
||||
}
|
||||
aiChatSession.removeConversation(conversation);
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException(e.getMessage(), e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<WebAIChatConversation> getChatConversations(
|
||||
@NotNull WebSession webSession,
|
||||
@Nullable DataSourceId dataSourceId
|
||||
) throws DBWebException {
|
||||
AIChatSession session = WebAIUtils.getAiChatSession(webSession);
|
||||
DBPDataSourceContainer connectionInfo = getDataSource(webSession, dataSourceId);
|
||||
try {
|
||||
return session.getAllConversations(connectionInfo).stream()
|
||||
.map(c -> new WebAIChatConversation(webSession, c))
|
||||
.collect(Collectors.toList());
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public WebAIChatConversation getChatConversationInfo(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String conversationId,
|
||||
@Nullable Boolean loadMetrics
|
||||
) throws DBWebException {
|
||||
WebAIUtils.validateAiPluginEnabled();
|
||||
AIChatConversation conversation = WebAIUtils.getAiChatConversation(webSession, conversationId);
|
||||
WebAIChatConversationMetrics metrics = null;
|
||||
if (loadMetrics != null && loadMetrics) {
|
||||
AIExtendedUsage aiExtendedUsage = conversation.computeUsage();
|
||||
metrics = WebAIChatConversationMetrics.from(aiExtendedUsage);
|
||||
}
|
||||
return new WebAIChatConversation(webSession, conversation, metrics);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public WebAISendChatMessageInfo asyncSendChatMessage(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String conversationId,
|
||||
@NotNull String prompt
|
||||
) throws DBWebException {
|
||||
WebAIUtils.validateAiPluginEnabled();
|
||||
try {
|
||||
AIChatSession aiChatSession = WebAIUtils.getAiChatSession(webSession);
|
||||
AIChatConversation conversation = aiChatSession.getConversation(conversationId);
|
||||
if (conversation == null) {
|
||||
throw new DBWebException("Invalid chat conversation ID " + conversationId);
|
||||
}
|
||||
if (prompt.trim().isEmpty()) {
|
||||
throw new DBWebException("Invalid prompt");
|
||||
}
|
||||
AIMessage message = AIMessage.userMessage(prompt.trim());
|
||||
return WebAIUtils.submitPrompt(webSession, aiChatSession, conversation, message);
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setLastChatMessage(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String conversationId,
|
||||
@NotNull String messageId
|
||||
) throws DBWebException {
|
||||
AIChatSession chatSession = WebAIUtils.getAiChatSession(webSession);
|
||||
AIChatConversation conversation = WebAIUtils.getAiChatConversation(webSession, conversationId);
|
||||
AIChatMessage message = conversation.getMessages().stream()
|
||||
.filter(m -> messageId.equals(CommonUtils.toString(m.id())))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new DBWebException("Invalid message ID " + messageId));
|
||||
chatSession.notifyMessagesRemove(conversation, message);
|
||||
conversation.clearMessagesAfter(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public WebAIDataSourceSettings getDataSourceAiSettings(
|
||||
@NotNull HttpServletRequest request,
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull DataSourceId dataSourceId
|
||||
) throws DBWebException {
|
||||
DBPDataSourceContainer dataSourceContainer = getDataSource(webSession, dataSourceId);
|
||||
AIContextSettingsDataSource settings = new AIContextSettingsDataSource(dataSourceContainer);
|
||||
String userOrigin = ServletAppUtils.getOriginFromRequest(request);
|
||||
return new WebAIDataSourceSettings(settings, userOrigin);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public WebAIDataSourceSettings saveDataSourceAiSettings(
|
||||
@NotNull HttpServletRequest request,
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String projectId,
|
||||
@NotNull DataSourceId dataSourceId,
|
||||
@NotNull WebAiChatCompletionSettingsInput settingsInput
|
||||
) throws DBWebException {
|
||||
DBPDataSourceContainer dataSourceContainer = getDataSource(webSession, dataSourceId);
|
||||
AIContextSettingsDataSource aiSettings = new AIContextSettingsDataSource(dataSourceContainer);
|
||||
if (settingsInput.mcpEnabled() != null) {
|
||||
aiSettings.setMcpEnabled(settingsInput.mcpEnabled());
|
||||
}
|
||||
if (settingsInput.metaTransferConfirmed() != null) {
|
||||
aiSettings.setMetaTransferConfirmed(settingsInput.metaTransferConfirmed());
|
||||
}
|
||||
if (settingsInput.scope() != null) {
|
||||
aiSettings.setScope(settingsInput.scope());
|
||||
}
|
||||
if (settingsInput.customObjectIds() != null) {
|
||||
aiSettings.setCustomObjectIds(
|
||||
WebAIUtils.convertNodePathsToObjectIds(
|
||||
webSession,
|
||||
dataSourceContainer.getProject(),
|
||||
settingsInput.customObjectIds()
|
||||
)
|
||||
);
|
||||
}
|
||||
aiSettings.saveSettings();
|
||||
String userOrigin = ServletAppUtils.getOriginFromRequest(request);
|
||||
return new WebAIDataSourceSettings(aiSettings, userOrigin);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public WebAIConfigurationProfile createProfile(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull WebAIConfigurationProfileInput input
|
||||
) throws DBWebException {
|
||||
WebAIUtils.validateAiPluginEnabled();
|
||||
AISettings settings = AISettingsManager.getInstance().getSettings();
|
||||
if (CommonUtils.isEmpty(input.profileId())) {
|
||||
throw new DBWebException("Profile ID is not specified");
|
||||
}
|
||||
if (CommonUtils.isEmpty(input.engineId())) {
|
||||
throw new DBWebException("Engine ID is not specified");
|
||||
}
|
||||
if (CommonUtils.isEmpty(input.profileName())) {
|
||||
throw new DBWebException("Profile name is not specified");
|
||||
}
|
||||
AIEngineDescriptor engineDescriptor = AIEngineRegistry.getInstance().getEngineDescriptor(input.engineId());
|
||||
if (engineDescriptor == null) {
|
||||
throw new DBWebException("Invalid AI engine configuration ID " + input.engineId());
|
||||
}
|
||||
try {
|
||||
settings.createConfiguration(input.profileId(), engineDescriptor);
|
||||
AIConfigurationProfile profile = settings.getConfiguration(input.profileId());
|
||||
profile.setProfileName(input.profileName());
|
||||
if (input.configuration() != null) {
|
||||
profile.setConfiguration(toEngineConfiguration(webSession.getProgressMonitor(), profile, input.configuration()));
|
||||
}
|
||||
AISettingsManager.getInstance().saveSettings();
|
||||
addAISettingsChangedEvent(webSession);
|
||||
return new WebAIConfigurationProfile(webSession, settings.getConfiguration(input.profileId()));
|
||||
} catch (DBException e) {
|
||||
|
||||
throw new DBWebException("Error creating AI configuration " + input.profileId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public WebAIConfigurationProfile updateProfile(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull WebAIConfigurationProfileInput input
|
||||
) throws DBWebException {
|
||||
WebAIUtils.validateAiPluginEnabled();
|
||||
try {
|
||||
AISettings settings = AISettingsManager.getInstance().getSettings();
|
||||
AIConfigurationProfile profile = settings.getConfiguration(input.profileId());
|
||||
if (input.profileName() != null) {
|
||||
profile.setProfileName(input.profileName());
|
||||
}
|
||||
if (input.configuration() != null) {
|
||||
profile.setConfiguration(toEngineConfiguration(webSession.getProgressMonitor(), profile, input.configuration()));
|
||||
}
|
||||
AISettingsManager.getInstance().saveSettings();
|
||||
addAISettingsChangedEvent(webSession);
|
||||
return new WebAIConfigurationProfile(webSession, profile);
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error updating AI configuration " + input.profileId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteProfile(@NotNull WebSession webSession, @NotNull String profileId) throws DBWebException {
|
||||
WebAIUtils.validateAiPluginEnabled();
|
||||
try {
|
||||
AISettings settings = AISettingsManager.getInstance().getSettings();
|
||||
AIConfigurationProfile profile = settings.getConfiguration(profileId);
|
||||
settings.removeConfiguration(profile);
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error deleting AI configuration " + profileId, e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNullWhen("dataSourceId != null")
|
||||
private DBPDataSourceContainer getDataSource(
|
||||
@NotNull WebSession webSession,
|
||||
@Nullable DataSourceId dataSourceId
|
||||
) throws DBWebException {
|
||||
if (dataSourceId == null) {
|
||||
return null;
|
||||
}
|
||||
return WebDataSourceUtils.getWebConnectionInfo(webSession, dataSourceId.projectId(), dataSourceId.connectionId())
|
||||
.getDataSourceContainer();
|
||||
}
|
||||
|
||||
private void addAISettingsChangedEvent(@NotNull WebSession webSession) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
new WSWorkspaceConfigurationChangedEvent(
|
||||
AISettingsManager.AI_CONFIGURATION_FILE_NAME,
|
||||
WebEventUtils.getSmSessionId(webSession),
|
||||
webSession.getUserId()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Map<String, Object> fillModelDefaults(
|
||||
@NotNull DBRProgressMonitor monitor,
|
||||
@NotNull AIConfigurationProfile profile,
|
||||
@NotNull Map<String, Object> engineSettingsInput
|
||||
) throws DBException {
|
||||
// TODO: Think how default values can be set without modifying input map and not affecting the code model
|
||||
Map<String, Object> inputProperties = JSONUtils.getObject(engineSettingsInput, "properties");
|
||||
|
||||
AIEngineProperties configuration;
|
||||
try {
|
||||
configuration = profile.getConfiguration();
|
||||
|
||||
if (!configuration.isValidConfiguration()) {
|
||||
inputProperties.put(AIConstants.AI_CONTEXT_SIZE_PROPERTY, AIConstants.DEFAULT_CONTEXT_WINDOW_SIZE);
|
||||
return engineSettingsInput;
|
||||
}
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error converting engine configuration", e);
|
||||
}
|
||||
|
||||
if (!profile.getEngineDescriptor().isProvidesMetadata()) {
|
||||
return engineSettingsInput;
|
||||
}
|
||||
try (AIEngine<?> engine = profile.getEngineDescriptor().createEngineInstance(configuration)) {
|
||||
Object modelId = inputProperties.get(AIConstants.AI_MODEL_PROPERTY);
|
||||
AIModel model = engine.getModels(monitor)
|
||||
.stream()
|
||||
.filter(m -> Objects.equals(m.name(), modelId))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (model != null) {
|
||||
inputProperties.put(AIConstants.AI_CONTEXT_SIZE_PROPERTY, model.contextWindowSize());
|
||||
inputProperties.put(AIConstants.AI_TEMPERATURE_PROPERTY, model.defaultTemperature());
|
||||
} else {
|
||||
inputProperties.put(AIConstants.AI_CONTEXT_SIZE_PROPERTY, AIConstants.DEFAULT_CONTEXT_WINDOW_SIZE);
|
||||
log.info("Model " + modelId + " not found");
|
||||
}
|
||||
} catch (DBException e) {
|
||||
log.info("Error fetching models ", e);
|
||||
}
|
||||
return engineSettingsInput;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private AIEngineProperties toEngineConfiguration(
|
||||
@NotNull DBRProgressMonitor progressMonitor,
|
||||
@NotNull AIConfigurationProfile profile,
|
||||
@NotNull Map<String, Object> engineSettingsInput
|
||||
) throws DBWebException {
|
||||
Map<String, Object> engineProperties = JSONUtils.getObject(engineSettingsInput, "properties");
|
||||
// TODO: Think how default values can be set without modifying input map and not affecting the code model
|
||||
if (engineProperties.get(AIConstants.AI_TEMPERATURE_PROPERTY) instanceof String stringValue && stringValue.isBlank()) {
|
||||
engineProperties.put(AIConstants.AI_TEMPERATURE_PROPERTY, 0.0);
|
||||
}
|
||||
if (engineProperties.get(AIConstants.AI_CONTEXT_SIZE_PROPERTY) instanceof String stringValue && stringValue.isBlank()) {
|
||||
engineProperties.put(AIConstants.AI_CONTEXT_SIZE_PROPERTY, AIConstants.DEFAULT_CONTEXT_WINDOW_SIZE);
|
||||
}
|
||||
|
||||
try {
|
||||
AIEngineProperties configuration = profile.getConfiguration();
|
||||
PropertySourceEditable pse = new PropertySourceEditable(configuration, configuration);
|
||||
pse.collectProperties();
|
||||
for (DBPPropertyDescriptor pd : pse.getProperties()) {
|
||||
Object value = engineProperties.get(pd.getId());
|
||||
if (value != null) {
|
||||
pse.setPropertyValue(progressMonitor, pd.getId(), value);
|
||||
}
|
||||
}
|
||||
return configuration;
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error converting engine configuration", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void renameConversation(
|
||||
@NotNull AIChatSession chatSession,
|
||||
@NotNull AIChatConversation conversation,
|
||||
@NotNull String newName
|
||||
) {
|
||||
conversation.setCaption(newName.trim());
|
||||
chatSession.notifyConversationRenamed(conversation, newName.trim());
|
||||
}
|
||||
|
||||
private static void saveCompletionSettings(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull AIChatSession session,
|
||||
@NotNull AIChatConversation conversation,
|
||||
@Nullable WebAiChatCompletionSettingsInput settingsInput
|
||||
) throws DBWebException {
|
||||
AIContextSettingsChatConversation settings = WebAIUtils.getConversationSettings(session, conversation);
|
||||
if (settings == null || settingsInput == null) {
|
||||
return;
|
||||
}
|
||||
if (settingsInput.profile() != null) {
|
||||
AIConfigurationProfile profile = AISettingsManager.getInstance().getSettings().getConfigurationOrNull(settingsInput.profile());
|
||||
if (profile == null) {
|
||||
throw new DBWebException("Invalid AI configuration profile " + settingsInput.profile());
|
||||
}
|
||||
conversation.setProfile(profile);
|
||||
}
|
||||
if (settingsInput.metaTransferConfirmed() != null) {
|
||||
settings.setMetaTransferConfirmed(settingsInput.metaTransferConfirmed());
|
||||
}
|
||||
if (settingsInput.scope() != null) {
|
||||
settings.setScope(settingsInput.scope());
|
||||
}
|
||||
if (settingsInput.customObjectIds() != null) {
|
||||
DBPProject project = conversation.getDataSource() == null ? null : conversation.getDataSource().getProject();
|
||||
if (project != null) {
|
||||
settings.setCustomObjectIds(WebAIUtils.convertNodePathsToObjectIds(webSession, project, settingsInput.customObjectIds()));
|
||||
}
|
||||
}
|
||||
try {
|
||||
settings.saveSettings();
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error saving AI completion settings", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setAiLanguage(@NotNull WebSession webSession, @Nullable String language) throws DBException {
|
||||
if (language != null) {
|
||||
CBApplication.getInstance().saveProductConfiguration(
|
||||
webSession,
|
||||
Map.of(AIConstants.AI_RESPONSE_LANGUAGE, language)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getAiLanguage() {
|
||||
return DBWorkbench.getPlatform().getPreferenceStore()
|
||||
.getString(AIConstants.AI_RESPONSE_LANGUAGE);
|
||||
}
|
||||
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp
|
||||
*
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* NOTICE: All information contained herein is, and remains
|
||||
* the property of DBeaver Corp and its suppliers, if any.
|
||||
* The intellectual and technical concepts contained
|
||||
* herein are proprietary to DBeaver Corp and its suppliers
|
||||
* and may be covered by U.S. and Foreign Patents,
|
||||
* patents in process, and are protected by trade secret or copyright law.
|
||||
* Dissemination of this information or reproduction of this material
|
||||
* is strictly forbidden unless prior written permission is obtained
|
||||
* from DBeaver Corp.
|
||||
*/
|
||||
package io.cloudbeaver.service.ai.gql;
|
||||
|
||||
import io.cloudbeaver.model.app.ServletAppConfiguration;
|
||||
import io.cloudbeaver.model.app.ServletApplication;
|
||||
import io.cloudbeaver.model.app.ServletServerConfiguration;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.service.DBWServiceServerConfigurator;
|
||||
import io.cloudbeaver.service.ai.WebAIFeatureProvider;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.model.ai.registry.AISettingsManager;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSWorkspaceConfigurationChangedEvent;
|
||||
|
||||
public class WebServiceAIConfigurator implements DBWServiceServerConfigurator {
|
||||
@Override
|
||||
public void configureServer(
|
||||
@NotNull ServletApplication application,
|
||||
@Nullable WebSession session,
|
||||
@NotNull ServletServerConfiguration serverConfiguration,
|
||||
@NotNull ServletAppConfiguration appConfig
|
||||
) throws DBException {
|
||||
var aiDisabled = !appConfig.isFeatureEnabled(WebAIFeatureProvider.AI_FEATURE_ID);
|
||||
|
||||
if (aiDisabled && !AISettingsManager.isConfigExists()) {
|
||||
// do nothing, ai disabled and settings not exists
|
||||
return;
|
||||
}
|
||||
var settings = AISettingsManager.getInstance().getSettings();
|
||||
settings.setAiDisabled(aiDisabled);
|
||||
application.getEventController().addEvent(
|
||||
new WSWorkspaceConfigurationChangedEvent(
|
||||
AISettingsManager.AI_CONFIGURATION_FILE_NAME,
|
||||
null,
|
||||
null
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void migrateConfigurationIfNeeded(@NotNull ServletApplication application) throws DBException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reloadConfiguration(@NotNull ServletAppConfiguration appConfig) throws DBException {
|
||||
|
||||
}
|
||||
}
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.service.ai.gql;
|
||||
|
||||
import io.cloudbeaver.server.CBApplication;
|
||||
import io.cloudbeaver.server.graphql.GraphQLEndpoint;
|
||||
import io.cloudbeaver.service.DBWBindingContext;
|
||||
import io.cloudbeaver.service.DBWServiceInitializer;
|
||||
import io.cloudbeaver.service.WebServiceBindingBase;
|
||||
import io.cloudbeaver.service.ai.WebAIFeatureProvider;
|
||||
import io.cloudbeaver.service.ai.model.inputs.DataSourceId;
|
||||
import io.cloudbeaver.service.ai.model.inputs.WebAIChatConversationInput;
|
||||
import io.cloudbeaver.service.ai.model.inputs.WebAIConfigurationProfileInput;
|
||||
import io.cloudbeaver.service.ai.model.inputs.WebAiChatCompletionSettingsInput;
|
||||
import io.cloudbeaver.service.sql.WebServiceBindingSQL;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.ai.AIConstants;
|
||||
import org.jkiss.dbeaver.model.ai.registry.AISettingsManager;
|
||||
import org.jkiss.dbeaver.model.data.json.JSONUtils;
|
||||
import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore;
|
||||
import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
import org.jkiss.dbeaver.utils.PrefUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class WebServiceBindingAI extends WebServiceBindingBase<DBWServiceAI> implements DBWServiceInitializer {
|
||||
private static final Log log = Log.getLog(WebServiceBindingAI.class);
|
||||
|
||||
public WebServiceBindingAI() {
|
||||
super(DBWServiceAI.class, new WebServiceAI(), "schema/service.ai.graphqls");
|
||||
DBPPreferenceStore store = DBWorkbench.getPlatform().getPreferenceStore();
|
||||
PrefUtils.setDefaultPreferenceValue(store, AIConstants.AI_INCLUDE_SOURCE_TEXT_IN_QUERY_COMMENT, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindWiring(DBWBindingContext model) {
|
||||
model.getQueryType()
|
||||
.dataFetcher("aiSettings", env -> getService(env).getAiSettings())
|
||||
.dataFetcher("aiListEngines", env -> getService(env).getEngineConfigurations())
|
||||
.dataFetcher(
|
||||
"aiListEngineProperties",
|
||||
env -> getService(env).getEngineConfigurationParameters(
|
||||
getWebSession(env),
|
||||
getArgumentVal(env, "engineId"),
|
||||
getArgument(env, "profileId"),
|
||||
env.getArgument("settings")
|
||||
)
|
||||
).dataFetcher("aiListFunctions", env -> getService(env).getFunctions(getWebSession(env))
|
||||
).dataFetcher("aiListProfiles", env -> getService(env).getProfiles(getWebSession(env))
|
||||
).dataFetcher(
|
||||
"aiListChatConversations",
|
||||
env -> getService(env).getChatConversations(
|
||||
getWebSession(env),
|
||||
JSONUtils.deserializeObject(getArgument(env, "dataSourceId"), DataSourceId.class)
|
||||
)
|
||||
).dataFetcher(
|
||||
"aiChatConversationInfo",
|
||||
env -> getService(env).getChatConversationInfo(
|
||||
getWebSession(env),
|
||||
getArgumentVal(env, "conversationId"),
|
||||
env.getArgument("loadMetrics")
|
||||
)
|
||||
).dataFetcher(
|
||||
"aiDataSourceSettings",
|
||||
env -> getService(env).getDataSourceAiSettings(
|
||||
GraphQLEndpoint.getServletRequestOrThrow(env),
|
||||
getWebSession(env),
|
||||
JSONUtils.deserializeObject(getArgumentVal(env, "dataSourceId"), DataSourceId.class)
|
||||
)
|
||||
);
|
||||
model.getMutationType()
|
||||
.dataFetcher(
|
||||
"aiSaveSettings",
|
||||
env -> getService(env).saveAiSettings(
|
||||
getWebSession(env),
|
||||
JSONUtils.deserializeObject(getArgumentVal(env, "settings"), WebAISettingsConfig.class)
|
||||
)
|
||||
)
|
||||
.dataFetcher(
|
||||
"aiSaveDataSourceSettings",
|
||||
|
||||
env -> {
|
||||
DataSourceId dataSourceId = JSONUtils.deserializeObject(
|
||||
getArgumentVal(env, "dataSourceId"),
|
||||
DataSourceId.class
|
||||
);
|
||||
return getService(env).saveDataSourceAiSettings(
|
||||
GraphQLEndpoint.getServletRequestOrThrow(env),
|
||||
getWebSession(env),
|
||||
dataSourceId.projectId(),
|
||||
dataSourceId,
|
||||
JSONUtils.deserializeObject(getArgumentVal(env, "settings"), WebAiChatCompletionSettingsInput.class)
|
||||
);
|
||||
}
|
||||
)
|
||||
.dataFetcher(
|
||||
"aiSaveEngineConfiguration",
|
||||
env -> getService(env).saveEngineConfiguration(
|
||||
getWebSession(env),
|
||||
getArgumentVal(env, "profileId"),
|
||||
getArgumentVal(env, "settings")
|
||||
)
|
||||
).dataFetcher(
|
||||
"asyncAiPerformQueryCompletion",
|
||||
env -> getService(env).performQueryCompletion(
|
||||
getWebSession(env),
|
||||
WebServiceBindingSQL.getSQLContext(env),
|
||||
getArgumentVal(env, "request")
|
||||
)
|
||||
).dataFetcher(
|
||||
"asyncAiPerformQueryCompletionResult",
|
||||
env -> getService(env).performQueryCompletionResult(
|
||||
getWebSession(env),
|
||||
getArgumentVal(env, "taskId")
|
||||
)
|
||||
).dataFetcher(
|
||||
"aiCreateChatConversation",
|
||||
env -> getService(env).createChat(
|
||||
getWebSession(env),
|
||||
JSONUtils.deserializeObject(getArgument(env, "config"), WebAIChatConversationInput.class)
|
||||
)
|
||||
).dataFetcher(
|
||||
"aiUpdateChatConversation",
|
||||
env -> getService(env).updateChatConversation(
|
||||
getWebSession(env),
|
||||
getArgumentVal(env, "conversationId"),
|
||||
JSONUtils.deserializeObject(getArgument(env, "config"), WebAIChatConversationInput.class)
|
||||
)
|
||||
).dataFetcher(
|
||||
"aiDeleteChatConversation",
|
||||
env -> getService(env).deleteChatConversation(
|
||||
getWebSession(env),
|
||||
getArgumentVal(env, "conversationId")
|
||||
)
|
||||
).dataFetcher(
|
||||
"aiSendChatMessage",
|
||||
env -> getService(env).asyncSendChatMessage(
|
||||
getWebSession(env),
|
||||
getArgumentVal(env, "conversationId"),
|
||||
getArgumentVal(env, "prompt")
|
||||
)
|
||||
).dataFetcher(
|
||||
"aiClearLastChatMessages",
|
||||
env -> getService(env).setLastChatMessage(
|
||||
getWebSession(env),
|
||||
getArgumentVal(env, "conversationId"),
|
||||
getArgumentVal(env, "messageId")
|
||||
)
|
||||
).dataFetcher(
|
||||
"aiCreateProfile", env -> getService(env).createProfile(
|
||||
getWebSession(env),
|
||||
JSONUtils.deserializeObject(getArgumentVal(env, "config"), WebAIConfigurationProfileInput.class)
|
||||
)
|
||||
).dataFetcher(
|
||||
"aiUpdateProfile", env -> getService(env).updateProfile(
|
||||
getWebSession(env),
|
||||
JSONUtils.deserializeObject(getArgumentVal(env, "config"), WebAIConfigurationProfileInput.class)
|
||||
)
|
||||
).dataFetcher(
|
||||
"aiDeleteProfile", env -> getService(env).deleteProfile(
|
||||
getWebSession(env),
|
||||
getArgumentVal(env, "profileId")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeService(@NotNull CBApplication<?> application) {
|
||||
// adds event listener to config file changer
|
||||
AISettingsManager.getInstance().addChangedListener(
|
||||
x -> {
|
||||
// check if AI is enabled
|
||||
boolean aiEnabled = !x.getSettings().isAiDisabled();
|
||||
List<String> enabledFeatures = new ArrayList<>(Arrays.asList(application.getAppConfiguration().getEnabledFeatures()));
|
||||
boolean aiFeatureEnabled = enabledFeatures.contains(WebAIFeatureProvider.AI_FEATURE_ID);
|
||||
// remove AI feature if it is disabled (and do not remove if it is enabled)
|
||||
if ((aiEnabled && aiFeatureEnabled) || (!aiEnabled && !aiFeatureEnabled)) {
|
||||
// nothing to change
|
||||
return;
|
||||
}
|
||||
if (aiEnabled) {
|
||||
enabledFeatures.add(WebAIFeatureProvider.AI_FEATURE_ID);
|
||||
} else {
|
||||
enabledFeatures.remove(WebAIFeatureProvider.AI_FEATURE_ID);
|
||||
}
|
||||
application.getAppConfiguration().setEnabledFeatures(enabledFeatures.toArray(String[]::new));
|
||||
if (application.isConfigurationMode()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
application.flushConfiguration();
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to save server configuration", e);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.service.ai.model;
|
||||
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.service.ai.gql.WebServiceAI;
|
||||
import io.cloudbeaver.service.ai.model.inputs.DataSourceId;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
|
||||
import org.jkiss.dbeaver.model.ai.AIChatConversation;
|
||||
import org.jkiss.dbeaver.model.ai.AIContextSettingsChatConversation;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class WebAIChatConversation {
|
||||
|
||||
@NotNull
|
||||
private final WebSession webSession;
|
||||
@NotNull
|
||||
private final AIChatConversation conversation;
|
||||
@Nullable
|
||||
private final WebAIChatConversationMetrics metrics;
|
||||
|
||||
public WebAIChatConversation(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull AIChatConversation conversation
|
||||
) {
|
||||
this.webSession = webSession;
|
||||
this.conversation = conversation;
|
||||
this.metrics = null;
|
||||
}
|
||||
|
||||
public WebAIChatConversation(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull AIChatConversation conversation,
|
||||
@Nullable WebAIChatConversationMetrics metrics
|
||||
) {
|
||||
this.webSession = webSession;
|
||||
this.conversation = conversation;
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public DataSourceId getDataSourceId() {
|
||||
DBPDataSourceContainer dataSourceContainer = conversation.getDataSource();
|
||||
if (dataSourceContainer == null) {
|
||||
return null;
|
||||
}
|
||||
return new DataSourceId(
|
||||
dataSourceContainer.getProject().getId(),
|
||||
dataSourceContainer.getId()
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public UUID getId() {
|
||||
return conversation.getId();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getPromptGeneratorId() {
|
||||
return conversation.getPromptGenerator().generatorId();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getCaption() {
|
||||
return conversation.getCaption();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public WebAIChatConversationSettings getSettings() {
|
||||
AIContextSettingsChatConversation settings = conversation.getCustomSettings();
|
||||
if (settings == null) {
|
||||
return null;
|
||||
}
|
||||
return new WebAIChatConversationSettings(webSession, settings, conversation.getDataSource());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public OffsetDateTime getTime() {
|
||||
LocalDateTime time = conversation.getMessages().isEmpty() ? conversation.getTime() : conversation.getLastMessageTime();
|
||||
return time.atZone(ZoneId.systemDefault())
|
||||
.withZoneSameInstant(ZoneOffset.UTC)
|
||||
.toOffsetDateTime();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<WebAIMessage> getMessages() {
|
||||
return conversation.getMessages().stream()
|
||||
.map(message -> new WebAIMessage(message, conversation))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public boolean isWaitingForResponse() {
|
||||
return webSession.getAttribute(WebServiceAI.AI_WAITING_ATTR + conversation.getId()) != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public WebAIChatConversationMetrics getMetrics() {
|
||||
return metrics;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getProfile() {
|
||||
return conversation.getProfile() == null ? null : conversation.getProfile().getProfileId();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public AIChatConversation getConversation() {
|
||||
return conversation;
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp
|
||||
*
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* NOTICE: All information contained herein is, and remains
|
||||
* the property of DBeaver Corp and its suppliers, if any.
|
||||
* The intellectual and technical concepts contained
|
||||
* herein are proprietary to DBeaver Corp and its suppliers
|
||||
* and may be covered by U.S. and Foreign Patents,
|
||||
* patents in process, and are protected by trade secret or copyright law.
|
||||
* Dissemination of this information or reproduction of this material
|
||||
* is strictly forbidden unless prior written permission is obtained
|
||||
* from DBeaver Corp.
|
||||
*/
|
||||
package io.cloudbeaver.service.ai.model;
|
||||
|
||||
import org.jkiss.dbeaver.model.ai.AIExtendedUsage;
|
||||
|
||||
public class WebAIChatConversationMetrics {
|
||||
private final int totalInputTokens;
|
||||
private final int totalOutputTokens;
|
||||
|
||||
private WebAIChatConversationMetrics(int totalInputTokens, int totalOutputTokens) {
|
||||
this.totalInputTokens = totalInputTokens;
|
||||
this.totalOutputTokens = totalOutputTokens;
|
||||
}
|
||||
|
||||
public int getTotalInputTokens() {
|
||||
return totalInputTokens;
|
||||
}
|
||||
|
||||
public int getTotalOutputTokens() {
|
||||
return totalOutputTokens;
|
||||
}
|
||||
|
||||
public static WebAIChatConversationMetrics from(AIExtendedUsage aiExtendedUsage) {
|
||||
return new WebAIChatConversationMetrics(
|
||||
aiExtendedUsage.totalInputTokens(),
|
||||
aiExtendedUsage.totalOutputTokens()
|
||||
);
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.service.ai.model;
|
||||
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.service.ai.WebAIUtils;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
|
||||
import org.jkiss.dbeaver.model.ai.AIContextSettingsChatConversation;
|
||||
import org.jkiss.dbeaver.model.ai.AIDatabaseScope;
|
||||
import org.jkiss.dbeaver.model.app.DBPProject;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class WebAIChatConversationSettings {
|
||||
private static final Log log = Log.getLog(WebAIChatConversationSettings.class);
|
||||
|
||||
@NotNull
|
||||
private final WebSession webSession;
|
||||
@NotNull
|
||||
private final AIContextSettingsChatConversation settings;
|
||||
@Nullable
|
||||
private final DBPProject project;
|
||||
|
||||
public WebAIChatConversationSettings(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull AIContextSettingsChatConversation settings,
|
||||
@Nullable DBPDataSourceContainer container
|
||||
) {
|
||||
this.webSession = webSession;
|
||||
this.settings = settings;
|
||||
this.project = container == null ? null : container.getProject();
|
||||
}
|
||||
|
||||
public boolean isMetaTransferConfirmed() {
|
||||
return true; //FIXME: we need to implement this
|
||||
}
|
||||
|
||||
public AIDatabaseScope getScope() {
|
||||
return settings.getScope();
|
||||
}
|
||||
|
||||
public List<String> getCustomObjectIds() {
|
||||
if (project == null) {
|
||||
return List.of();
|
||||
}
|
||||
String[] customObjectIds = settings.getCustomObjectIds();
|
||||
if (customObjectIds == null || customObjectIds.length == 0) {
|
||||
return List.of();
|
||||
}
|
||||
return WebAIUtils.convertObjectIdsToNodePaths(
|
||||
webSession.getProgressMonitor(),
|
||||
project,
|
||||
customObjectIds
|
||||
);
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp
|
||||
*
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* NOTICE: All information contained herein is, and remains
|
||||
* the property of DBeaver Corp and its suppliers, if any.
|
||||
* The intellectual and technical concepts contained
|
||||
* herein are proprietary to DBeaver Corp and its suppliers
|
||||
* and may be covered by U.S. and Foreign Patents,
|
||||
* patents in process, and are protected by trade secret or copyright law.
|
||||
* Dissemination of this information or reproduction of this material
|
||||
* is strictly forbidden unless prior written permission is obtained
|
||||
* from DBeaver Corp.
|
||||
*/
|
||||
package io.cloudbeaver.service.ai.model;
|
||||
|
||||
import io.cloudbeaver.WebServiceUtils;
|
||||
import io.cloudbeaver.model.WebPropertyInfo;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.model.ai.AIConfigurationProfile;
|
||||
|
||||
public class WebAIConfigurationProfile {
|
||||
|
||||
@NotNull
|
||||
private final transient WebSession webSession;
|
||||
@NotNull
|
||||
private final AIConfigurationProfile profile;
|
||||
|
||||
public WebAIConfigurationProfile(@NotNull WebSession webSession, @NotNull AIConfigurationProfile profile) {
|
||||
this.webSession = webSession;
|
||||
this.profile = profile;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getId() {
|
||||
return profile.getProfileId();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getName() {
|
||||
return profile.getProfileName();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getEngineId() {
|
||||
return profile.getEngineId();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public WebPropertyInfo[] getConfiguration() throws DBException {
|
||||
return WebServiceUtils.getObjectFilteredProperties(webSession, profile.getConfiguration(), null);
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.service.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
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.service.ai.model;
|
||||
|
||||
import io.cloudbeaver.WebServiceUtils;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.ai.AIFunctionDescriptor;
|
||||
|
||||
public class WebAIFunctionInfo {
|
||||
|
||||
@NotNull
|
||||
private final transient AIFunctionDescriptor descriptor;
|
||||
|
||||
public WebAIFunctionInfo(@NotNull AIFunctionDescriptor descriptor) {
|
||||
this.descriptor = descriptor;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getId() {
|
||||
return descriptor.getFullId();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getName() {
|
||||
return descriptor.getName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getDescription() {
|
||||
return descriptor.getAiDescription();
|
||||
}
|
||||
|
||||
public boolean isSystem() {
|
||||
return descriptor.isSystem();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getIcon() {
|
||||
return WebServiceUtils.makeIconId(descriptor.getIcon());
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp
|
||||
*
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* NOTICE: All information contained herein is, and remains
|
||||
* the property of DBeaver Corp and its suppliers, if any.
|
||||
* The intellectual and technical concepts contained
|
||||
* herein are proprietary to DBeaver Corp and its suppliers
|
||||
* and may be covered by U.S. and Foreign Patents,
|
||||
* patents in process, and are protected by trade secret or copyright law.
|
||||
* Dissemination of this information or reproduction of this material
|
||||
* is strictly forbidden unless prior written permission is obtained
|
||||
* from DBeaver Corp.
|
||||
*/
|
||||
package io.cloudbeaver.service.ai.model;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.ai.*;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.List;
|
||||
|
||||
public class WebAIMessage {
|
||||
|
||||
private final AIChatMessage message;
|
||||
private final AIChatConversation conversation;
|
||||
|
||||
public WebAIMessage(@NotNull AIChatMessage message, @NotNull AIChatConversation conversation) {
|
||||
this.message = message;
|
||||
this.conversation = conversation;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getConversationId() {
|
||||
return conversation.getId().toString();
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return message.id();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public AIMessageType getRole() {
|
||||
return message.message().getRole();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getContent() {
|
||||
return message.message().getContent();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getDisplayMessage() {
|
||||
return message.message().getDisplayMessage();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AIFunctionCall getFunctionCall() {
|
||||
return message.message().getFunctionCall();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public List<AIFunctionCall> getConfirmationFunctionCalls() {
|
||||
if (message.message().getConfirmation() instanceof AIFunctionCallConfirmation fcc) {
|
||||
return fcc.getFunctionCalls();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AIConfirmation getFunctionConfirmation() {
|
||||
return message.message().getConfirmation();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AIFunctionResult getFunctionResult() {
|
||||
return message.message().getFunctionResult();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public OffsetDateTime getTime() {
|
||||
return message.message().getTime().atZone(ZoneId.systemDefault())
|
||||
.withZoneSameInstant(ZoneOffset.UTC)
|
||||
.toOffsetDateTime();
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.service.ai.model;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
|
||||
public record WebAISendChatMessageInfo(
|
||||
@NotNull WebAIChatConversation conversation,
|
||||
@NotNull WebAIMessage userMessage,
|
||||
@NotNull WebAIMessage assistantMessage
|
||||
) {
|
||||
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp
|
||||
*
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* NOTICE: All information contained herein is, and remains
|
||||
* the property of DBeaver Corp and its suppliers, if any.
|
||||
* The intellectual and technical concepts contained
|
||||
* herein are proprietary to DBeaver Corp and its suppliers
|
||||
* and may be covered by U.S. and Foreign Patents,
|
||||
* patents in process, and are protected by trade secret or copyright law.
|
||||
* Dissemination of this information or reproduction of this material
|
||||
* is strictly forbidden unless prior written permission is obtained
|
||||
* from DBeaver Corp.
|
||||
*/
|
||||
package io.cloudbeaver.service.ai.model;
|
||||
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.service.ai.model.events.WSAiChatMessageChunkEvent;
|
||||
import io.cloudbeaver.service.ai.model.events.WSAiChatMessageErrorEvent;
|
||||
import io.cloudbeaver.service.ai.model.events.WSAiChatMessageEvent;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.ai.*;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class WebAiChatResponseConsumer implements AIChatResponseConsumer {
|
||||
private final StringBuilder responseBuilder;
|
||||
private final AIChatConversation conversation;
|
||||
private final WebSession webSession;
|
||||
private final AIChatSession chatSession;
|
||||
|
||||
public WebAiChatResponseConsumer(
|
||||
@NotNull AIChatConversation conversation,
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull AIChatSession chatSession
|
||||
) {
|
||||
this.conversation = conversation;
|
||||
this.webSession = webSession;
|
||||
this.chatSession = chatSession;
|
||||
this.responseBuilder = new StringBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nextMessageChunk(@NotNull String item) {
|
||||
if (CommonUtils.isEmpty(item)) {
|
||||
return;
|
||||
}
|
||||
if (responseBuilder.isEmpty()) {
|
||||
var chatMessage = new AIChatMessage(conversation.getNextMessageId(), AIMessage.assistantMessage("", null));
|
||||
webSession.addSessionEvent(new WSAiChatMessageEvent(new WebAIMessage(chatMessage, conversation)));
|
||||
}
|
||||
responseBuilder.append(item);
|
||||
webSession.addSessionEvent(new WSAiChatMessageChunkEvent(
|
||||
conversation.getId(),
|
||||
conversation.getNextMessageId(),
|
||||
item,
|
||||
false
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processFunctionCall(@NotNull AIMessage fcMessage) {
|
||||
// Send function call to front-end over web sockets
|
||||
AIChatMessage chatMessage = conversation.addMessage(fcMessage);
|
||||
WebAIMessage webMessage = new WebAIMessage(chatMessage, conversation);
|
||||
webSession.addSessionEvent(new WSAiChatMessageEvent(webMessage));
|
||||
chatSession.notifyMessageAdd(conversation, chatMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warning(@NotNull String message) {
|
||||
AIChatMessage chatMessage = conversation.addMessage(AIMessage.warningMessage(message));
|
||||
webSession.addSessionEvent(new WSAiChatMessageEvent(new WebAIMessage(chatMessage, conversation)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(@NotNull Throwable throwable) {
|
||||
var errorMessage = conversation.addMessage(AIMessage.errorMessage(throwable));
|
||||
if (responseBuilder.isEmpty()) {
|
||||
webSession.addSessionEvent(
|
||||
new WSAiChatMessageEvent(new WebAIMessage(errorMessage, conversation)));
|
||||
} else {
|
||||
webSession.addSessionEvent(
|
||||
new WSAiChatMessageErrorEvent(
|
||||
conversation.getId(),
|
||||
conversation.getNextMessageId(),
|
||||
throwable
|
||||
));
|
||||
}
|
||||
chatSession.notifyMessageAdd(conversation, errorMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void complete(@NotNull List<AIMessageMeta> meta, boolean finishConversation) {
|
||||
if (responseBuilder.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
AIChatMessage responseMessage = conversation.addMessage(AIMessage.assistantMessage(responseBuilder.toString(), meta));
|
||||
chatSession.notifyMessageAdd(conversation, responseMessage);
|
||||
webSession.addSessionEvent(new WSAiChatMessageChunkEvent(conversation.getId(), responseMessage.id(), null, true));
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2024 DBeaver Corp
|
||||
*
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* NOTICE: All information contained herein is, and remains
|
||||
* the property of DBeaver Corp and its suppliers, if any.
|
||||
* The intellectual and technical concepts contained
|
||||
* herein are proprietary to DBeaver Corp and its suppliers
|
||||
* and may be covered by U.S. and Foreign Patents,
|
||||
* patents in process, and are protected by trade secret or copyright law.
|
||||
* Dissemination of this information or reproduction of this material
|
||||
* is strictly forbidden unless prior written permission is obtained
|
||||
* from DBeaver Corp.
|
||||
*/
|
||||
package io.cloudbeaver.service.ai.model.events;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.websocket.WSConstants;
|
||||
import org.jkiss.dbeaver.model.websocket.event.session.WSAbstractSessionEvent;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class WSAiChatMessageChunkEvent extends WSAbstractSessionEvent {
|
||||
|
||||
private final String messageId;
|
||||
@NotNull
|
||||
private final String conversationId;
|
||||
@Nullable
|
||||
private final String chunk;
|
||||
private final boolean completed;
|
||||
|
||||
public WSAiChatMessageChunkEvent(@NotNull UUID conversationId, int messageId, @Nullable String chunk, boolean completed) {
|
||||
super("cb_ai_chat_message_chunk", WSConstants.TOPIC_AI);
|
||||
this.messageId = CommonUtils.toString(messageId);
|
||||
this.conversationId = conversationId.toString();
|
||||
this.chunk = chunk;
|
||||
this.completed = completed;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getConversationId() {
|
||||
return conversationId;
|
||||
}
|
||||
|
||||
public String getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getChunk() {
|
||||
return chunk;
|
||||
}
|
||||
|
||||
public boolean isCompleted() {
|
||||
return completed;
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2024 DBeaver Corp
|
||||
*
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* NOTICE: All information contained herein is, and remains
|
||||
* the property of DBeaver Corp and its suppliers, if any.
|
||||
* The intellectual and technical concepts contained
|
||||
* herein are proprietary to DBeaver Corp and its suppliers
|
||||
* and may be covered by U.S. and Foreign Patents,
|
||||
* patents in process, and are protected by trade secret or copyright law.
|
||||
* Dissemination of this information or reproduction of this material
|
||||
* is strictly forbidden unless prior written permission is obtained
|
||||
* from DBeaver Corp.
|
||||
*/
|
||||
package io.cloudbeaver.service.ai.model.events;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.websocket.WSConstants;
|
||||
import org.jkiss.dbeaver.model.websocket.event.session.WSAbstractSessionEvent;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class WSAiChatMessageErrorEvent extends WSAbstractSessionEvent {
|
||||
|
||||
private final String messageId;
|
||||
@NotNull
|
||||
private final String conversationId;
|
||||
@Nullable
|
||||
private final String errorMessage;
|
||||
|
||||
public WSAiChatMessageErrorEvent(@NotNull UUID conversationId, int messageId, @Nullable Throwable throwable) {
|
||||
super("cb_ai_chat_message_error", WSConstants.TOPIC_AI);
|
||||
this.conversationId = conversationId.toString();
|
||||
this.messageId = CommonUtils.toString(messageId);
|
||||
this.errorMessage = CommonUtils.getAllExceptionMessages(throwable);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getConversationId() {
|
||||
return conversationId;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp
|
||||
*
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* NOTICE: All information contained herein is, and remains
|
||||
* the property of DBeaver Corp and its suppliers, if any.
|
||||
* The intellectual and technical concepts contained
|
||||
* herein are proprietary to DBeaver Corp and its suppliers
|
||||
* and may be covered by U.S. and Foreign Patents,
|
||||
* patents in process, and are protected by trade secret or copyright law.
|
||||
* Dissemination of this information or reproduction of this material
|
||||
* is strictly forbidden unless prior written permission is obtained
|
||||
* from DBeaver Corp.
|
||||
*/
|
||||
package io.cloudbeaver.service.ai.model.events;
|
||||
|
||||
import io.cloudbeaver.service.ai.model.WebAIMessage;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.ai.AIFunctionCall;
|
||||
import org.jkiss.dbeaver.model.ai.AIFunctionCallConfirmation;
|
||||
import org.jkiss.dbeaver.model.ai.AIFunctionResult;
|
||||
import org.jkiss.dbeaver.model.websocket.WSConstants;
|
||||
import org.jkiss.dbeaver.model.websocket.event.session.WSAbstractSessionEvent;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
public class WSAiChatMessageEvent extends WSAbstractSessionEvent {
|
||||
private static final String ID = "cb_ai_chat_message";
|
||||
|
||||
private final String messageId;
|
||||
@NotNull
|
||||
private final String conversationId;
|
||||
@Nullable
|
||||
private final String content;
|
||||
@Nullable
|
||||
private final String displayMessage;
|
||||
@NotNull
|
||||
private final String role;
|
||||
|
||||
private final String time;
|
||||
|
||||
@Nullable
|
||||
private AIFunctionCallConfirmation functionConfirmation;
|
||||
@Nullable
|
||||
private final AIFunctionCall functionCall;
|
||||
@Nullable
|
||||
private final AIFunctionResult functionResult;
|
||||
|
||||
public WSAiChatMessageEvent(@NotNull WebAIMessage message) {
|
||||
super(ID, WSConstants.TOPIC_AI);
|
||||
this.messageId = CommonUtils.toString(message.getId());
|
||||
this.conversationId = message.getConversationId();
|
||||
this.displayMessage = message.getDisplayMessage();
|
||||
this.content = message.getContent();
|
||||
this.role = message.getRole().name();
|
||||
this.time = message.getTime().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
|
||||
this.functionCall = message.getFunctionCall();
|
||||
this.functionResult = message.getFunctionResult();
|
||||
|
||||
if (message.getConfirmationFunctionCalls() != null) {
|
||||
this.functionConfirmation = new AIFunctionCallConfirmation(
|
||||
message.getConfirmationFunctionCalls());
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getConversationId() {
|
||||
return conversationId;
|
||||
}
|
||||
|
||||
public String getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getDisplayMessage() {
|
||||
return displayMessage;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AIFunctionCallConfirmation getFunctionConfirmation() {
|
||||
return functionConfirmation;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AIFunctionCall getFunctionCall() {
|
||||
return functionCall;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AIFunctionResult getFunctionResult() {
|
||||
return functionResult;
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp
|
||||
*
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* NOTICE: All information contained herein is, and remains
|
||||
* the property of DBeaver Corp and its suppliers, if any.
|
||||
* The intellectual and technical concepts contained
|
||||
* herein are proprietary to DBeaver Corp and its suppliers
|
||||
* and may be covered by U.S. and Foreign Patents,
|
||||
* patents in process, and are protected by trade secret or copyright law.
|
||||
* Dissemination of this information or reproduction of this material
|
||||
* is strictly forbidden unless prior written permission is obtained
|
||||
* from DBeaver Corp.
|
||||
*/
|
||||
package io.cloudbeaver.service.ai.model.events;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.websocket.WSConstants;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class WSAiFunctionCallConfirmationClientEvent extends WSClientEvent {
|
||||
|
||||
public static final String ID = "cb_client_ai_function_call_confirmation";
|
||||
|
||||
@NotNull
|
||||
private final String conversationId;
|
||||
@NotNull
|
||||
private final String messageId;
|
||||
private final List<String> confirmedFunctionCalls;
|
||||
private final List<String> declinedFunctionCalls;
|
||||
|
||||
public WSAiFunctionCallConfirmationClientEvent(
|
||||
@NotNull String conversationId,
|
||||
@NotNull String messageId,
|
||||
@NotNull List<String> confirmedFunctionCalls,
|
||||
@NotNull List<String> declinedFunctionCalls
|
||||
) {
|
||||
super(ID, WSConstants.TOPIC_AI);
|
||||
this.conversationId = conversationId;
|
||||
this.messageId = messageId;
|
||||
this.confirmedFunctionCalls = confirmedFunctionCalls;
|
||||
this.declinedFunctionCalls = declinedFunctionCalls;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getConversationId() {
|
||||
return conversationId;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<String> getConfirmedFunctionCalls() {
|
||||
return confirmedFunctionCalls == null ? List.of() : confirmedFunctionCalls;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<String> getDeclinedFunctionCalls() {
|
||||
return declinedFunctionCalls == null ? List.of() : declinedFunctionCalls;
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.service.ai.model.events;
|
||||
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.service.ai.WebAIUtils;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.ai.*;
|
||||
import org.jkiss.dbeaver.model.ai.utils.AIUtils;
|
||||
import org.jkiss.dbeaver.model.auth.SMSession;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSClientEventHandler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class WSAiFunctionCallConfirmationHandler implements WSClientEventHandler<WSAiFunctionCallConfirmationClientEvent> {
|
||||
public static final Log log = Log.getLog(WSAiFunctionCallConfirmationHandler.class);
|
||||
|
||||
@Override
|
||||
public void handleEvent(@NotNull SMSession session, @NotNull WSAiFunctionCallConfirmationClientEvent event) throws DBException {
|
||||
if (session instanceof WebSession webSession) {
|
||||
AIChatSession chatSession = WebAIUtils.findAiChatSession(webSession);
|
||||
if (chatSession == null) {
|
||||
webSession.addSessionError(new DBException("Chat session '" + event.getConversationId() + "' not found."));
|
||||
return;
|
||||
}
|
||||
AIChatConversation conversation = chatSession.getConversation(event.getConversationId());
|
||||
if (conversation == null) {
|
||||
webSession.addSessionError(new DBException("Chat conversation '" + event.getConversationId() + "' not found"));
|
||||
return;
|
||||
}
|
||||
AIChatMessage message = conversation.getMessage(event.getMessageId());
|
||||
if (message == null || !(message.message().getConfirmation() instanceof AIFunctionCallConfirmation fcc)) {
|
||||
webSession.addSessionError(new DBException("Chat conversation confirmation '" + event.getMessageId() + "' not found"));
|
||||
return;
|
||||
}
|
||||
List<AIFunctionCall> declinedCalls = new ArrayList<>();
|
||||
List<AIFunctionCall> approvedCalls = new ArrayList<>();
|
||||
|
||||
List<String> confirmedFunctions = event.getConfirmedFunctionCalls();
|
||||
List<String> declinedFunctions = event.getDeclinedFunctionCalls();
|
||||
|
||||
for (AIFunctionCall fc : fcc.getFunctionCalls()) {
|
||||
if (confirmedFunctions.contains(fc.getId().toString())) {
|
||||
approvedCalls.add(fc);
|
||||
} else if (declinedFunctions.contains(fc.getId().toString())) {
|
||||
declinedCalls.add(fc);
|
||||
}
|
||||
}
|
||||
|
||||
chatSession.declineFunctionCalls(conversation, declinedCalls);
|
||||
CompletableFuture<AIChatConversation> submission;
|
||||
if (approvedCalls.isEmpty()) {
|
||||
submission = WebAIUtils.scheduleConversationSubmission(
|
||||
webSession,
|
||||
chatSession,
|
||||
conversation,
|
||||
null,
|
||||
"Continue AI conversation"
|
||||
);
|
||||
} else {
|
||||
submission = WebAIUtils.scheduleConversationSubmission(
|
||||
webSession,
|
||||
chatSession,
|
||||
conversation,
|
||||
new AIFunctionCallConfirmation(approvedCalls),
|
||||
"Confirm function calls"
|
||||
);
|
||||
AIToolboxManager toolboxManager = chatSession.getAssistant().getToolboxManager();
|
||||
if (!declinedCalls.isEmpty() && !AIUtils.hasInformationFunctions(toolboxManager, approvedCalls)) {
|
||||
submission = submission.thenCompose(ignored -> WebAIUtils.scheduleConversationSubmission(
|
||||
webSession,
|
||||
chatSession,
|
||||
conversation,
|
||||
null,
|
||||
"Continue AI conversation"
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2024 DBeaver Corp
|
||||
*
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* NOTICE: All information contained herein is, and remains
|
||||
* the property of DBeaver Corp and its suppliers, if any.
|
||||
* The intellectual and technical concepts contained
|
||||
* herein are proprietary to DBeaver Corp and its suppliers
|
||||
* and may be covered by U.S. and Foreign Patents,
|
||||
* patents in process, and are protected by trade secret or copyright law.
|
||||
* Dissemination of this information or reproduction of this material
|
||||
* is strictly forbidden unless prior written permission is obtained
|
||||
* from DBeaver Corp.
|
||||
*/
|
||||
package io.cloudbeaver.service.ai.model.inputs;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
|
||||
public record DataSourceId(@NotNull String projectId, @NotNull String connectionId) {
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2024 DBeaver Corp
|
||||
*
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* NOTICE: All information contained herein is, and remains
|
||||
* the property of DBeaver Corp and its suppliers, if any.
|
||||
* The intellectual and technical concepts contained
|
||||
* herein are proprietary to DBeaver Corp and its suppliers
|
||||
* and may be covered by U.S. and Foreign Patents,
|
||||
* patents in process, and are protected by trade secret or copyright law.
|
||||
* Dissemination of this information or reproduction of this material
|
||||
* is strictly forbidden unless prior written permission is obtained
|
||||
* from DBeaver Corp.
|
||||
*/
|
||||
package io.cloudbeaver.service.ai.model.inputs;
|
||||
|
||||
import org.jkiss.code.Nullable;
|
||||
|
||||
public record WebAIChatConversationInput(
|
||||
@Nullable String promptGeneratorId,
|
||||
@Nullable DataSourceId dataSourceId,
|
||||
@Nullable String caption,
|
||||
@Nullable WebAiChatCompletionSettingsInput settings
|
||||
) {
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp
|
||||
*
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* NOTICE: All information contained herein is, and remains
|
||||
* the property of DBeaver Corp and its suppliers, if any.
|
||||
* The intellectual and technical concepts contained
|
||||
* herein are proprietary to DBeaver Corp and its suppliers
|
||||
* and may be covered by U.S. and Foreign Patents,
|
||||
* patents in process, and are protected by trade secret or copyright law.
|
||||
* Dissemination of this information or reproduction of this material
|
||||
* is strictly forbidden unless prior written permission is obtained
|
||||
* from DBeaver Corp.
|
||||
*/
|
||||
package io.cloudbeaver.service.ai.model.inputs;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public record WebAIConfigurationProfileInput(
|
||||
@NotNull String profileId,
|
||||
@Nullable String profileName,
|
||||
@Nullable String engineId,
|
||||
@Nullable Map<String, Object> configuration
|
||||
) {
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp
|
||||
*
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* NOTICE: All information contained herein is, and remains
|
||||
* the property of DBeaver Corp and its suppliers, if any.
|
||||
* The intellectual and technical concepts contained
|
||||
* herein are proprietary to DBeaver Corp and its suppliers
|
||||
* and may be covered by U.S. and Foreign Patents,
|
||||
* patents in process, and are protected by trade secret or copyright law.
|
||||
* Dissemination of this information or reproduction of this material
|
||||
* is strictly forbidden unless prior written permission is obtained
|
||||
* from DBeaver Corp.
|
||||
*/
|
||||
package io.cloudbeaver.service.ai.model.inputs;
|
||||
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.ai.AIDatabaseScope;
|
||||
|
||||
public record WebAiChatCompletionSettingsInput(
|
||||
@Nullable Boolean metaTransferConfirmed,
|
||||
@Nullable AIDatabaseScope scope,
|
||||
@Nullable String[] customObjectIds,
|
||||
@Nullable String profile,
|
||||
@Nullable Boolean mcpEnabled) {
|
||||
}
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-Name: Cloudbeaver Web Service - Authentication
|
||||
Bundle-SymbolicName: io.cloudbeaver.service.auth;singleton:=true
|
||||
Bundle-Version: 1.0.150.qualifier
|
||||
Bundle-Release-Date: 20260803
|
||||
Bundle-Version: 1.0.151.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: io.cloudbeaver.server.ce
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.service.auth</artifactId>
|
||||
<version>1.0.150-SNAPSHOT</version>
|
||||
<version>1.0.151-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
####################################################
|
||||
# Authentication API
|
||||
####################################################
|
||||
|
||||
enum AuthCredentialEncryption {
|
||||
none,
|
||||
plain,
|
||||
@@ -15,11 +19,11 @@ type AuthCredentialInfo {
|
||||
displayName: String!
|
||||
description: String
|
||||
|
||||
# This field must be shown in admin panel
|
||||
"This field must be shown in admin panel"
|
||||
admin: Boolean!
|
||||
# This field must be shown in login form
|
||||
"This field must be shown in login form"
|
||||
user: Boolean!
|
||||
# This field can be used as user identifier
|
||||
"This field can be used as user identifier"
|
||||
|
||||
identifying: Boolean!
|
||||
possibleValues: [String]
|
||||
|
||||
+2
-1
@@ -80,8 +80,9 @@ public class ReverseProxyConfigurator implements DBWServiceServerConfigurator {
|
||||
parameters.put(RPConstants.PARAM_FIRST_NAME, RPAuthProvider.X_FIRST_NAME);
|
||||
parameters.put(RPConstants.PARAM_LAST_NAME, RPAuthProvider.X_LAST_NAME);
|
||||
parameters.put(RPConstants.PARAM_FULL_NAME, RPAuthProvider.X_FULL_NAME);
|
||||
parameters.put(RPAuthProvider.PARAM_AUTO_USER_PROVISIONING, true);
|
||||
smReverseProxyProviderConfiguration.setParameters(parameters);
|
||||
authApplication.getAuthConfiguration().addAuthProviderConfiguration(smReverseProxyProviderConfiguration );
|
||||
authApplication.getAuthConfiguration().addAuthProviderConfiguration(smReverseProxyProviderConfiguration);
|
||||
try {
|
||||
authApplication.flushConfiguration();
|
||||
} catch (Exception e) {
|
||||
|
||||
+1
-1
@@ -129,7 +129,7 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
|
||||
}
|
||||
try {
|
||||
Map<String, Object> authParameters = new HashMap<>();
|
||||
authParameters.put(SMConstants.USER_ORIGIN, ServletAppUtils.getOriginFromRequest(httpRequest));
|
||||
authParameters.put(SMConstants.USER_ORIGIN, ServletAppUtils.getHostOriginFromRequest(httpRequest));
|
||||
|
||||
var smAuthInfo = initiateAuthentication(webSession, providerId, providerConfigurationId, authParameters, forceSessionsLogout);
|
||||
if (smAuthInfo.getAuthStatus() != SMAuthStatus.IN_PROGRESS) {
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-Name: Cloudbeaver Web Service - Data Transfer
|
||||
Bundle-SymbolicName: io.cloudbeaver.service.data.transfer;singleton:=true
|
||||
Bundle-Version: 1.0.151.qualifier
|
||||
Bundle-Release-Date: 20260803
|
||||
Bundle-Version: 1.0.152.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: io.cloudbeaver.server.ce,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.service.data.transfer</artifactId>
|
||||
<version>1.0.151-SNAPSHOT</version>
|
||||
<version>1.0.152-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
+32
@@ -1,4 +1,6 @@
|
||||
####################################################
|
||||
# Data transfer queries
|
||||
####################################################
|
||||
|
||||
type DataTransferProcessorInfo {
|
||||
id: ID!
|
||||
@@ -34,6 +36,27 @@ type DataTransferDefaultExportSettings {
|
||||
supportedEncodings: [String!]!
|
||||
}
|
||||
|
||||
"""
|
||||
Database dependent data import settings.
|
||||
Which of them are supported by a particular connection is reported by ConnectionInfo.driverConfiguration.
|
||||
New database dependent import settings should be added here.
|
||||
"""
|
||||
input DataTransferImportSettings {
|
||||
"Id of the 'on duplicate key' replace method"
|
||||
onDuplicateKeyMethod: ID
|
||||
"Use bulk load during import"
|
||||
useBulkLoad: Boolean
|
||||
"Use transactions during import"
|
||||
useTransactions: Boolean
|
||||
}
|
||||
|
||||
input DataTransferImportParameters {
|
||||
"Import processor ID"
|
||||
processorId: ID!
|
||||
"Import settings. Omitted settings keep their default value"
|
||||
settings: DataTransferImportSettings
|
||||
}
|
||||
|
||||
input DataTransferParameters {
|
||||
"Processor ID"
|
||||
processorId: ID!
|
||||
@@ -78,6 +101,15 @@ extend type Query {
|
||||
parameters: DataTransferParameters!
|
||||
): AsyncTaskInfo!
|
||||
|
||||
"Creates async task for data transfer import of a previously uploaded file into the results"
|
||||
dataTransferImportDataIntoResults(
|
||||
projectId: ID,
|
||||
connectionId: ID!,
|
||||
contextId: ID!,
|
||||
resultsId: ID!,
|
||||
parameters: DataTransferImportParameters!
|
||||
): AsyncTaskInfo!
|
||||
|
||||
"Deletes data transfer file by ID"
|
||||
dataTransferRemoveDataFile(dataFileId: String!): Boolean @deprecated(reason: "25.0.1")
|
||||
|
||||
|
||||
+17
-9
@@ -21,13 +21,9 @@ import io.cloudbeaver.WebAction;
|
||||
import io.cloudbeaver.model.WebAsyncTaskInfo;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.service.DBWService;
|
||||
import io.cloudbeaver.service.data.transfer.impl.WebDataTransferDefaultExportSettings;
|
||||
import io.cloudbeaver.service.data.transfer.impl.WebDataTransferParameters;
|
||||
import io.cloudbeaver.service.data.transfer.impl.WebDataTransferStreamProcessor;
|
||||
import io.cloudbeaver.service.data.transfer.impl.WebDataTransferTaskConfig;
|
||||
import io.cloudbeaver.service.data.transfer.impl.*;
|
||||
import io.cloudbeaver.service.sql.WebSQLContextInfo;
|
||||
import io.cloudbeaver.service.sql.WebSQLProcessor;
|
||||
import io.cloudbeaver.service.sql.WebSQLResultsInfo;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.model.rm.RMConstants;
|
||||
@@ -61,11 +57,17 @@ public interface DBWServiceDataTransfer extends DBWService {
|
||||
@NotNull
|
||||
@WebAction(requireGlobalPermissions = RMConstants.PERMISSION_DATA_EDITOR_IMPORT)
|
||||
WebAsyncTaskInfo asyncImportDataContainer(
|
||||
@NotNull String processorId,
|
||||
@NotNull Path path,
|
||||
@NotNull WebSQLResultsInfo webSQLResultsInfo,
|
||||
@NotNull WebSQLContextInfo sqlContextInfo,
|
||||
@NotNull String resultsId,
|
||||
@NotNull WebDataTransferImportParameters parameters,
|
||||
@NotNull WebSession webSession) throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
WebAsyncTaskInfo runImportDataTask(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String taskId,
|
||||
@NotNull Path path) throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
@WebAction(requireGlobalPermissions = RMConstants.PERMISSION_DATA_EDITOR_EXPORT)
|
||||
WebAsyncTaskInfo dataTransferExportDataFromResults(
|
||||
@@ -94,4 +96,10 @@ public interface DBWServiceDataTransfer extends DBWService {
|
||||
@NotNull WebDataTransferTaskConfig taskConfig,
|
||||
@NotNull OutputStream outputStream
|
||||
) throws DBException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user has permission to import data.
|
||||
* Returns {@code true} if import is allowed, {@code false} otherwise.
|
||||
*/
|
||||
boolean validateImportPermission(@NotNull WebSession session);
|
||||
}
|
||||
+7
-4
@@ -21,10 +21,7 @@ import io.cloudbeaver.service.DBWBindingContext;
|
||||
import io.cloudbeaver.service.DBWServiceBindingServlet;
|
||||
import io.cloudbeaver.service.DBWServletContext;
|
||||
import io.cloudbeaver.service.WebServiceBindingBase;
|
||||
import io.cloudbeaver.service.data.transfer.impl.WebDataTransferImportServlet;
|
||||
import io.cloudbeaver.service.data.transfer.impl.WebDataTransferParameters;
|
||||
import io.cloudbeaver.service.data.transfer.impl.WebDataTransferServlet;
|
||||
import io.cloudbeaver.service.data.transfer.impl.WebServiceDataTransfer;
|
||||
import io.cloudbeaver.service.data.transfer.impl.*;
|
||||
import io.cloudbeaver.service.sql.WebServiceBindingSQL;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
@@ -56,6 +53,12 @@ public class WebServiceBindingDataTransfer extends WebServiceBindingBase<DBWServ
|
||||
getArgumentVal(env, "resultsId"),
|
||||
new WebDataTransferParameters(getArgument(env, "parameters"))
|
||||
))
|
||||
.dataFetcher("dataTransferImportDataIntoResults", env -> getService(env).asyncImportDataContainer(
|
||||
WebServiceBindingSQL.getSQLContext(env),
|
||||
getArgument(env, "resultsId"),
|
||||
new WebDataTransferImportParameters(getArgument(env, "parameters")),
|
||||
getWebSession(env)
|
||||
))
|
||||
.dataFetcher("dataTransferRemoveDataFile", env -> getService(env).dataTransferRemoveDataFile(
|
||||
getWebSession(env),
|
||||
getArgumentVal(env, "dataFileId")
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.service.data.transfer.impl;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.data.json.JSONUtils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Parameters of a data import
|
||||
*/
|
||||
public class WebDataTransferImportParameters {
|
||||
|
||||
private final String processorId;
|
||||
private final Map<String, Object> settings;
|
||||
|
||||
public WebDataTransferImportParameters(@NotNull Map<String, Object> params) {
|
||||
this.processorId = JSONUtils.getString(params, "processorId");
|
||||
this.settings = JSONUtils.getObjectOrNull(params, "settings");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getProcessorId() {
|
||||
return processorId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Map<String, Object> getSettings() {
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
+27
-55
@@ -20,31 +20,23 @@ import com.google.gson.stream.JsonWriter;
|
||||
import io.cloudbeaver.DBWConstants;
|
||||
import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.model.WebAsyncTaskInfo;
|
||||
import io.cloudbeaver.model.WebConnectionInfo;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.server.BaseWebPlatform;
|
||||
import io.cloudbeaver.server.CBConstants;
|
||||
import io.cloudbeaver.server.WebAppUtils;
|
||||
import io.cloudbeaver.server.WebApplication;
|
||||
import io.cloudbeaver.service.WebServiceServletBase;
|
||||
import io.cloudbeaver.service.data.transfer.DBWServiceDataTransfer;
|
||||
import io.cloudbeaver.service.sql.WebSQLContextInfo;
|
||||
import io.cloudbeaver.service.sql.WebSQLProcessor;
|
||||
import io.cloudbeaver.service.sql.WebSQLResultsInfo;
|
||||
import io.cloudbeaver.service.sql.WebServiceBindingSQL;
|
||||
import jakarta.servlet.MultipartConfigElement;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.annotation.MultipartConfig;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.data.json.JSONUtils;
|
||||
import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
import org.jkiss.utils.HttpConstants;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -54,11 +46,10 @@ import java.util.UUID;
|
||||
@MultipartConfig
|
||||
public class WebDataTransferImportServlet extends WebServiceServletBase {
|
||||
|
||||
private static final Log log = Log.getLog(WebDataTransferImportServlet.class);
|
||||
public static final String ECLIPSE_JETTY_MULTIPART_CONFIG = "org.eclipse.jetty.multipartConfig";
|
||||
private static final String PARAM_TASK_ID = "taskId";
|
||||
|
||||
DBWServiceDataTransfer dbwServiceDataTransfer;
|
||||
|
||||
private final DBWServiceDataTransfer dbwServiceDataTransfer;
|
||||
|
||||
public WebDataTransferImportServlet(WebApplication application, DBWServiceDataTransfer dbwServiceDataTransfer) {
|
||||
super(application);
|
||||
@@ -79,7 +70,7 @@ public class WebDataTransferImportServlet extends WebServiceServletBase {
|
||||
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Permission denied");
|
||||
return;
|
||||
}
|
||||
if (validateImportPermission(session)) {
|
||||
if (!dbwServiceDataTransfer.validateImportPermission(session)) {
|
||||
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Import is not allowed for this user");
|
||||
return;
|
||||
}
|
||||
@@ -91,57 +82,38 @@ public class WebDataTransferImportServlet extends WebServiceServletBase {
|
||||
request.setAttribute(ECLIPSE_JETTY_MULTIPART_CONFIG, MULTI_PART_CONFIG);
|
||||
|
||||
Map<String, Object> variables = getVariables(request);
|
||||
|
||||
String projectId = JSONUtils.getString(variables, "projectId");
|
||||
String connectionId = JSONUtils.getString(variables, "connectionId");
|
||||
String contextId = JSONUtils.getString(variables, "contextId");
|
||||
String resultId = JSONUtils.getString(variables, "resultsId");
|
||||
String processorId = JSONUtils.getString(variables, "processorId");
|
||||
|
||||
if (projectId == null || connectionId == null || contextId == null || resultId == null || processorId == null) {
|
||||
throw new IllegalArgumentException("Missing required parameters");
|
||||
String taskId = JSONUtils.getString(variables, "taskId");
|
||||
if (CommonUtils.isEmpty(taskId)) {
|
||||
throw new IllegalArgumentException("Missing required parameter '" + PARAM_TASK_ID + "'");
|
||||
}
|
||||
|
||||
WebConnectionInfo webConnectionInfo = session.getAccessibleProjectById(projectId).getWebConnectionInfo(connectionId);
|
||||
WebSQLProcessor processor = WebServiceBindingSQL.getSQLProcessor(webConnectionInfo);
|
||||
WebSQLContextInfo webSQLContextInfo = processor.getContext(contextId);
|
||||
|
||||
if (webSQLContextInfo == null) {
|
||||
throw new DBWebException("Context is empty");
|
||||
}
|
||||
|
||||
WebSQLResultsInfo webSQLResultsInfo = webSQLContextInfo.getResults(resultId);
|
||||
Path filePath;
|
||||
|
||||
String fileName = UUID.randomUUID().toString();
|
||||
Path filePath = tempFolder.resolve(fileName);
|
||||
try {
|
||||
InputStream file = request.getPart("fileData").getInputStream();
|
||||
filePath = tempFolder.resolve(UUID.randomUUID().toString());
|
||||
Files.write(filePath, file.readAllBytes());
|
||||
request.getPart("fileData").write(fileName);
|
||||
} catch (ServletException e) {
|
||||
throw new DBWebException(e.getMessage());
|
||||
}
|
||||
|
||||
WebAsyncTaskInfo asyncImportDataContainer =
|
||||
dbwServiceDataTransfer.asyncImportDataContainer(processorId, filePath, webSQLResultsInfo, session);
|
||||
WebAsyncTaskInfo importTask;
|
||||
try {
|
||||
importTask = dbwServiceDataTransfer.runImportDataTask(session, taskId, filePath);
|
||||
} catch (Exception e) {
|
||||
Files.deleteIfExists(filePath);
|
||||
throw e;
|
||||
}
|
||||
|
||||
response.setContentType(HttpConstants.CONTENT_TYPE_JSON);
|
||||
Map<String, Object> parameters = new LinkedHashMap<>();
|
||||
parameters.put("id", asyncImportDataContainer.getId());
|
||||
parameters.put("name", asyncImportDataContainer.getName());
|
||||
parameters.put("running", asyncImportDataContainer.isRunning());
|
||||
parameters.put("status", asyncImportDataContainer.getStatus());
|
||||
parameters.put("error", asyncImportDataContainer.getError());
|
||||
parameters.put("taskResult", asyncImportDataContainer.getTaskResult());
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("id", importTask.getId());
|
||||
result.put("name", importTask.getName());
|
||||
result.put("running", importTask.isRunning());
|
||||
result.put("status", importTask.getStatus());
|
||||
result.put("error", importTask.getError());
|
||||
result.put("taskResult", importTask.getTaskResult());
|
||||
try (JsonWriter writer = new JsonWriter(response.getWriter())) {
|
||||
JSONUtils.serializeMap(writer, parameters);
|
||||
JSONUtils.serializeMap(writer, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean validateImportPermission(@NotNull WebSession session) {
|
||||
if (WebAppUtils.getWebApplication().isCommunity()) {
|
||||
Map<String, Object> productSettings = WebAppUtils.getWebApplication().getServerConfiguration().getProductSettings();
|
||||
return JSONUtils.getBoolean(productSettings, CBConstants.PREF_DATA_EDITOR_IMPORT_DISABLED_OLD, false);
|
||||
}
|
||||
return !session.hasGlobalPermission(DBWConstants.GLOBAL_PERMISSION_DATA_EDITOR_IMPORT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.service.data.transfer.impl;
|
||||
|
||||
import io.cloudbeaver.service.sql.WebSQLResultsInfo;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.tools.transfer.registry.DataTransferProcessorDescriptor;
|
||||
|
||||
/**
|
||||
* A data import task config
|
||||
*/
|
||||
public class WebDataTransferImportTaskConfig {
|
||||
|
||||
@NotNull
|
||||
private final String taskId;
|
||||
@NotNull
|
||||
private final WebSQLResultsInfo results;
|
||||
@NotNull
|
||||
private final DataTransferProcessorDescriptor processor;
|
||||
@NotNull
|
||||
private final WebDataTransferImportParameters parameters;
|
||||
|
||||
public WebDataTransferImportTaskConfig(
|
||||
@NotNull String taskId,
|
||||
@NotNull WebSQLResultsInfo results,
|
||||
@NotNull DataTransferProcessorDescriptor processor,
|
||||
@NotNull WebDataTransferImportParameters parameters
|
||||
) {
|
||||
this.taskId = taskId;
|
||||
this.results = results;
|
||||
this.processor = processor;
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getTaskId() {
|
||||
return taskId;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public WebSQLResultsInfo getResults() {
|
||||
return results;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public DataTransferProcessorDescriptor getProcessor() {
|
||||
return processor;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public WebDataTransferImportParameters getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
+12
-1
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2025 DBeaver Corp and others
|
||||
* Copyright (C) 2010-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,10 +18,12 @@ package io.cloudbeaver.service.data.transfer.impl;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class WebDataTransferSessionConfig {
|
||||
|
||||
private final Map<String, WebDataTransferTaskConfig> tasks = new HashMap<>();
|
||||
private final Map<String, WebDataTransferImportTaskConfig> importTasks = new ConcurrentHashMap<>();
|
||||
|
||||
public WebDataTransferSessionConfig() {
|
||||
}
|
||||
@@ -36,10 +38,19 @@ public class WebDataTransferSessionConfig {
|
||||
}
|
||||
}
|
||||
|
||||
public void addImportTask(WebDataTransferImportTaskConfig importTaskConfig) {
|
||||
importTasks.put(importTaskConfig.getTaskId(), importTaskConfig);
|
||||
}
|
||||
|
||||
public WebDataTransferImportTaskConfig consumeImportTask(String taskId) {
|
||||
return importTasks.remove(taskId);
|
||||
}
|
||||
|
||||
public WebDataTransferSessionConfig deleteExportFiles() {
|
||||
synchronized (tasks) {
|
||||
tasks.clear();
|
||||
}
|
||||
importTasks.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
+74
-10
@@ -32,6 +32,7 @@ import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.data.json.JSONUtils;
|
||||
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
|
||||
@@ -39,6 +40,7 @@ import org.jkiss.dbeaver.model.struct.DBSDataContainer;
|
||||
import org.jkiss.dbeaver.model.struct.DBSDataManipulator;
|
||||
import org.jkiss.dbeaver.model.struct.DBSEntity;
|
||||
import org.jkiss.dbeaver.model.struct.DBSObjectContainer;
|
||||
import org.jkiss.dbeaver.tools.transfer.DTConstants;
|
||||
import org.jkiss.dbeaver.tools.transfer.IDataTransferConsumer;
|
||||
import org.jkiss.dbeaver.tools.transfer.IDataTransferProcessor;
|
||||
import org.jkiss.dbeaver.tools.transfer.database.*;
|
||||
@@ -233,20 +235,54 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public WebAsyncTaskInfo asyncImportDataContainer(
|
||||
@NotNull String processorId,
|
||||
@NotNull Path path,
|
||||
@NotNull WebSQLResultsInfo sqlContext,
|
||||
@NotNull WebSQLContextInfo sqlContext,
|
||||
@NotNull String resultsId,
|
||||
@NotNull WebDataTransferImportParameters parameters,
|
||||
@NotNull WebSession webSession
|
||||
) throws DBWebException {
|
||||
webSession.addInfoMessage("Import data");
|
||||
DataTransferProcessorDescriptor processor = DataTransferRegistry.getInstance().getProcessor(processorId);
|
||||
if (!validateImportPermission(webSession)) {
|
||||
throw new DBWebException("Permission denied. Data import is not allowed for this user");
|
||||
}
|
||||
DataTransferProcessorDescriptor processor = DataTransferRegistry.getInstance().getProcessor(parameters.getProcessorId());
|
||||
if (processor == null) {
|
||||
throw new DBWebException("Wrong data processor '" + processorId + "'");
|
||||
throw new DBWebException("Wrong data processor '" + parameters.getProcessorId() + "'");
|
||||
}
|
||||
WebSQLResultsInfo results = sqlContext.getResults(resultsId);
|
||||
if (!(results.getDataContainer() instanceof DBSDataManipulator)) {
|
||||
throw new DBWebException("Results '" + resultsId + "' do not support data import");
|
||||
}
|
||||
|
||||
// The task is created but not started: it is executed once the file is uploaded for this task id
|
||||
WebAsyncTaskInfo taskInfo = webSession.createAsyncTask("Data import");
|
||||
WebDataTransferUtils.getSessionDataTransferConfig(webSession)
|
||||
.addImportTask(new WebDataTransferImportTaskConfig(taskInfo.getId(), results, processor, parameters));
|
||||
return taskInfo;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public WebAsyncTaskInfo runImportDataTask(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String taskId,
|
||||
@NotNull Path path
|
||||
) throws DBWebException {
|
||||
WebDataTransferImportTaskConfig importTask =
|
||||
WebDataTransferUtils.getSessionDataTransferConfig(webSession).consumeImportTask(taskId);
|
||||
if (importTask == null) {
|
||||
throw new DBWebException("Data import task '" + taskId + "' not found");
|
||||
}
|
||||
WebAsyncTaskInfo taskInfo = webSession.getAsyncTask(taskId, "Data import", false);
|
||||
if (taskInfo == null) {
|
||||
throw new DBWebException("Data import task '" + taskId + "' not found");
|
||||
}
|
||||
webSession.addInfoMessage("Import data");
|
||||
log.info(String.format("Data import started: [userId=%s]", webSession.getUserId()));
|
||||
DBSDataContainer dataContainer = sqlContext.getDataContainer();
|
||||
|
||||
DataTransferProcessorDescriptor processor = importTask.getProcessor();
|
||||
DBSDataContainer dataContainer = importTask.getResults().getDataContainer();
|
||||
Map<String, Object> settings = importTask.getParameters().getSettings();
|
||||
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<>() {
|
||||
@Override
|
||||
public void run(@NotNull DBRProgressMonitor monitor) throws InvocationTargetException {
|
||||
@@ -254,7 +290,7 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
|
||||
try {
|
||||
monitor.subTask("Import data using " + processor.getName());
|
||||
try {
|
||||
importData(monitor, processor, (DBSDataManipulator) dataContainer, path);
|
||||
importData(monitor, processor, (DBSDataManipulator) dataContainer, path, settings);
|
||||
} catch (Exception e) {
|
||||
if (e instanceof DBException) {
|
||||
throw e;
|
||||
@@ -273,7 +309,15 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
|
||||
}
|
||||
}
|
||||
};
|
||||
return webSession.createAndRunAsyncTask("Data import", runnable);
|
||||
return webSession.runAsyncTask(taskInfo, runnable);
|
||||
}
|
||||
|
||||
public boolean validateImportPermission(@NotNull WebSession session) {
|
||||
if (WebAppUtils.getWebApplication().isCommunity()) {
|
||||
Map<String, Object> productSettings = WebAppUtils.getWebApplication().getServerConfiguration().getProductSettings();
|
||||
return !JSONUtils.getBoolean(productSettings, CBConstants.PREF_DATA_EDITOR_IMPORT_DISABLED_OLD, false);
|
||||
}
|
||||
return session.hasGlobalPermission(DBWConstants.GLOBAL_PERMISSION_DATA_EDITOR_IMPORT);
|
||||
}
|
||||
|
||||
private void exportData(
|
||||
@@ -342,7 +386,8 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
|
||||
@NotNull DBRProgressMonitor monitor,
|
||||
@NotNull DataTransferProcessorDescriptor processor,
|
||||
@NotNull DBSDataManipulator dataContainer,
|
||||
@NotNull Path path
|
||||
@NotNull Path path,
|
||||
@Nullable Map<String, Object> settings
|
||||
) throws DBException {
|
||||
IDataTransferProcessor processorInstance = processor.getInstance();
|
||||
|
||||
@@ -354,6 +399,7 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
|
||||
DatabaseConsumerSettings databaseConsumerSettings = new DatabaseConsumerSettings();
|
||||
databaseConsumerSettings.setContainer((DBSObjectContainer) dataContainer.getDataSource());
|
||||
databaseConsumerSettings.setEnableQmLogging(true);
|
||||
applyImportSettings(databaseConsumerSettings, settings);
|
||||
consumer.setSettings(databaseConsumerSettings);
|
||||
|
||||
StreamProducerSettings producerSettings = new StreamProducerSettings();
|
||||
@@ -384,4 +430,22 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
|
||||
}
|
||||
}
|
||||
|
||||
private void applyImportSettings(
|
||||
@NotNull DatabaseConsumerSettings consumerSettings,
|
||||
@Nullable Map<String, Object> settings
|
||||
) {
|
||||
if (CommonUtils.isEmpty(settings)) {
|
||||
return;
|
||||
}
|
||||
consumerSettings.setOnDuplicateKeyInsertMethodId(CommonUtils.toString(
|
||||
settings.get(DTConstants.PROP_ON_DUPLICATE_KEY_METHOD),
|
||||
consumerSettings.getOnDuplicateKeyInsertMethodId()));
|
||||
consumerSettings.setUseBulkLoad(CommonUtils.getBoolean(
|
||||
settings.get(DTConstants.PROP_USE_BULK_LOAD),
|
||||
consumerSettings.isUseBulkLoad()));
|
||||
consumerSettings.setUseTransactions(CommonUtils.getBoolean(
|
||||
settings.get(DTConstants.PROP_USE_TRANSACTIONS),
|
||||
consumerSettings.isUseTransactions()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-Name: Cloudbeaver Web Service - File System
|
||||
Bundle-SymbolicName: io.cloudbeaver.service.fs;singleton:=true
|
||||
Bundle-Version: 1.0.68.qualifier
|
||||
Bundle-Release-Date: 20260803
|
||||
Bundle-Version: 1.0.69.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: io.cloudbeaver.server.ce,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.service.fs</artifactId>
|
||||
<version>1.0.68-SNAPSHOT</version>
|
||||
<version>1.0.69-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
####################################################
|
||||
# File system API
|
||||
####################################################
|
||||
|
||||
type FSFile @since(version: "23.2.2") {
|
||||
"Name of the file or folder"
|
||||
name: String!
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Name: Cloudbeaver Web Service - LDAP
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-SymbolicName: io.cloudbeaver.service.ldap.auth;singleton:=true
|
||||
Bundle-Version: 1.0.33.qualifier
|
||||
Bundle-Release-Date: 20260803
|
||||
Bundle-Version: 1.0.34.qualifier
|
||||
Bundle-Release-Date: 20260817
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: org.jkiss.dbeaver.model;visibility:=reexport,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user