diff --git a/.gitignore b/.gitignore
index f015f90569..613308396f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,4 @@ node_modules
# Local Netlify folder
.netlify
+.vscode/settings.json
diff --git a/client/packages/lowcoder-design/src/icons/index.tsx b/client/packages/lowcoder-design/src/icons/index.tsx
index 94453db48b..0eace6d2f3 100644
--- a/client/packages/lowcoder-design/src/icons/index.tsx
+++ b/client/packages/lowcoder-design/src/icons/index.tsx
@@ -144,6 +144,7 @@ export { ReactComponent as MongoIcon } from "./v1/icon-query-MongoDB.svg";
export { ReactComponent as PostgresIcon } from "./v1/icon-query-postgres.svg";
export { ReactComponent as RedisIcon } from "./v1/icon-query-Redis.svg";
export { ReactComponent as MSSQLIcon } from "./v1/icon-query-mssql.svg";
+export { ReactComponent as DatabricksIcon } from "./v1/icon-query-databricks.svg";
export { ReactComponent as SMTPIcon } from "./v1/icon-query-SMTP.svg";
export { ReactComponent as OracleIcon } from "./v1/icon-query-OracleDB.svg";
export { ReactComponent as ClickHouseIcon } from "./v1/icon-query-ClickHouse.svg";
diff --git a/client/packages/lowcoder-design/src/icons/v1/icon-query-databricks.svg b/client/packages/lowcoder-design/src/icons/v1/icon-query-databricks.svg
new file mode 100644
index 0000000000..78ef0bffb4
--- /dev/null
+++ b/client/packages/lowcoder-design/src/icons/v1/icon-query-databricks.svg
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/api/datasourceApi.ts b/client/packages/lowcoder/src/api/datasourceApi.ts
index 1be29e6469..752a7e4918 100644
--- a/client/packages/lowcoder/src/api/datasourceApi.ts
+++ b/client/packages/lowcoder/src/api/datasourceApi.ts
@@ -27,6 +27,13 @@ export interface SQLConfig extends PreparedStatementConfig {
usingSsl: boolean;
}
+export interface DatabricksConfig extends SQLConfig {
+ authMechanism: string; // 3: Personal Access Token, 11: OAuth
+ usingUri: boolean;
+ jdbcUri: string;
+ httpPath: string;
+ catalog: string;
+}
export interface MongoConfig extends SQLConfig {
uri: string;
usingUri: boolean;
@@ -123,6 +130,7 @@ export type DatasourceConfigType =
| SQLConfig
| HttpConfig
| MongoConfig
+ | DatabricksConfig
| OAuthBasicConfig
| EsConfig
| OracleConfig
diff --git a/client/packages/lowcoder/src/comps/comps/formComp/generate/databricks.tsx b/client/packages/lowcoder/src/comps/comps/formComp/generate/databricks.tsx
new file mode 100644
index 0000000000..3cab3b2bd7
--- /dev/null
+++ b/client/packages/lowcoder/src/comps/comps/formComp/generate/databricks.tsx
@@ -0,0 +1,64 @@
+import {
+ DataSourceTypeConfig,
+ FullColumnInfo,
+ generateInsertSql,
+ QueryDataType,
+} from "./dataSourceCommon";
+import {
+ CompSelection,
+ dateCompSelection,
+ allCompSelection,
+ numberCompSelection,
+ timeCompSelection,
+ dateTimeCompSelection,
+} from "./comp";
+
+function getCompSelection(columnType: string): CompSelection | undefined {
+ if (!columnType) {
+ return undefined;
+ }
+ switch (columnType.toLowerCase()) {
+ case "bit":
+ case "tinyint":
+ case "smallint":
+ case "int":
+ case "bigint":
+ case "dec":
+ case "decimal":
+ case "numeric":
+ case "smallmoney":
+ case "money":
+ case "float":
+ case "real":
+ return numberCompSelection(true);
+ case "date":
+ return dateCompSelection();
+ case "datetime":
+ case "smalldatetime":
+ return dateTimeCompSelection();
+ case "time":
+ return timeCompSelection();
+ }
+ return allCompSelection();
+}
+
+function getQueryInitData(
+ formName: string,
+ tableName: string,
+ infos: FullColumnInfo[]
+): QueryDataType {
+ return {
+ compType: "databricks",
+ comp: {
+ sql: generateInsertSql(tableName, infos),
+ commandType: "INSERT",
+ mode: "SQL",
+ },
+ };
+}
+
+export const databricksConfig: DataSourceTypeConfig = {
+ type: "databricks",
+ getCompSelection,
+ getQueryInitData,
+};
diff --git a/client/packages/lowcoder/src/comps/comps/formComp/generate/index.tsx b/client/packages/lowcoder/src/comps/comps/formComp/generate/index.tsx
index 7fdffe2b7a..1ec29e8f64 100644
--- a/client/packages/lowcoder/src/comps/comps/formComp/generate/index.tsx
+++ b/client/packages/lowcoder/src/comps/comps/formComp/generate/index.tsx
@@ -3,6 +3,7 @@ import { msSqlConfig } from "./mssql";
import { mysqlConfig } from "./mysql";
import { oracleSqlConfig } from "./oracle";
import { postgreSqlConfig } from "./postgresql";
+import { databricksConfig } from "./databricks";
import { DatasourceType } from "@lowcoder-ee/constants/queryConstants";
export function getDataSourceTypeConfig(
@@ -14,6 +15,8 @@ export function getDataSourceTypeConfig(
return mysqlConfig;
case "postgres":
return postgreSqlConfig;
+ case "databricks":
+ return databricksConfig;
case "mssql":
return msSqlConfig;
case "oracle":
diff --git a/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx b/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx
index fd4939da47..0bf11b987f 100644
--- a/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx
+++ b/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx
@@ -449,7 +449,7 @@ export const QueryGeneralPropertyView = (props: {
- {["postgres", "mysql", "mssql", "oracle", "mariadb"].includes(datasourceType) && (
+ {["databricks", "postgres", "mysql", "mssql", "oracle", "mariadb"].includes(datasourceType) && (
[] = [
"mysql",
"mongodb",
"postgres",
+ "databricks",
"redis",
"es",
"mssql",
diff --git a/client/packages/lowcoder/src/constants/queryConstants.ts b/client/packages/lowcoder/src/constants/queryConstants.ts
index be78de0d6e..900c7db673 100644
--- a/client/packages/lowcoder/src/constants/queryConstants.ts
+++ b/client/packages/lowcoder/src/constants/queryConstants.ts
@@ -24,6 +24,7 @@ export type DatasourceType =
| "redis"
| "es"
| "mssql"
+ | "databricks"
| "smtp"
| "oracle"
| "clickHouse"
@@ -46,6 +47,7 @@ export const QueryMap = {
redis: RedisQuery,
es: EsQuery,
mssql: SQLQuery,
+ databricks: SQLQuery,
smtp: SMTPQuery,
oracle: SQLQuery,
clickHouse: SQLQuery,
diff --git a/client/packages/lowcoder/src/pages/datasource/form/databricksDatasourceForm.tsx b/client/packages/lowcoder/src/pages/datasource/form/databricksDatasourceForm.tsx
new file mode 100644
index 0000000000..c9ae59494f
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/datasource/form/databricksDatasourceForm.tsx
@@ -0,0 +1,139 @@
+import React, { useState, useEffect } from "react";
+import { DatasourceForm, FormInputItem, FormSection, FormSelectItem, FormInputPasswordItem, FormNumberInputItem, FormCheckboxItem } from "lowcoder-design";
+import { DatabricksConfig } from "api/datasourceApi";
+import { DatasourceFormProps } from "./datasourceFormRegistry";
+import { useHostCheck } from "./useHostCheck";
+import { trans } from "i18n";
+import {
+ DatabaseFormInputItem,
+ DatasourceNameFormInputItem,
+ encryptedPlaceholder,
+ HostFormInputItem,
+ PasswordFormInputItem,
+ PortFormInputItem,
+ SSLFormCheckboxItem,
+ // UserNameFormInputItem, // removed
+} from "../form";
+
+export const DatabricksDatasourceForm = (props: DatasourceFormProps) => {
+ const { form, datasource, size } = props;
+ const datasourceConfig = datasource?.datasourceConfig as DatabricksConfig;
+ const [usingUri, setUsingUri] = useState(datasourceConfig?.usingUri);
+ const hostRule = useHostCheck();
+
+ // Set username to "token" if Auth Mechanism is PAT (3)
+ useEffect(() => {
+ if (!usingUri && form.getFieldValue("authMechanism") === "3") {
+ form.setFieldsValue({ username: "token" });
+ }
+ }, [usingUri, form.getFieldValue("authMechanism"), form]);
+
+ return (
+
+
+
+
+
+
+ setUsingUri(value === "true")}
+ />
+
+
+
+ {usingUri ? (
+
+ ) : (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )}
+
+
+ );
+};
diff --git a/client/packages/lowcoder/src/pages/datasource/form/datasourceFormRegistry.tsx b/client/packages/lowcoder/src/pages/datasource/form/datasourceFormRegistry.tsx
index 9c2abb6127..af2ceb3b3b 100644
--- a/client/packages/lowcoder/src/pages/datasource/form/datasourceFormRegistry.tsx
+++ b/client/packages/lowcoder/src/pages/datasource/form/datasourceFormRegistry.tsx
@@ -9,6 +9,7 @@ import { GoogleSheetsDatasourceForm } from "./googleSheetsDatasourceForm";
import { DatasourceType } from "@lowcoder-ee/constants/queryConstants";
import { Datasource } from "@lowcoder-ee/constants/datasourceConstants";
import { sqlDatasourceForm } from "./sqlDatasourceForm";
+import { DatabricksDatasourceForm } from "./databricksDatasourceForm";
import { GraphqlDatasourceForm } from "./graphqlDatasourceForm";
import { OracleDatasourceForm } from "./oracleDatasourceForm";
import { DataSourceTypeInfo } from "api/datasourceApi";
@@ -55,4 +56,5 @@ export const DatasourceFormRegistry: Partial = ({ environment, workspaceI
'elasticsearch': '#005571',
'oracle': '#F80000',
'mssql': '#CC2927',
+ 'databricks': '#F55322',
'snowflake': '#29B5E8'
};
diff --git a/client/packages/lowcoder/src/util/bottomResUtils.tsx b/client/packages/lowcoder/src/util/bottomResUtils.tsx
index b2f2baf425..e24bffa66e 100644
--- a/client/packages/lowcoder/src/util/bottomResUtils.tsx
+++ b/client/packages/lowcoder/src/util/bottomResUtils.tsx
@@ -14,6 +14,7 @@ import {
MariaDBIcon,
MongoIcon,
MSSQLIcon,
+ DatabricksIcon,
MysqlIcon,
OptionsApiIcon,
OracleIcon,
@@ -124,6 +125,8 @@ export const getBottomResIcon = (
return ;
case "mssql":
return ;
+ case "databricks":
+ return ;
case "smtp":
return ;
case "oracle":
diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/plugin/service/impl/DatasourceMetaInfoServiceImpl.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/plugin/service/impl/DatasourceMetaInfoServiceImpl.java
index 979ae6b4e4..7c0d79e524 100644
--- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/plugin/service/impl/DatasourceMetaInfoServiceImpl.java
+++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/plugin/service/impl/DatasourceMetaInfoServiceImpl.java
@@ -93,6 +93,14 @@ public class DatasourceMetaInfoServiceImpl implements DatasourceMetaInfoService
.connectionPool(ClientBasedConnectionPool.class)
.build();
+ private static final DatasourceMetaInfo DATABRICKS = DatasourceMetaInfo.builder()
+ .type("databricks")
+ .displayName("Databricks")
+ .pluginExecutorKey("databricks-plugin")
+ .hasStructureInfo(true)
+ .connectionPool(ClientBasedConnectionPool.class)
+ .build();
+
private static final DatasourceMetaInfo ORACLE = DatasourceMetaInfo.builder()
.type("oracle")
.displayName("Oracle")
diff --git a/server/api-service/lowcoder-plugins/databricksPlugin/pom.xml b/server/api-service/lowcoder-plugins/databricksPlugin/pom.xml
new file mode 100644
index 0000000000..633bdd6ab5
--- /dev/null
+++ b/server/api-service/lowcoder-plugins/databricksPlugin/pom.xml
@@ -0,0 +1,111 @@
+
+
+
+
+ org.lowcoder
+ lowcoder-plugins
+ ${revision}
+
+
+ 4.0.0
+ org.lowcoder.plugins
+ databricksPlugin
+ jar
+
+ databricksPlugin
+
+
+ UTF-8
+ 17
+ ${java.version}
+ ${java.version}
+ databricks-plugin
+ org.lowcoder.plugin.databricks.DatabricksPlugin
+ ${revision}
+ service@lowcoder.org
+
+
+
+
+
+ org.lowcoder
+ sqlBasedPlugin
+ compile
+
+
+ com.databricks
+ databricks-jdbc
+ 2.6.36
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.8.2
+ test
+
+
+ org.mockito
+ mockito-core
+ 4.5.1
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.2.4
+
+
+ shade-plugin-jar
+ package
+
+ shade
+
+
+ false
+
+
+
+ ${plugin.id}
+ ${plugin.class}
+ ${plugin.version}
+ ${plugin.provider}
+
+
+
+
+
+
+
+
+ maven-dependency-plugin
+
+
+ copy-dependencies
+ package
+
+ copy-dependencies
+
+
+ runtime
+ ${project.build.directory}/lib
+
+
+
+
+
+ maven-antrun-plugin
+
+
+
+
\ No newline at end of file
diff --git a/server/api-service/lowcoder-plugins/databricksPlugin/src/main/java/org/lowcoder/plugin/databricks/DatabricksConnector.java b/server/api-service/lowcoder-plugins/databricksPlugin/src/main/java/org/lowcoder/plugin/databricks/DatabricksConnector.java
new file mode 100644
index 0000000000..627cbbbbd8
--- /dev/null
+++ b/server/api-service/lowcoder-plugins/databricksPlugin/src/main/java/org/lowcoder/plugin/databricks/DatabricksConnector.java
@@ -0,0 +1,86 @@
+package org.lowcoder.plugin.databricks;
+
+import com.zaxxer.hikari.HikariConfig;
+import org.lowcoder.plugin.databricks.model.*;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Set;
+import java.util.HashSet;
+
+public class DatabricksConnector {
+
+ private static final String JDBC_DRIVER = "com.databricks.client.jdbc.Driver";
+
+ public DatabricksConnector() {
+ super();
+ }
+
+ protected String getJdbcDriver() {
+ return JDBC_DRIVER;
+ }
+
+ protected void setUpConfigs(DatabricksDatasourceConfig databricksDatasourceConfig, HikariConfig config) {
+
+ config.setDriverClassName(JDBC_DRIVER);
+
+ String catalog = databricksDatasourceConfig.getCatalog();
+ String schema = databricksDatasourceConfig.getDatabase();
+ String httpPath = databricksDatasourceConfig.getHttpPath();
+ String password = databricksDatasourceConfig.getPassword();
+ Boolean usingSsl = databricksDatasourceConfig.isUsingSsl();
+ DatabricksAuthMechanism authMechanism = databricksDatasourceConfig.getAuthMechanism();
+ String username = databricksDatasourceConfig.getUsername();
+
+ // Build JDBC URL with schema in path if provided
+ String url = buildJdbcUrl(databricksDatasourceConfig);
+
+ config.setJdbcUrl(url);
+ config.addDataSourceProperty("HttpPath", httpPath);
+ config.addDataSourceProperty("AuthMech", authMechanism.getValue());
+ config.addDataSourceProperty("UID", username);
+ config.addDataSourceProperty("PWD", password);
+ if (catalog != null && !catalog.isEmpty()) {
+ config.addDataSourceProperty("ConnCatalog", catalog);
+ }
+ if (schema != null && !schema.isEmpty()) {
+ config.addDataSourceProperty("ConnSchema", schema);
+ }
+
+ if (usingSsl != null && usingSsl) {
+ config.addDataSourceProperty("ssl", "true");
+ config.addDataSourceProperty("sslmode", "require");
+ } else {
+ config.addDataSourceProperty("ssl", "false");
+ config.addDataSourceProperty("sslmode", "disable");
+ }
+
+ // Readonly is optional, set to false by default
+ config.setReadOnly(false);
+ }
+
+ public Set validateConfig(DatabricksDatasourceConfig connectionConfig) {
+ Set validates = new HashSet<>();
+ if (StringUtils.isBlank(connectionConfig.getHost())) {
+ validates.add("INVALID_HOST_CONFIG");
+ }
+ // Optionally validate other required fields here
+ return validates;
+ }
+
+ private String buildJdbcUrl(DatabricksDatasourceConfig config) {
+ String url;
+ if (config.isUsingUri()) {
+ if (StringUtils.isBlank(config.getUri())) {
+ throw new IllegalArgumentException("JDBC URI must be provided when usingUri is true");
+ }
+ url = config.getUri();
+ } else {
+ if (config.getSchema() != null && !config.getSchema().isEmpty()) {
+ url = String.format("jdbc:databricks://%s:%d/%s", config.getHost(), config.getPort(), config.getSchema());
+ } else {
+ url = String.format("jdbc:databricks://%s:%d", config.getHost(), config.getPort());
+ }
+ }
+ return url;
+ }
+}
diff --git a/server/api-service/lowcoder-plugins/databricksPlugin/src/main/java/org/lowcoder/plugin/databricks/DatabricksPlugin.java b/server/api-service/lowcoder-plugins/databricksPlugin/src/main/java/org/lowcoder/plugin/databricks/DatabricksPlugin.java
new file mode 100644
index 0000000000..cfa49c5c81
--- /dev/null
+++ b/server/api-service/lowcoder-plugins/databricksPlugin/src/main/java/org/lowcoder/plugin/databricks/DatabricksPlugin.java
@@ -0,0 +1,11 @@
+package org.lowcoder.plugin.databricks;
+
+import org.pf4j.Plugin;
+import org.pf4j.PluginWrapper;
+
+public class DatabricksPlugin extends Plugin {
+
+ public DatabricksPlugin(PluginWrapper wrapper) {
+ super(wrapper);
+ }
+}
diff --git a/server/api-service/lowcoder-plugins/databricksPlugin/src/main/java/org/lowcoder/plugin/databricks/DatabricksQueryExecutor.java b/server/api-service/lowcoder-plugins/databricksPlugin/src/main/java/org/lowcoder/plugin/databricks/DatabricksQueryExecutor.java
new file mode 100644
index 0000000000..34895fd9a9
--- /dev/null
+++ b/server/api-service/lowcoder-plugins/databricksPlugin/src/main/java/org/lowcoder/plugin/databricks/DatabricksQueryExecutor.java
@@ -0,0 +1,86 @@
+package org.lowcoder.plugin.databricks;
+
+import static org.lowcoder.plugin.databricks.util.DatabricksStructureParser.parseTableAndColumns;
+import static org.lowcoder.sdk.exception.PluginCommonError.DATASOURCE_GET_STRUCTURE_ERROR;
+import static org.lowcoder.sdk.exception.PluginCommonError.QUERY_ARGUMENT_ERROR;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.lowcoder.plugin.databricks.gui.DatabricksDeleteCommand;
+import org.lowcoder.plugin.databricks.gui.DatabricksInsertCommand;
+import org.lowcoder.plugin.databricks.gui.DatabricksUpdateCommand;
+import org.lowcoder.plugin.databricks.gui.DatabricksBulkInsertCommand;
+import org.lowcoder.plugin.databricks.gui.DatabricksBulkUpdateCommand;
+import org.lowcoder.plugin.databricks.util.DatabricksResultParser;
+import org.lowcoder.plugin.sql.GeneralSqlExecutor;
+import org.lowcoder.plugin.sql.SqlBasedQueryExecutor;
+import org.lowcoder.sdk.exception.PluginException;
+import org.lowcoder.sdk.models.DatasourceStructure;
+import org.lowcoder.sdk.models.DatasourceStructure.Table;
+import org.lowcoder.sdk.plugin.common.sql.SqlBasedDatasourceConnectionConfig;
+import org.lowcoder.sdk.plugin.sqlcommand.GuiSqlCommand;
+import org.pf4j.Extension;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Extension
+public class DatabricksQueryExecutor extends SqlBasedQueryExecutor {
+
+ // PF4J requires a public no-arg constructor
+ public DatabricksQueryExecutor() {
+ super(new GeneralSqlExecutor() {
+ @Override
+ protected List