From 33837ec903ad425851c1016873ba12efbbde82fd Mon Sep 17 00:00:00 2001
From: SwallowGG <1558143046@qq.com>
Date: Mon, 28 Oct 2024 14:42:14 +0800
Subject: [PATCH 1/8] feat:support download task
---
.../chat2db/plugin/clickhouse/builder/ClickHouseSqlBuilder.java | 2 ++
.../chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java | 1 +
.../java/ai/chat2db/plugin/hive/builder/HiveSqlBuilder.java | 2 ++
.../web/api/controller/rdb/converter/RdbWebConverter.java | 2 ++
4 files changed, 7 insertions(+)
diff --git a/chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/builder/ClickHouseSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/builder/ClickHouseSqlBuilder.java
index 9f313c27c..0eef415af 100644
--- a/chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/builder/ClickHouseSqlBuilder.java
+++ b/chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/builder/ClickHouseSqlBuilder.java
@@ -10,6 +10,8 @@
import ai.chat2db.spi.model.TableIndex;
import org.apache.commons.lang3.StringUtils;
+import java.util.List;
+
public class ClickHouseSqlBuilder extends DefaultSqlBuilder {
@Override
diff --git a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java
index 1b1cc2fd6..05f938c3b 100644
--- a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java
+++ b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java
@@ -3,6 +3,7 @@
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;
diff --git a/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/builder/HiveSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/builder/HiveSqlBuilder.java
index 02a761df7..a3ca677dd 100644
--- a/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/builder/HiveSqlBuilder.java
+++ b/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/builder/HiveSqlBuilder.java
@@ -2,6 +2,7 @@
import ai.chat2db.plugin.hive.type.HiveColumnTypeEnum;
import ai.chat2db.plugin.hive.type.HiveIndexTypeEnum;
+import ai.chat2db.spi.SqlBuilder;
import ai.chat2db.spi.jdbc.DefaultSqlBuilder;
import ai.chat2db.spi.model.Database;
import ai.chat2db.spi.model.Table;
@@ -9,6 +10,7 @@
import ai.chat2db.spi.model.TableIndex;
import org.apache.commons.lang3.StringUtils;
+import java.util.List;
public class HiveSqlBuilder extends DefaultSqlBuilder implements SqlBuilder
{
diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java
index f9c529a5b..7881cf93a 100644
--- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java
+++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java
@@ -42,6 +42,8 @@ public abstract class RdbWebConverter {
public abstract DlExecuteParam request2param(DmlRequest request);
+
+ public abstract GroupByParam request2param(GroupByRequest request);
/**
* Parameter conversion
*
From d37fd66981b6d09a63a93692c145149a46c56765 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=B4=BA=E5=96=9C?=
<86969353+shanhexi@users.noreply.github.com>
Date: Mon, 28 Oct 2024 22:40:08 +0800
Subject: [PATCH 2/8] =?UTF-8?q?=E4=BF=AE=E6=94=B9jar?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/release.yml | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 61b492976..c5c31fbdf 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -86,11 +86,7 @@ jobs:
- name: Enable tls1
if: ${{ runner.os == 'Windows' }}
run: |
- # sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "${{ env.JAVA_HOME }}/conf/security/java.security"
- $filePath = "${{ env.JAVA_HOME }}\conf\security\java.security"
- $content = Get-Content $filePath -Raw
- $updatedContent = $content -replace '^(jdk.tls.disabledAlgorithms=)(.*)( TLSv1, TLSv1.1,)(.*)', '$1$2$4'
- $updatedContent | Set-Content $filePath
+ sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "${{ env.JAVA_HOME }}/conf/security/java.security"
shell: pwsh
# java.security open tls1 macOS
From 85b99252f697a4296d94ac9ed143ce4eaac26213 Mon Sep 17 00:00:00 2001
From: SwallowGG <1558143046@qq.com>
Date: Tue, 29 Oct 2024 09:50:57 +0800
Subject: [PATCH 3/8] feat:support download task
---
.github/workflows/release.yml | 34 ++++++++++++++++++++++++----------
1 file changed, 24 insertions(+), 10 deletions(-)
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c5c31fbdf..9f2590a88 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -83,17 +83,31 @@ jobs:
java-package: "jre"
# java.security open tls1 Windows
- - name: Enable tls1
- if: ${{ runner.os == 'Windows' }}
- run: |
- sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "${{ env.JAVA_HOME }}/conf/security/java.security"
- shell: pwsh
-
- # java.security open tls1 macOS
- - name: Enable tls1
- if: ${{ runner.os == 'macOS' }}
+# - name: Enable tls1
+# if: ${{ runner.os == 'Windows' }}
+# run: |
+# sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "${{ env.JAVA_HOME }}/conf/security/java.security"
+# shell: pwsh
+#
+# # java.security open tls1 macOS
+# - name: Enable tls1
+# if: ${{ runner.os == 'macOS' }}
+# run: |
+# sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" $JAVA_HOME/conf/security/java.security
+ # 开放TLS
+ - name: Enable TLS 1.0 and 1.1 in java.security
run: |
- sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" $JAVA_HOME/conf/security/java.security
+ if [ "$RUNNER_OS" = "Windows" ]; then
+ sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security"
+ elif [ "$RUNNER_OS" = "Linux" ]; then
+ sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security"
+ elif [ "$RUNNER_OS" = "macOS" ]; then
+ sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security"
+ fi
+ shell: bash
+ env:
+ RUNNER_OS: ${{ runner.os }}
+ JAVA_HOME: ${{ env.JAVA_HOME }}
# Copy jre Windows
- name: Copy Jre for Windows
From cb4fb975d5660c99b4b7961c2dc2414e1b02f118 Mon Sep 17 00:00:00 2001
From: SwallowGG <1558143046@qq.com>
Date: Tue, 29 Oct 2024 16:25:08 +0800
Subject: [PATCH 4/8] feat:support download task
---
.github/workflows/release.yml | 89 +++++++++++++++--------------------
1 file changed, 37 insertions(+), 52 deletions(-)
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 9f2590a88..3d83f04f2 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -2,14 +2,14 @@
name: Build Client
# Workflow's trigger
-# Pack when creating tags
+# 在创建标签的时候打包
on:
push:
tags:
- v*
# Workflow's jobs
-# A total of 3 computers are required to run
+# 一共需要3台电脑运行
# windows
# macos-latest x86_64
# macos-latest arm64
@@ -31,7 +31,7 @@ jobs:
- name: Check out git repository
uses: actions/checkout@main
- # Obtaining the version number is not supported by workflow, so a plug-in is used.
+ # 获取版本号 workflow不支持 所以用插件
- name: Create version
id: chat2db_version
uses: bhowell2/github-substring-action@1.0.1
@@ -39,13 +39,13 @@ jobs:
value: ${{ github.ref }}
index_of_str: "refs/tags/v"
- # Output basic information
+ # 输出基础信息
- name: Print basic information
run: |
echo "current environment: ${{ env.CHAT2DB_ENVIRONMENT }}"
echo "current version: ${{ steps.chat2db_version.outputs.substring }}"
- # Install jre Windows
+ # 安装jre Windows
- name: Install Jre for Windows
if: ${{ runner.os == 'Windows' }}
uses: actions/setup-java@main
@@ -54,7 +54,7 @@ jobs:
distribution: "temurin"
java-package: "jre"
- # Install jre MacOS X64
+ # 安装jre MacOS X64
- name: Install Jre MacOS X64
if: ${{ runner.os == 'macOS' && matrix.arch == 'x86_64' }}
uses: actions/setup-java@main
@@ -63,7 +63,7 @@ jobs:
distribution: "temurin"
java-package: "jre"
- # Install jre MacOS arm64
+ # 安装jre MacOS arm64
- name: Install Jre MacOS arm64
if: ${{ runner.os == 'macOS' && matrix.arch == 'arm64' }}
uses: actions/setup-java@main
@@ -73,7 +73,7 @@ jobs:
java-package: "jre"
architecture: "aarch64"
- # Install jre Linux
+ # 安装jre Linux
- name: Install Jre for Linux
if: ${{ runner.os == 'Linux' }}
uses: actions/setup-java@main
@@ -82,41 +82,26 @@ jobs:
distribution: "temurin"
java-package: "jre"
- # java.security open tls1 Windows
-# - name: Enable tls1
-# if: ${{ runner.os == 'Windows' }}
-# run: |
-# sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "${{ env.JAVA_HOME }}/conf/security/java.security"
-# shell: pwsh
-#
-# # java.security open tls1 macOS
-# - name: Enable tls1
-# if: ${{ runner.os == 'macOS' }}
-# run: |
-# sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" $JAVA_HOME/conf/security/java.security
- # 开放TLS
- - name: Enable TLS 1.0 and 1.1 in java.security
+ # java.security 开放tls1 Windows
+ - name: Enable tls1
+ if: ${{ runner.os == 'Windows' }}
+ run: |
+ sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "${{ env.JAVA_HOME }}/conf/security/java.security"
+
+ # java.security 开放tls1 macOS
+ - name: Enable tls1
+ if: ${{ runner.os == 'macOS' }}
run: |
- if [ "$RUNNER_OS" = "Windows" ]; then
- sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security"
- elif [ "$RUNNER_OS" = "Linux" ]; then
- sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security"
- elif [ "$RUNNER_OS" = "macOS" ]; then
- sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security"
- fi
- shell: bash
- env:
- RUNNER_OS: ${{ runner.os }}
- JAVA_HOME: ${{ env.JAVA_HOME }}
-
- # Copy jre Windows
+ sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" $JAVA_HOME/conf/security/java.security
+
+ # 复制jre Windows
- name: Copy Jre for Windows
if: ${{ runner.os == 'Windows' }}
run: |
mkdir chat2db-client/static
cp -r "${{ env.JAVA_HOME }}" chat2db-client/static/jre
- # Copy jre macOS
+ # 复制jre macOS
- name: Copy Jre for macOS
if: ${{ runner.os == 'macOS' }}
run: |
@@ -124,7 +109,7 @@ jobs:
cp -r $JAVA_HOME chat2db-client/static/jre
chmod -R 777 chat2db-client/static/jre/
- # Copy jre Linux
+ # 复制jre Linux
- name: Copy Jre for Linux
if: ${{ runner.os == 'Linux' }}
run: |
@@ -132,7 +117,7 @@ jobs:
cp -r $JAVA_HOME chat2db-client/static/jre
chmod -R 777 chat2db-client/static/jre/
- # Install node
+ # 安装node
- name: Install Node.js
uses: actions/setup-node@main
with:
@@ -140,7 +125,7 @@ jobs:
cache: "yarn"
cache-dependency-path: chat2db-client/yarn.lock
- # Install java
+ # 安装java
- name: Install Java and Maven
uses: actions/setup-java@main
with:
@@ -148,7 +133,7 @@ jobs:
distribution: "temurin"
cache: "maven"
- # Build static file information
+ # 构建静态文件信息
- name: Yarn install & build & copy
run: |
cd chat2db-client
@@ -160,7 +145,7 @@ jobs:
yarn
yarn run build
- # Compile server-side java version
+ # 编译服务端java版本
- name: Build Java
run: mvn clean package -B '-Dmaven.test.skip=true' -f chat2db-server/pom.xml
@@ -175,7 +160,7 @@ jobs:
echo -n ${{ steps.chat2db_version.outputs.substring }} > version
cp -r version ./versions/
- # Copy server-side java to the specified location
+ # 复制服务端java 到指定位置
- name: Copy App
run: |
cp chat2db-server/chat2db-server-start/target/chat2db-server-start.jar chat2db-client/versions/${{ steps.chat2db_version.outputs.substring }}/static/
@@ -220,7 +205,7 @@ jobs:
run: |
xcrun notarytool store-credentials "Chat2DB" --apple-id "${{secrets.MAC_APPLE_ID}}" --password "${{secrets.MAC_APPLE_PASSWORD}}" --team-id "${{secrets.MAC_TEAM_ID}}"
xcrun notarytool submit chat2db-client/release/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg --keychain-profile "Chat2DB"
-
+
# macos arm64
- name: Build/release Electron app for MacOS arm64
if: ${{ runner.os == 'macOS' && matrix.arch == 'arm64' }}
@@ -260,14 +245,14 @@ jobs:
args: "-c.extraMetadata.version=${{ steps.chat2db_version.outputs.substring }} --linux"
release: true
- # Prepare the required data Windows
+ # 准备要需要的数据 Windows
- name: Prepare upload for Windows
if: runner.os == 'Windows'
run: |
mkdir oss_temp_file
cp -r chat2db-client/release/*Setup*.exe ./oss_temp_file
- # Prepare the required data MacOS x86_64
+ # 准备要需要的数据 MacOS x86_64
- name: Prepare upload for MacOS x86_64
if: ${{ runner.os == 'macOS' && matrix.arch == 'x86_64' }}
run: |
@@ -280,21 +265,21 @@ jobs:
cd static/ && zip -r chat2db-server-start.zip ./
cp -r chat2db-server-start.zip ../../../../oss_temp_file
- # Prepare the required data MacOS arm64
+ # 准备要需要的数据 MacOS arm64
- name: Prepare upload for MacOS arm64
if: ${{ runner.os == 'macOS' && matrix.arch == 'arm64' }}
run: |
mkdir oss_temp_file
cp -r chat2db-client/release/*.dmg ./oss_temp_file
- # Prepare the required data Linux
+ # 准备要需要的数据 Linux
- name: Prepare upload for Linux
if: runner.os == 'Linux'
run: |
mkdir oss_temp_file
cp -r chat2db-client/release/*.AppImage ./oss_temp_file
- # Upload files to OSS for easy downloading
+ # 把文件上传到OSS 方便下载
- name: Set up oss utils
uses: yizhoumo/setup-ossutil@v1
with:
@@ -306,7 +291,7 @@ jobs:
run: |
ossutil cp -rf --acl=public-read ./oss_temp_file/ oss://chat2db-client/release/${{ steps.chat2db_version.outputs.substring }}/
- # Build completion notification
+ # 构建完成通知
- name: Send dingtalk message for Windows
if: ${{ runner.os == 'Windows' }}
uses: ghostoy/dingtalk-action@master
@@ -319,7 +304,7 @@ jobs:
"text": "# Windows-release-打包完成通知 \n  \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Windows下载地址:[https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB%20Setup%20${{ steps.chat2db_version.outputs.substring }}.exe](https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB%20Setup%20${{ steps.chat2db_version.outputs.substring }}.exe) "
}
- # Build completion notification
+ # 构建完成通知
- name: Send dingtalk message for MacOS x86_64
if: ${{ runner.os == 'macOS' && matrix.arch == 'x86_64' }}
uses: ghostoy/dingtalk-action@master
@@ -332,7 +317,7 @@ jobs:
"text": "# MacOS-x86_64-release-打包完成通知 \n  \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Intel芯片下载地址:[https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg](https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg) \n ### jar包下载地址:[https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/chat2db-server-start.zip](https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/chat2db-server-start.zip) "
}
- # Build completion notification
+ # 构建完成通知
- name: Send dingtalk message for MacOS arm64
if: ${{ runner.os == 'macOS' && matrix.arch == 'arm64' }}
uses: ghostoy/dingtalk-action@master
@@ -345,7 +330,7 @@ jobs:
"text": "# MacOS-arm64-release-打包完成通知 \n  \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Apple芯片下载地址:[https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}-arm64.dmg](https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}-arm64.dmg) "
}
- # Build completion notification
+ # 构建完成通知
- name: Send dingtalk message for Linux
if: ${{ runner.os == 'Linux' }}
uses: ghostoy/dingtalk-action@master
From ecd350d41e682065dfd63425cbdda3eea10d38d3 Mon Sep 17 00:00:00 2001
From: SwallowGG <1558143046@qq.com>
Date: Tue, 29 Oct 2024 17:09:42 +0800
Subject: [PATCH 5/8] feat:support download task
---
.github/workflows/release.yml | 66 +++++++++++++++++++----------------
1 file changed, 36 insertions(+), 30 deletions(-)
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 3d83f04f2..cca88c92a 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -2,14 +2,14 @@
name: Build Client
# Workflow's trigger
-# 在创建标签的时候打包
+# Pack when creating tags
on:
push:
tags:
- v*
# Workflow's jobs
-# 一共需要3台电脑运行
+# A total of 3 computers are required to run
# windows
# macos-latest x86_64
# macos-latest arm64
@@ -31,7 +31,7 @@ jobs:
- name: Check out git repository
uses: actions/checkout@main
- # 获取版本号 workflow不支持 所以用插件
+ # Obtaining the version number is not supported by workflow, so a plug-in is used.
- name: Create version
id: chat2db_version
uses: bhowell2/github-substring-action@1.0.1
@@ -39,13 +39,13 @@ jobs:
value: ${{ github.ref }}
index_of_str: "refs/tags/v"
- # 输出基础信息
+ # Output basic information
- name: Print basic information
run: |
echo "current environment: ${{ env.CHAT2DB_ENVIRONMENT }}"
echo "current version: ${{ steps.chat2db_version.outputs.substring }}"
- # 安装jre Windows
+ # Install jre Windows
- name: Install Jre for Windows
if: ${{ runner.os == 'Windows' }}
uses: actions/setup-java@main
@@ -54,7 +54,7 @@ jobs:
distribution: "temurin"
java-package: "jre"
- # 安装jre MacOS X64
+ # Install jre MacOS X64
- name: Install Jre MacOS X64
if: ${{ runner.os == 'macOS' && matrix.arch == 'x86_64' }}
uses: actions/setup-java@main
@@ -62,8 +62,9 @@ jobs:
java-version: "17"
distribution: "temurin"
java-package: "jre"
+ architecture: "x64"
- # 安装jre MacOS arm64
+ # Install jre MacOS arm64
- name: Install Jre MacOS arm64
if: ${{ runner.os == 'macOS' && matrix.arch == 'arm64' }}
uses: actions/setup-java@main
@@ -73,7 +74,7 @@ jobs:
java-package: "jre"
architecture: "aarch64"
- # 安装jre Linux
+ # Install jre Linux
- name: Install Jre for Linux
if: ${{ runner.os == 'Linux' }}
uses: actions/setup-java@main
@@ -82,26 +83,31 @@ jobs:
distribution: "temurin"
java-package: "jre"
- # java.security 开放tls1 Windows
+ # java.security open tls1 Windows
- name: Enable tls1
if: ${{ runner.os == 'Windows' }}
run: |
- sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "${{ env.JAVA_HOME }}/conf/security/java.security"
-
- # java.security 开放tls1 macOS
+ # sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "${{ env.JAVA_HOME }}\conf\security\java.security"
+ $filePath = "${{ env.JAVA_HOME }}\conf\security\java.security"
+ $content = Get-Content $filePath -Raw
+ $updatedContent = $content -replace '^(jdk.tls.disabledAlgorithms=)(.*)( TLSv1, TLSv1.1,)(.*)', '$1$2$4'
+ $updatedContent | Set-Content $filePath
+ shell: pwsh
+
+ # java.security open tls1 macOS
- name: Enable tls1
if: ${{ runner.os == 'macOS' }}
run: |
sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" $JAVA_HOME/conf/security/java.security
- # 复制jre Windows
+ # Copy jre Windows
- name: Copy Jre for Windows
if: ${{ runner.os == 'Windows' }}
run: |
mkdir chat2db-client/static
cp -r "${{ env.JAVA_HOME }}" chat2db-client/static/jre
- # 复制jre macOS
+ # Copy jre macOS
- name: Copy Jre for macOS
if: ${{ runner.os == 'macOS' }}
run: |
@@ -109,7 +115,7 @@ jobs:
cp -r $JAVA_HOME chat2db-client/static/jre
chmod -R 777 chat2db-client/static/jre/
- # 复制jre Linux
+ # Copy jre Linux
- name: Copy Jre for Linux
if: ${{ runner.os == 'Linux' }}
run: |
@@ -117,7 +123,7 @@ jobs:
cp -r $JAVA_HOME chat2db-client/static/jre
chmod -R 777 chat2db-client/static/jre/
- # 安装node
+ # Install node
- name: Install Node.js
uses: actions/setup-node@main
with:
@@ -125,7 +131,7 @@ jobs:
cache: "yarn"
cache-dependency-path: chat2db-client/yarn.lock
- # 安装java
+ # Install java
- name: Install Java and Maven
uses: actions/setup-java@main
with:
@@ -133,7 +139,7 @@ jobs:
distribution: "temurin"
cache: "maven"
- # 构建静态文件信息
+ # Build static file information
- name: Yarn install & build & copy
run: |
cd chat2db-client
@@ -145,7 +151,7 @@ jobs:
yarn
yarn run build
- # 编译服务端java版本
+ # Compile server-side java version
- name: Build Java
run: mvn clean package -B '-Dmaven.test.skip=true' -f chat2db-server/pom.xml
@@ -160,7 +166,7 @@ jobs:
echo -n ${{ steps.chat2db_version.outputs.substring }} > version
cp -r version ./versions/
- # 复制服务端java 到指定位置
+ # Copy server-side java to the specified location
- name: Copy App
run: |
cp chat2db-server/chat2db-server-start/target/chat2db-server-start.jar chat2db-client/versions/${{ steps.chat2db_version.outputs.substring }}/static/
@@ -245,14 +251,14 @@ jobs:
args: "-c.extraMetadata.version=${{ steps.chat2db_version.outputs.substring }} --linux"
release: true
- # 准备要需要的数据 Windows
+ # Prepare the required data Windows
- name: Prepare upload for Windows
if: runner.os == 'Windows'
run: |
mkdir oss_temp_file
cp -r chat2db-client/release/*Setup*.exe ./oss_temp_file
- # 准备要需要的数据 MacOS x86_64
+ # Prepare the required data MacOS x86_64
- name: Prepare upload for MacOS x86_64
if: ${{ runner.os == 'macOS' && matrix.arch == 'x86_64' }}
run: |
@@ -265,21 +271,21 @@ jobs:
cd static/ && zip -r chat2db-server-start.zip ./
cp -r chat2db-server-start.zip ../../../../oss_temp_file
- # 准备要需要的数据 MacOS arm64
+ # Prepare the required data MacOS arm64
- name: Prepare upload for MacOS arm64
if: ${{ runner.os == 'macOS' && matrix.arch == 'arm64' }}
run: |
mkdir oss_temp_file
cp -r chat2db-client/release/*.dmg ./oss_temp_file
- # 准备要需要的数据 Linux
+ # Prepare the required data Linux
- name: Prepare upload for Linux
if: runner.os == 'Linux'
run: |
mkdir oss_temp_file
cp -r chat2db-client/release/*.AppImage ./oss_temp_file
- # 把文件上传到OSS 方便下载
+ # Upload files to OSS for easy downloading
- name: Set up oss utils
uses: yizhoumo/setup-ossutil@v1
with:
@@ -291,7 +297,7 @@ jobs:
run: |
ossutil cp -rf --acl=public-read ./oss_temp_file/ oss://chat2db-client/release/${{ steps.chat2db_version.outputs.substring }}/
- # 构建完成通知
+ # Build completion notification
- name: Send dingtalk message for Windows
if: ${{ runner.os == 'Windows' }}
uses: ghostoy/dingtalk-action@master
@@ -304,7 +310,7 @@ jobs:
"text": "# Windows-release-打包完成通知 \n  \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Windows下载地址:[https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB%20Setup%20${{ steps.chat2db_version.outputs.substring }}.exe](https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB%20Setup%20${{ steps.chat2db_version.outputs.substring }}.exe) "
}
- # 构建完成通知
+ # Build completion notification
- name: Send dingtalk message for MacOS x86_64
if: ${{ runner.os == 'macOS' && matrix.arch == 'x86_64' }}
uses: ghostoy/dingtalk-action@master
@@ -317,7 +323,7 @@ jobs:
"text": "# MacOS-x86_64-release-打包完成通知 \n  \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Intel芯片下载地址:[https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg](https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg) \n ### jar包下载地址:[https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/chat2db-server-start.zip](https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/chat2db-server-start.zip) "
}
- # 构建完成通知
+ # Build completion notification
- name: Send dingtalk message for MacOS arm64
if: ${{ runner.os == 'macOS' && matrix.arch == 'arm64' }}
uses: ghostoy/dingtalk-action@master
@@ -330,7 +336,7 @@ jobs:
"text": "# MacOS-arm64-release-打包完成通知 \n  \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Apple芯片下载地址:[https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}-arm64.dmg](https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}-arm64.dmg) "
}
- # 构建完成通知
+ # Build completion notification
- name: Send dingtalk message for Linux
if: ${{ runner.os == 'Linux' }}
uses: ghostoy/dingtalk-action@master
@@ -341,4 +347,4 @@ jobs:
{
"title": "Linux-test-打包完成通知",
"text": "# Linux-test-打包完成通知 \n  \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Linux下载地址:[https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.AppImage](https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.AppImage)"
- }
+ }
\ No newline at end of file
From d383444213d5510b9e46294ec044ef36f676da3d Mon Sep 17 00:00:00 2001
From: SwallowGG <1558143046@qq.com>
Date: Tue, 29 Oct 2024 17:33:01 +0800
Subject: [PATCH 6/8] delete demo data error
---
.../main/resources/db/migration/V2_1_10__REMOVEdEMO.sql | 7 +++++++
1 file changed, 7 insertions(+)
create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_10__REMOVEdEMO.sql
diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_10__REMOVEdEMO.sql b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_10__REMOVEdEMO.sql
new file mode 100644
index 000000000..b0f5f6300
--- /dev/null
+++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_10__REMOVEdEMO.sql
@@ -0,0 +1,7 @@
+delete from DATA_SOURCE where ALIAS ='DEMO@db.sqlgpt.cn';
+
+delete from DASHBOARD where id =ID;
+
+delete from CHART where id<=3;
+
+delete from DASHBOARD_CHART_RELATION where CHART_ID<=3;
From aca27de139fe97e76636c86ebc131df09614263b Mon Sep 17 00:00:00 2001
From: tmlx1990
Date: Thu, 21 Nov 2024 12:37:20 +0800
Subject: [PATCH 7/8] add duckdb
---
.../ConnectionEdit/config/dataSource.ts | 40 ++
.../components/ConnectionEdit/config/enum.ts | 1 +
chat2db-client/src/constants/common.ts | 1 +
chat2db-client/src/constants/database.ts | 7 +
.../chat2db-plugins/chat2db-duckdb/pom.xml | 35 ++
.../chat2db/plugin/duckdb/DuckDBManage.java | 253 +++++++++++
.../chat2db/plugin/duckdb/DuckDBMetaData.java | 360 +++++++++++++++
.../chat2db/plugin/duckdb/DuckDBPlugin.java | 25 ++
.../duckdb/builder/DuckDBSqlBuilder.java | 424 ++++++++++++++++++
.../chat2db/plugin/duckdb/builder/form.json | 45 ++
.../java/ai/chat2db/plugin/duckdb/duckDB.json | 18 +
.../duckdb/type/DuckDBColumnTypeEnum.java | 316 +++++++++++++
.../duckdb/type/DuckDBDefaultValueEnum.java | 27 ++
.../duckdb/type/DuckDBIndexTypeEnum.java | 129 ++++++
.../META-INF/services/ai.chat2db.spi.Plugin | 1 +
chat2db-server/chat2db-plugins/pom.xml | 1 +
.../chat2db-server-domain-core/pom.xml | 5 +
17 files changed, 1688 insertions(+)
create mode 100644 chat2db-server/chat2db-plugins/chat2db-duckdb/pom.xml
create mode 100644 chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/DuckDBManage.java
create mode 100644 chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/DuckDBMetaData.java
create mode 100644 chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/DuckDBPlugin.java
create mode 100644 chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/builder/DuckDBSqlBuilder.java
create mode 100644 chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/builder/form.json
create mode 100644 chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/duckDB.json
create mode 100644 chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/type/DuckDBColumnTypeEnum.java
create mode 100644 chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/type/DuckDBDefaultValueEnum.java
create mode 100644 chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/type/DuckDBIndexTypeEnum.java
create mode 100644 chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin
diff --git a/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts b/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts
index c95ca70ef..ec9ec7486 100644
--- a/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts
+++ b/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts
@@ -2104,4 +2104,44 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [
},
ssh: sshConfig,
},
+ // DUCKDB
+ {
+ type: DatabaseTypeCode.DUCKDB,
+ baseInfo: {
+ items: [
+ {
+ defaultValue: '@localhost',
+ inputType: InputType.INPUT,
+ labelNameCN: '名称',
+ labelNameEN: 'Name',
+ name: 'alias',
+ required: true,
+ },
+ envItem,
+ {
+ defaultValue: 'localhost',
+ inputType: InputType.INPUT,
+ labelNameCN: '路径',
+ labelNameEN: 'Host',
+ name: 'host',
+ required: true,
+ styles: {
+ width: '70%',
+ },
+ },
+ {
+ defaultValue: 'jdbc:duckdb:{filePath}',
+ inputType: InputType.INPUT,
+ labelNameCN: 'URL',
+ labelNameEN: 'URL',
+ name: 'url',
+ required: true,
+ },
+ ],
+ pattern: /jdbc:duckdb:\/\/(\w+)/,
+ template: 'jdbc:duckdb://{host}',
+ excludes: [OperationColumn.ViewDDL, OperationColumn.CreateTable, OperationColumn.EditTable],
+ },
+ ssh: sshConfig,
+ },
];
diff --git a/chat2db-client/src/components/ConnectionEdit/config/enum.ts b/chat2db-client/src/components/ConnectionEdit/config/enum.ts
index 6c0e425cf..e66e37e93 100644
--- a/chat2db-client/src/components/ConnectionEdit/config/enum.ts
+++ b/chat2db-client/src/components/ConnectionEdit/config/enum.ts
@@ -2,6 +2,7 @@ export enum InputType {
INPUT = 'input',
PASSWORD = 'password',
SELECT = 'select',
+ FILE = 'file',
}
export enum AuthenticationType {
diff --git a/chat2db-client/src/constants/common.ts b/chat2db-client/src/constants/common.ts
index 44824008d..8ea26bcbf 100644
--- a/chat2db-client/src/constants/common.ts
+++ b/chat2db-client/src/constants/common.ts
@@ -16,6 +16,7 @@ export enum DatabaseTypeCode {
HIVE = 'HIVE',
KINGBASE = 'KINGBASE',
TIMEPLUS = 'TIMEPLUS',
+ DUCKDB = 'DUCKDB',
}
export enum ConsoleStatus {
diff --git a/chat2db-client/src/constants/database.ts b/chat2db-client/src/constants/database.ts
index e586cb883..7f174ed5e 100644
--- a/chat2db-client/src/constants/database.ts
+++ b/chat2db-client/src/constants/database.ts
@@ -125,6 +125,13 @@ export const databaseMap: {
// port: 8123,
icon: '\ue8f4',
},
+ [DatabaseTypeCode.DUCKDB]: {
+ name: 'DuckDB',
+ img: moreDBLogo,
+ code: DatabaseTypeCode.DUCKDB,
+ // port: 8123,
+ icon: '\ue8f4',
+ },
// [DatabaseTypeCode.REDIS]: {
// name: 'Redis',
// img: moreDBLogo,
diff --git a/chat2db-server/chat2db-plugins/chat2db-duckdb/pom.xml b/chat2db-server/chat2db-plugins/chat2db-duckdb/pom.xml
new file mode 100644
index 000000000..7eddf5958
--- /dev/null
+++ b/chat2db-server/chat2db-plugins/chat2db-duckdb/pom.xml
@@ -0,0 +1,35 @@
+
+
+
+
+ ai.chat2db
+ chat2db-plugins
+ ${revision}
+ ../pom.xml
+
+ 4.0.0
+ chat2db-duckdb
+
+
+
+ ai.chat2db
+ chat2db-spi
+
+
+
+
+
+ src/main/java
+
+
+ **/*.json
+
+
+
+ src/main/resources
+
+
+
+
\ No newline at end of file
diff --git a/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/DuckDBManage.java b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/DuckDBManage.java
new file mode 100644
index 000000000..2014da51b
--- /dev/null
+++ b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/DuckDBManage.java
@@ -0,0 +1,253 @@
+package ai.chat2db.plugin.duckdb;
+
+import ai.chat2db.spi.DBManage;
+import ai.chat2db.spi.jdbc.DefaultDBManage;
+import ai.chat2db.spi.model.AsyncContext;
+import ai.chat2db.spi.model.Function;
+import ai.chat2db.spi.model.Procedure;
+import ai.chat2db.spi.sql.SQLExecutor;
+import cn.hutool.core.date.DateUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.StringUtils;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Date;
+
+import static cn.hutool.core.date.DatePattern.NORM_DATETIME_PATTERN;
+
+@Slf4j
+public class DuckDBManage extends DefaultDBManage implements DBManage {
+
+ private static String PROCEDURE_SQL = "SELECT COUNT(*) FROM INFORMATION_SCHEMA.ROUTINES " +
+ "WHERE ROUTINE_SCHEMA = '%s' AND ROUTINE_NAME = '%s' AND ROUTINE_TYPE = 'PROCEDURE'";
+
+ @Override
+ public void exportDatabase(Connection connection, String databaseName, String schemaName, AsyncContext asyncContext) throws SQLException {
+ asyncContext.write(String.format(EXPORT_TITLE, DateUtil.format(new Date(), NORM_DATETIME_PATTERN)));
+ exportTables(connection, databaseName, schemaName, asyncContext);
+ asyncContext.setProgress(50);
+ exportViews(connection, databaseName, asyncContext);
+ asyncContext.setProgress(60);
+ exportProcedures(connection, asyncContext);
+ asyncContext.setProgress(70);
+ exportTriggers(connection, asyncContext);
+ asyncContext.setProgress(90);
+ exportFunctions(connection, databaseName, asyncContext);
+ asyncContext.finish();
+ }
+
+ private void exportFunctions(Connection connection, String databaseName, AsyncContext asyncContext) throws SQLException {
+ try (ResultSet resultSet = connection.getMetaData().getFunctions(databaseName, null, null)) {
+ while (resultSet.next()) {
+ exportFunction(connection, resultSet.getString("FUNCTION_NAME"), asyncContext);
+ }
+
+ }
+ }
+
+ private void exportFunction(Connection connection, String functionName, AsyncContext asyncContext) throws SQLException {
+ String sql = String.format("SHOW CREATE FUNCTION %s;", functionName);
+ try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) {
+ if (resultSet.next()) {
+ asyncContext.write(String.format(FUNCTION_TITLE, functionName));
+ StringBuilder sqlBuilder = new StringBuilder();
+ sqlBuilder.append("DROP FUNCTION IF EXISTS ").append(functionName).append(";").append("\n");
+
+ sqlBuilder.append("delimiter ;;").append("\n").append(resultSet.getString("Create Function")).append(";;")
+ .append("\n").append("delimiter ;").append("\n\n");
+ asyncContext.write(sqlBuilder.toString());
+ }
+ }
+ }
+
+ private void exportTables(Connection connection, String databaseName, String schemaName, AsyncContext asyncContext) throws SQLException {
+ asyncContext.write("SET FOREIGN_KEY_CHECKS=0;");
+ try (ResultSet resultSet = connection.getMetaData().getTables(databaseName, null, null, new String[]{"TABLE", "SYSTEM TABLE"})) {
+ while (resultSet.next()) {
+ String tableName = resultSet.getString("TABLE_NAME");
+ exportTable(connection, databaseName, schemaName, tableName, asyncContext);
+ }
+ }
+ asyncContext.write("SET FOREIGN_KEY_CHECKS=1;");
+ }
+
+
+ public void exportTable(Connection connection, String databaseName, String schemaName, String tableName, AsyncContext asyncContext) throws SQLException {
+ String sql = String.format("show create table %s ", tableName);
+ try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) {
+ if (resultSet.next()) {
+ StringBuilder sqlBuilder = new StringBuilder();
+ asyncContext.write(String.format(TABLE_TITLE, tableName));
+ sqlBuilder.append("DROP TABLE IF EXISTS ").append(format(tableName)).append(";").append("\n")
+ .append(resultSet.getString("Create Table")).append(";").append("\n");
+ asyncContext.write(sqlBuilder.toString());
+ if (asyncContext.isContainsData()) {
+ exportTableData(connection, databaseName, schemaName, tableName, asyncContext);
+ }
+ }
+ } catch (Exception e) {
+ log.error("export table error", e);
+ asyncContext.error(String.format("export table %s error:%s", tableName, e.getMessage()));
+ }
+ }
+
+
+ private void exportViews(Connection connection, String databaseName, AsyncContext asyncContext) throws SQLException {
+ try (ResultSet resultSet = connection.getMetaData().getTables(databaseName, null, null, new String[]{"VIEW"})) {
+ while (resultSet.next()) {
+ exportView(connection, resultSet.getString("TABLE_NAME"), asyncContext);
+ }
+ }
+ }
+
+ private void exportView(Connection connection, String viewName, AsyncContext asyncContext) throws SQLException {
+ String sql = String.format("show create view %s ", viewName);
+ try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) {
+ if (resultSet.next()) {
+ asyncContext.write(String.format(VIEW_TITLE, viewName));
+ StringBuilder sqlBuilder = new StringBuilder();
+ sqlBuilder.append("DROP VIEW IF EXISTS ").append(format(viewName)).append(";").append("\n")
+ .append(resultSet.getString("Create View")).append(";").append("\n\n");
+ asyncContext.write(sqlBuilder.toString());
+ }
+ }
+ }
+
+ private void exportProcedures(Connection connection, AsyncContext asyncContext) throws SQLException {
+ String sql = "SHOW PROCEDURE STATUS WHERE Db = DATABASE()";
+ try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) {
+ while (resultSet.next()) {
+ exportProcedure(connection, resultSet.getString("Name"), asyncContext);
+ }
+ }
+ }
+
+ private void exportProcedure(Connection connection, String procedureName, AsyncContext asyncContext) throws SQLException {
+ String sql = String.format("show create procedure %s ", procedureName);
+ try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) {
+ if (resultSet.next()) {
+ asyncContext.write(String.format(PROCEDURE_TITLE, procedureName));
+ StringBuilder sqlBuilder = new StringBuilder();
+ sqlBuilder.append("DROP PROCEDURE IF EXISTS ").append(format(procedureName)).append(";").append("\n")
+ .append("delimiter ;;").append("\n").append(resultSet.getString("Create Procedure")).append(";;")
+ .append("\n").append("delimiter ;").append("\n\n");
+ asyncContext.write(sqlBuilder.toString());
+ }
+ }
+ }
+
+ private void exportTriggers(Connection connection, AsyncContext asyncContext) throws SQLException {
+ String sql = "SHOW TRIGGERS";
+ try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) {
+ while (resultSet.next()) {
+ String triggerName = resultSet.getString("Trigger");
+ exportTrigger(connection, triggerName, asyncContext);
+ }
+ }
+ }
+
+ private void exportTrigger(Connection connection, String triggerName, AsyncContext asyncContext) throws SQLException {
+ String sql = String.format("show create trigger %s ", triggerName);
+ try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) {
+ if (resultSet.next()) {
+ asyncContext.write(String.format(TRIGGER_TITLE, triggerName));
+ StringBuilder sqlBuilder = new StringBuilder();
+ sqlBuilder.append("DROP TRIGGER IF EXISTS ").append(format(triggerName)).append(";").append("\n")
+ .append("delimiter ;;").append("\n").append(resultSet.getString("SQL Original Statement")).append(";;")
+ .append("\n").append("delimiter ;").append("\n\n");
+ asyncContext.write(sqlBuilder.toString());
+ }
+ }
+ }
+
+ @Override
+ public void updateProcedure(Connection connection, String databaseName, String schemaName, Procedure procedure) throws SQLException {
+ try {
+ connection.setAutoCommit(false);
+ String procedureBody = procedure.getProcedureBody();
+ if (procedureBody == null || !procedureBody.trim().toUpperCase().startsWith("CREATE")) {
+ throw new IllegalArgumentException("No CREATE statement found.");
+ }
+
+ String procedureNewName = getSchemaOrProcedureName(procedureBody, databaseName, procedure);
+ if (!procedureNewName.equals(procedure.getProcedureName())) {
+ procedureBody = procedureBody.replace(procedure.getProcedureName(), procedureNewName);
+ }
+ String checkProcedureSQL = String.format(PROCEDURE_SQL, databaseName, procedure.getProcedureName());
+ SQLExecutor.getInstance().execute(connection, checkProcedureSQL, resultSet -> {
+ try {
+ if (resultSet.next()) {
+ int count = resultSet.getInt(1);
+ if (count >= 1) {
+ throw new Exception("Procedure already exists");
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ });
+ SQLExecutor.getInstance().execute(connection, procedureBody, resultSet -> {});
+ } catch (Exception e) {
+ connection.rollback();
+ throw new RuntimeException(e);
+ } finally {
+ connection.setAutoCommit(true);
+ }
+
+ }
+
+ @Override
+ public void connectDatabase(Connection connection, String database) {
+ if (StringUtils.isEmpty(database)) {
+ return;
+ }
+ try {
+ SQLExecutor.getInstance().execute(connection, "use `" + database + "`;");
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ @Override
+ public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) {
+ String sql = "DROP TABLE " + format(tableName);
+ SQLExecutor.getInstance().execute(connection, sql, resultSet -> null);
+ }
+
+ @Override
+ public void deleteProcedure(Connection connection, String databaseName, String schemaName, Procedure procedure) {
+ String procedureNewName = getSchemaOrProcedureName(procedure.getProcedureBody(), databaseName, procedure);
+ String sql = "DROP PROCEDURE " + procedureNewName;
+ SQLExecutor.getInstance().execute(connection, sql, resultSet -> null);
+ }
+
+ @Override
+ public void deleteFunction(Connection connection, String databaseName, String schemaName, Function function) {
+ String functionNewName = getSchemaOrFunctionName(function.getFunctionBody(), databaseName, function);
+ String sql = "DROP FUNCTION " + functionNewName;
+ SQLExecutor.getInstance().execute(connection, sql, resultSet -> null);
+ }
+
+ private static String getSchemaOrProcedureName(String procedureBody, String schemaName, Procedure procedure) {
+ if (procedureBody.toLowerCase().contains(schemaName.toLowerCase())) {
+ return procedure.getProcedureName();
+ } else {
+ return schemaName + "." + procedure.getProcedureName();
+ }
+ }
+
+ private static String getSchemaOrFunctionName(String functionBody, String schemaName, Function function) {
+ if (functionBody.toLowerCase().contains(schemaName.toLowerCase())) {
+ return function.getFunctionName();
+ } else {
+ return schemaName + "." + function.getFunctionName();
+ }
+ }
+
+ public static String format(String tableName) {
+ return "`" + tableName + "`";
+ }
+}
diff --git a/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/DuckDBMetaData.java b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/DuckDBMetaData.java
new file mode 100644
index 000000000..1e0bfb194
--- /dev/null
+++ b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/DuckDBMetaData.java
@@ -0,0 +1,360 @@
+package ai.chat2db.plugin.duckdb;
+
+import ai.chat2db.plugin.duckdb.builder.DuckDBSqlBuilder;
+import ai.chat2db.plugin.duckdb.type.DuckDBColumnTypeEnum;
+import ai.chat2db.plugin.duckdb.type.DuckDBDefaultValueEnum;
+import ai.chat2db.plugin.duckdb.type.DuckDBIndexTypeEnum;
+import ai.chat2db.spi.MetaData;
+import ai.chat2db.spi.SqlBuilder;
+import ai.chat2db.spi.jdbc.DefaultMetaService;
+import ai.chat2db.spi.model.*;
+import ai.chat2db.spi.sql.SQLExecutor;
+import jakarta.validation.constraints.NotEmpty;
+import org.apache.commons.lang3.StringUtils;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static ai.chat2db.spi.util.SortUtils.sortDatabase;
+
+public class DuckDBMetaData extends DefaultMetaService implements MetaData {
+
+ private List systemDatabases = Arrays.asList("information_schema", "performance_schema", "mysql", "sys");
+
+ @Override
+ public List databases(Connection connection) {
+ List databases = SQLExecutor.getInstance().databases(connection);
+ return sortDatabase(databases, systemDatabases, connection);
+ }
+
+
+ private static String TABLES_SQL
+ = "SELECT TABLE_SCHEMA, TABLE_NAME, ENGINE, VERSION, TABLE_ROWS, DATA_LENGTH, AUTO_INCREMENT, CREATE_TIME, UPDATE_TIME, TABLE_COLLATION, TABLE_COMMENT FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA = '%s'";
+ @Override
+ public List tables(Connection connection, @NotEmpty String databaseName, String schemaName, String tableName) {
+ String sql = String.format(TABLES_SQL, databaseName);
+ if(StringUtils.isNotBlank(tableName)){
+ sql += " AND TABLE_NAME = '" + tableName + "'";
+ }
+ return SQLExecutor.getInstance().execute(connection, sql, resultSet -> {
+ List tables = new ArrayList<>();
+ while (resultSet.next()) {
+ Table table = new Table();
+ table.setDatabaseName(databaseName);
+ table.setSchemaName(schemaName);
+ table.setName(resultSet.getString("TABLE_NAME"));
+ table.setEngine(resultSet.getString("ENGINE"));
+ table.setRows(resultSet.getLong("TABLE_ROWS"));
+ table.setDataLength(resultSet.getLong("DATA_LENGTH"));
+ table.setCreateTime(resultSet.getString("CREATE_TIME"));
+ table.setUpdateTime(resultSet.getString("UPDATE_TIME"));
+ table.setCollate(resultSet.getString("TABLE_COLLATION"));
+ table.setComment(resultSet.getString("TABLE_COMMENT"));
+ tables.add(table);
+ }
+ return tables;
+ });
+ }
+
+
+ @Override
+ public String tableDDL(Connection connection, @NotEmpty String databaseName, String schemaName,
+ @NotEmpty String tableName) {
+ String sql = "SHOW CREATE TABLE " + format(databaseName) + "."
+ + format(tableName);
+ return SQLExecutor.getInstance().execute(connection, sql, resultSet -> {
+ if (resultSet.next()) {
+ return resultSet.getString("Create Table");
+ }
+ return null;
+ });
+ }
+
+ public static String format(String tableName) {
+ return "`" + tableName + "`";
+ }
+
+ private static String ROUTINES_SQL
+ =
+ "SELECT SPECIFIC_NAME, ROUTINE_COMMENT, ROUTINE_DEFINITION FROM information_schema.routines WHERE "
+ + "routine_type = '%s' AND ROUTINE_SCHEMA ='%s' AND "
+ + "routine_name = '%s';";
+
+ @Override
+ public Function function(Connection connection, @NotEmpty String databaseName, String schemaName,
+ String functionName) {
+
+ String functionInfoSql = String.format(ROUTINES_SQL, "FUNCTION", databaseName, functionName);
+ Function function = SQLExecutor.getInstance().execute(connection, functionInfoSql, resultSet -> {
+ Function f = new Function();
+ f.setDatabaseName(databaseName);
+ f.setSchemaName(schemaName);
+ f.setFunctionName(functionName);
+ if (resultSet.next()) {
+ f.setSpecificName(resultSet.getString("SPECIFIC_NAME"));
+ f.setRemarks(resultSet.getString("ROUTINE_COMMENT"));
+ }
+ return f;
+ });
+ String functionDDlSql = String.format("SHOW CREATE FUNCTION %s", functionName);
+ SQLExecutor.getInstance().execute(connection, functionDDlSql, resultSet -> {
+ if (resultSet.next()) {
+ function.setFunctionBody(resultSet.getString("Create Function"));
+ }
+ });
+ return function;
+
+ }
+
+ private static String TRIGGER_SQL
+ = "SELECT TRIGGER_NAME,EVENT_MANIPULATION, ACTION_STATEMENT FROM INFORMATION_SCHEMA.TRIGGERS where "
+ + "TRIGGER_SCHEMA = '%s' AND TRIGGER_NAME = '%s';";
+
+ private static String TRIGGER_SQL_LIST
+ = "SELECT TRIGGER_NAME FROM INFORMATION_SCHEMA.TRIGGERS where TRIGGER_SCHEMA = '%s';";
+
+ @Override
+ public List triggers(Connection connection, String databaseName, String schemaName) {
+ List triggers = new ArrayList<>();
+ String sql = String.format(TRIGGER_SQL_LIST, databaseName);
+ return SQLExecutor.getInstance().execute(connection, sql, resultSet -> {
+ while (resultSet.next()) {
+ Trigger trigger = new Trigger();
+ trigger.setTriggerName(resultSet.getString("TRIGGER_NAME"));
+ trigger.setSchemaName(schemaName);
+ trigger.setDatabaseName(databaseName);
+ triggers.add(trigger);
+ }
+ return triggers;
+ });
+ }
+
+
+ @Override
+ public Trigger trigger(Connection connection, @NotEmpty String databaseName, String schemaName,
+ String triggerName) {
+
+ String sql = String.format(TRIGGER_SQL, databaseName, triggerName);
+ return SQLExecutor.getInstance().execute(connection, sql, resultSet -> {
+ Trigger trigger = new Trigger();
+ trigger.setDatabaseName(databaseName);
+ trigger.setSchemaName(schemaName);
+ trigger.setTriggerName(triggerName);
+ if (resultSet.next()) {
+ trigger.setTriggerBody(resultSet.getString("ACTION_STATEMENT"));
+ }
+ return trigger;
+ });
+ }
+
+ @Override
+ public List procedures(Connection connection, String databaseName, String schemaName) {
+ String sql = "SHOW PROCEDURE STATUS WHERE Db = DATABASE()";
+ return SQLExecutor.getInstance().execute(connection, sql, resultSet -> {
+ ArrayList procedures = new ArrayList<>();
+ while (resultSet.next()) {
+ Procedure procedure = new Procedure();
+ procedure.setProcedureName(resultSet.getString("Name"));
+ procedures.add(procedure);
+ }
+ return procedures;
+ });
+ }
+
+ @Override
+ public Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName,
+ String procedureName) {
+ String routinesSql = String.format(ROUTINES_SQL, "PROCEDURE", databaseName, procedureName);
+ String showCreateProcedureSql = "SHOW CREATE PROCEDURE " + procedureName;
+ Procedure procedure = SQLExecutor.getInstance().execute(connection, routinesSql, resultSet -> {
+ Procedure p = new Procedure();
+ p.setDatabaseName(databaseName);
+ p.setSchemaName(schemaName);
+ p.setProcedureName(procedureName);
+ if (resultSet.next()) {
+ p.setSpecificName(resultSet.getString("SPECIFIC_NAME"));
+ p.setRemarks(resultSet.getString("ROUTINE_COMMENT"));
+ }
+ return p;
+ });
+ SQLExecutor.getInstance().execute(connection, showCreateProcedureSql, resultSet -> {
+ if (resultSet.next()) {
+ procedure.setProcedureBody(resultSet.getString("Create Procedure"));
+ }
+ });
+ return procedure;
+ }
+
+ private static String SELECT_TABLE_COLUMNS = "SELECT * FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s' order by ORDINAL_POSITION";
+
+ @Override
+ public List columns(Connection connection, String databaseName, String schemaName, String tableName) {
+ String sql = String.format(SELECT_TABLE_COLUMNS, databaseName, tableName);
+ List tableColumns = new ArrayList<>();
+ return SQLExecutor.getInstance().execute(connection, sql, resultSet -> {
+ while (resultSet.next()) {
+ TableColumn column = new TableColumn();
+ column.setDatabaseName(databaseName);
+ column.setTableName(tableName);
+ column.setOldName(resultSet.getString("COLUMN_NAME"));
+ column.setName(resultSet.getString("COLUMN_NAME"));
+ //column.setColumnType(resultSet.getString("COLUMN_TYPE"));
+ column.setColumnType(resultSet.getString("DATA_TYPE").toUpperCase());
+ //column.setDataType(resultSet.getInt("DATA_TYPE"));
+ column.setDefaultValue(resultSet.getString("COLUMN_DEFAULT"));
+ column.setAutoIncrement(resultSet.getString("EXTRA").contains("auto_increment"));
+ column.setComment(resultSet.getString("COLUMN_COMMENT"));
+ column.setPrimaryKey("PRI".equalsIgnoreCase(resultSet.getString("COLUMN_KEY")));
+ column.setNullable("YES".equalsIgnoreCase(resultSet.getString("IS_NULLABLE")) ? 1 : 0);
+ column.setOrdinalPosition(resultSet.getInt("ORDINAL_POSITION"));
+ column.setDecimalDigits(resultSet.getInt("NUMERIC_SCALE"));
+ column.setCharSetName(resultSet.getString("CHARACTER_SET_NAME"));
+ column.setCollationName(resultSet.getString("COLLATION_NAME"));
+ setColumnSize(column, resultSet.getString("COLUMN_TYPE"));
+ tableColumns.add(column);
+ }
+ return tableColumns;
+ });
+ }
+
+ private void setColumnSize(TableColumn column, String columnType) {
+ try {
+ if (columnType.contains("(")) {
+ String size = columnType.substring(columnType.indexOf("(") + 1, columnType.indexOf(")"));
+ if ("SET".equalsIgnoreCase(column.getColumnType()) || "ENUM".equalsIgnoreCase(column.getColumnType())) {
+ column.setValue(size);
+ } else {
+ if (size.contains(",")) {
+ String[] sizes = size.split(",");
+ if (StringUtils.isNotBlank(sizes[0])) {
+ column.setColumnSize(Integer.parseInt(sizes[0]));
+ }
+ if (StringUtils.isNotBlank(sizes[1])) {
+ column.setDecimalDigits(Integer.parseInt(sizes[1]));
+ }
+ } else {
+ column.setColumnSize(Integer.parseInt(size));
+ }
+ }
+ }
+ } catch (Exception e) {
+ }
+ }
+
+ private static String VIEW_DDL_SQL = "show create view %s";
+
+ @Override
+ public Table view(Connection connection, String databaseName, String schemaName, String viewName) {
+ String sql = String.format(VIEW_DDL_SQL, viewName);
+ return SQLExecutor.getInstance().execute(connection, sql, resultSet -> {
+ Table table = new Table();
+ table.setDatabaseName(databaseName);
+ table.setSchemaName(schemaName);
+ table.setName(viewName);
+ if (resultSet.next()) {
+ table.setDdl(resultSet.getString("Create View"));
+ }
+ return table;
+ });
+ }
+
+
+ @Override
+ public List indexes(Connection connection, String databaseName, String schemaName, String tableName) {
+ StringBuilder queryBuf = new StringBuilder("SHOW INDEX FROM ");
+ queryBuf.append("`").append(tableName).append("`");
+ queryBuf.append(" FROM ");
+ queryBuf.append("`").append(databaseName).append("`");
+ return SQLExecutor.getInstance().execute(connection, queryBuf.toString(), resultSet -> {
+ LinkedHashMap map = new LinkedHashMap();
+ while (resultSet.next()) {
+ String keyName = resultSet.getString("Key_name");
+ TableIndex tableIndex = map.get(keyName);
+ if (tableIndex != null) {
+ List columnList = tableIndex.getColumnList();
+ columnList.add(getTableIndexColumn(resultSet));
+ columnList = columnList.stream().sorted(Comparator.comparing(TableIndexColumn::getOrdinalPosition))
+ .collect(Collectors.toList());
+ tableIndex.setColumnList(columnList);
+ } else {
+ TableIndex index = new TableIndex();
+ index.setDatabaseName(databaseName);
+ index.setSchemaName(schemaName);
+ index.setTableName(tableName);
+ index.setName(keyName);
+ index.setUnique(!resultSet.getBoolean("Non_unique"));
+ index.setType(resultSet.getString("Index_type"));
+ index.setComment(resultSet.getString("Index_comment"));
+ List tableIndexColumns = new ArrayList<>();
+ tableIndexColumns.add(getTableIndexColumn(resultSet));
+ index.setColumnList(tableIndexColumns);
+ if ("PRIMARY".equalsIgnoreCase(keyName)) {
+ index.setType(DuckDBIndexTypeEnum.PRIMARY_KEY.getName());
+ } else if (index.getUnique()) {
+ index.setType(DuckDBIndexTypeEnum.UNIQUE.getName());
+ } else {
+ index.setType(DuckDBIndexTypeEnum.NORMAL.getName());
+ }
+ map.put(keyName, index);
+ }
+ }
+ return map.values().stream().collect(Collectors.toList());
+ });
+
+ }
+
+ private TableIndexColumn getTableIndexColumn(ResultSet resultSet) throws SQLException {
+ TableIndexColumn tableIndexColumn = new TableIndexColumn();
+ tableIndexColumn.setColumnName(resultSet.getString("Column_name"));
+ tableIndexColumn.setOrdinalPosition(resultSet.getShort("Seq_in_index"));
+ tableIndexColumn.setCollation(resultSet.getString("Collation"));
+ tableIndexColumn.setCardinality(resultSet.getLong("Cardinality"));
+ tableIndexColumn.setSubPart(resultSet.getLong("Sub_part"));
+ String collation = resultSet.getString("Collation");
+ if ("a".equalsIgnoreCase(collation)) {
+ tableIndexColumn.setAscOrDesc("ASC");
+ } else if ("d".equalsIgnoreCase(collation)) {
+ tableIndexColumn.setAscOrDesc("DESC");
+ }
+ return tableIndexColumn;
+ }
+
+ @Override
+ public SqlBuilder getSqlBuilder() {
+ return new DuckDBSqlBuilder();
+ }
+
+ @Override
+ public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) {
+ return TableMeta.builder()
+ .columnTypes(DuckDBColumnTypeEnum.getTypes())
+ //.collations(MysqlCollationEnum.getCollations())
+ .indexTypes(DuckDBIndexTypeEnum.getIndexTypes())
+ .defaultValues(DuckDBDefaultValueEnum.getDefaultValues())
+ .build();
+ }
+
+ @Override
+ public String getMetaDataName(String... names) {
+ return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> "`" + name + "`").collect(Collectors.joining("."));
+ }
+
+// @Override
+// public ValueHandler getValueHandler() {
+// return new MysqlValueHandler();
+// }
+
+ /*@Override
+ public ValueProcessor getValueProcessor() {
+ return new DuckDBValueProcessor();
+ }*/
+
+ @Override
+ public List getSystemDatabases() {
+ return systemDatabases;
+ }
+
+}
diff --git a/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/DuckDBPlugin.java b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/DuckDBPlugin.java
new file mode 100644
index 000000000..648ecd3aa
--- /dev/null
+++ b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/DuckDBPlugin.java
@@ -0,0 +1,25 @@
+package ai.chat2db.plugin.duckdb;
+
+import ai.chat2db.spi.DBManage;
+import ai.chat2db.spi.MetaData;
+import ai.chat2db.spi.Plugin;
+import ai.chat2db.spi.config.DBConfig;
+import ai.chat2db.spi.util.FileUtils;
+
+public class DuckDBPlugin implements Plugin {
+
+ @Override
+ public DBConfig getDBConfig() {
+ return FileUtils.readJsonValue(this.getClass(),"duckDB.json", DBConfig.class);
+ }
+
+ @Override
+ public MetaData getMetaData() {
+ return new DuckDBMetaData();
+ }
+
+ @Override
+ public DBManage getDBManage() {
+ return new DuckDBManage();
+ }
+}
diff --git a/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/builder/DuckDBSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/builder/DuckDBSqlBuilder.java
new file mode 100644
index 000000000..d6474d9b8
--- /dev/null
+++ b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/builder/DuckDBSqlBuilder.java
@@ -0,0 +1,424 @@
+package ai.chat2db.plugin.duckdb.builder;
+
+import ai.chat2db.plugin.duckdb.type.DuckDBColumnTypeEnum;
+import ai.chat2db.plugin.duckdb.type.DuckDBIndexTypeEnum;
+import ai.chat2db.spi.enums.EditStatus;
+import ai.chat2db.spi.jdbc.DefaultSqlBuilder;
+import ai.chat2db.spi.model.Database;
+import ai.chat2db.spi.model.Table;
+import ai.chat2db.spi.model.TableColumn;
+import ai.chat2db.spi.model.TableIndex;
+import ai.chat2db.spi.util.SqlUtils;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+
+
+public class DuckDBSqlBuilder extends DefaultSqlBuilder {
+ @Override
+ public String buildCreateTableSql(Table table) {
+ StringBuilder script = new StringBuilder();
+ script.append("CREATE TABLE ");
+ if (StringUtils.isNotBlank(table.getDatabaseName())) {
+ script.append("`").append(table.getDatabaseName()).append("`").append(".");
+ }
+ script.append("`").append(table.getName()).append("`").append(" (").append("\n");
+
+ // append column
+ for (TableColumn column : table.getColumnList()) {
+ if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType())) {
+ continue;
+ }
+ DuckDBColumnTypeEnum typeEnum = DuckDBColumnTypeEnum.getByType(column.getColumnType());
+ if (typeEnum == null) {
+ continue;
+ }
+ script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n");
+ }
+
+ // append primary key and index
+ for (TableIndex tableIndex : table.getIndexList()) {
+ if (StringUtils.isBlank(tableIndex.getName()) || StringUtils.isBlank(tableIndex.getType())) {
+ continue;
+ }
+ DuckDBIndexTypeEnum mysqlIndexTypeEnum = DuckDBIndexTypeEnum.getByType(tableIndex.getType());
+ if (mysqlIndexTypeEnum == null) {
+ continue;
+ }
+ script.append("\t").append("").append(mysqlIndexTypeEnum.buildIndexScript(tableIndex)).append(",\n");
+ }
+
+ script = new StringBuilder(script.substring(0, script.length() - 2));
+ script.append("\n)");
+
+
+ if (StringUtils.isNotBlank(table.getEngine())) {
+ script.append(" ENGINE=").append(table.getEngine());
+ }
+
+ if (StringUtils.isNotBlank(table.getCharset())) {
+ script.append(" DEFAULT CHARACTER SET=").append(table.getCharset());
+ }
+
+ if (StringUtils.isNotBlank(table.getCollate())) {
+ script.append(" COLLATE=").append(table.getCollate());
+ }
+
+ if (table.getIncrementValue() != null) {
+ script.append(" AUTO_INCREMENT=").append(table.getIncrementValue());
+ }
+
+ if (StringUtils.isNotBlank(table.getComment())) {
+ script.append(" COMMENT='").append(table.getComment()).append("'");
+ }
+
+ if (StringUtils.isNotBlank(table.getPartition())) {
+ script.append(" \n").append(table.getPartition());
+ }
+ script.append(";");
+
+ return script.toString();
+ }
+
+ @Override
+ public String buildModifyTaleSql(Table oldTable, Table newTable) {
+ StringBuilder tableBuilder = new StringBuilder();
+ tableBuilder.append("ALTER TABLE ");
+ if (StringUtils.isNotBlank(oldTable.getDatabaseName())) {
+ tableBuilder.append("`").append(oldTable.getDatabaseName()).append("`").append(".");
+ }
+ tableBuilder.append("`").append(oldTable.getName()).append("`").append("\n");
+
+ StringBuilder script = new StringBuilder();
+ if (!StringUtils.equalsIgnoreCase(oldTable.getName(), newTable.getName())) {
+ script.append("\t").append("RENAME TO ").append("`").append(newTable.getName()).append("`").append(",\n");
+ }
+ if (!StringUtils.equalsIgnoreCase(oldTable.getComment(), newTable.getComment())) {
+ script.append("\t").append("COMMENT=").append("'").append(newTable.getComment()).append("'").append(",\n");
+ }
+ if (oldTable.getIncrementValue() != newTable.getIncrementValue()) {
+ script.append("\t").append("AUTO_INCREMENT=").append(newTable.getIncrementValue()).append(",\n");
+ }
+
+ // 判断新增字段
+ List addColumnList = new ArrayList<>();
+ for (TableColumn tableColumn : newTable.getColumnList()) {
+ if (tableColumn.getEditStatus() != null ? tableColumn.getEditStatus().equals("ADD") : false) {
+ addColumnList.add(tableColumn);
+ }
+ }
+
+ // 判断移动的字段
+ List moveColumnList = movedElements(oldTable.getColumnList(), newTable.getColumnList());
+
+ // append modify column
+ for (TableColumn tableColumn : newTable.getColumnList()) {
+ if ((StringUtils.isNotBlank(tableColumn.getEditStatus()) && StringUtils.isNotBlank(tableColumn.getColumnType())
+ && StringUtils.isNotBlank(tableColumn.getName())) || moveColumnList.contains(tableColumn) || addColumnList.contains(tableColumn)) {
+ DuckDBColumnTypeEnum typeEnum = DuckDBColumnTypeEnum.getByType(tableColumn.getColumnType());
+ if (typeEnum == null) {
+ continue;
+ }
+ if (moveColumnList.contains(tableColumn) || addColumnList.contains(tableColumn)) {
+ script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(",\n");
+ } else {
+ script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(",\n");
+ }
+ }
+ }
+
+ // append modify index
+ for (TableIndex tableIndex : newTable.getIndexList()) {
+ if (StringUtils.isNotBlank(tableIndex.getEditStatus()) && StringUtils.isNotBlank(tableIndex.getType())) {
+ DuckDBIndexTypeEnum mysqlIndexTypeEnum = DuckDBIndexTypeEnum.getByType(tableIndex.getType());
+ if (mysqlIndexTypeEnum == null) {
+ continue;
+ }
+ script.append("\t").append(mysqlIndexTypeEnum.buildModifyIndex(tableIndex)).append(",\n");
+ }
+ }
+
+ // append reorder column
+ // script.append(buildGenerateReorderColumnSql(oldTable, newTable));
+
+ if (script.length() > 2) {
+ script = new StringBuilder(script.substring(0, script.length() - 2));
+ script.append(";");
+ return tableBuilder.append(script).toString();
+ } else {
+ return StringUtils.EMPTY;
+ }
+
+ }
+
+ private String findPrevious(TableColumn tableColumn, Table newTable) {
+ int index = newTable.getColumnList().indexOf(tableColumn);
+ if (index == 0) {
+ return "-1";
+ }
+ // Find the previous column that is not deleted
+ for (int i = index - 1; i >= 0; i--) {
+ if (newTable.getColumnList().get(i).getEditStatus() == null || !newTable.getColumnList().get(i).getEditStatus().equals(EditStatus.DELETE.name())) {
+ return newTable.getColumnList().get(i).getName();
+ }
+ }
+ return "-1";
+ }
+
+ @Override
+ public String pageLimit(String sql, int offset, int pageNo, int pageSize) {
+ StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
+ sqlBuilder.append(sql);
+ if (offset == 0) {
+ sqlBuilder.append("\n LIMIT ");
+ sqlBuilder.append(pageSize);
+ } else {
+ sqlBuilder.append("\n LIMIT ");
+ sqlBuilder.append(offset);
+ sqlBuilder.append(",");
+ sqlBuilder.append(pageSize);
+ }
+ return sqlBuilder.toString();
+ }
+
+
+ @Override
+ public String buildCreateDatabaseSql(Database database) {
+ StringBuilder sqlBuilder = new StringBuilder();
+ sqlBuilder.append("CREATE DATABASE `" + database.getName() + "`");
+ if (StringUtils.isNotBlank(database.getCharset())) {
+ sqlBuilder.append(" DEFAULT CHARACTER SET=").append(database.getCharset());
+ }
+ if (StringUtils.isNotBlank(database.getCollation())) {
+ sqlBuilder.append(" COLLATE=").append(database.getCollation());
+ }
+ return sqlBuilder.toString();
+ }
+
+ public static List movedElements(List original, List modified) {
+ int[][] dp = new int[original.size() + 1][modified.size() + 1];
+
+ // 构建DP表
+ for (int i = 1; i <= original.size(); i++) {
+ for (int j = 1; j <= modified.size(); j++) {
+ if (original.get(i - 1).getName().equals(modified.get(j - 1).getOldName())) {
+ dp[i][j] = dp[i - 1][j - 1] + 1;
+ } else {
+ dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
+ }
+ }
+ }
+
+ // 追踪LCS,找出移动了位置的元素
+ List moved = new ArrayList<>();
+ int i = original.size();
+ int j = modified.size();
+ while (i > 0 && j > 0) {
+ if (original.get(i - 1).equals(modified.get(j - 1))) {
+ i--;
+ j--;
+ } else if (dp[i - 1][j] >= dp[i][j - 1]) {
+ moved.add(original.get(i - 1));
+ // modified List中找到original.get(i-1)的位置
+// System.out.println("Moved elements:"+ original.get(i-1).getName() + " after " + modified.indexOf(original.get(i-1)) );
+ i--;
+ } else {
+ j--;
+ }
+ }
+
+ // 这里添加原始列表中未被包含在LCS中的元素
+ while (i > 0) {
+ moved.add(original.get(i - 1));
+ i--;
+ }
+
+ return moved;
+ }
+
+ public String buildGenerateReorderColumnSql(Table oldTable, Table newTable) {
+ StringBuilder sql = new StringBuilder();
+ int n = 0;
+ // Create a map to store the index of each column in the old table's column list
+ Map oldColumnIndexMap = new HashMap<>();
+ for (int i = 0; i < oldTable.getColumnList().size(); i++) {
+ oldColumnIndexMap.put(oldTable.getColumnList().get(i).getName(), i);
+ }
+ String[] oldColumnArray = oldTable.getColumnList().stream().map(TableColumn::getName).toArray(String[]::new);
+ String[] newColumnArray = newTable.getColumnList().stream().map(TableColumn::getName).toArray(String[]::new);
+
+ Set oldColumnSet = new HashSet<>(Arrays.asList(oldColumnArray));
+ Set newColumnSet = new HashSet<>(Arrays.asList(newColumnArray));
+ if (!oldColumnSet.equals(newColumnSet)) {
+ return "";
+ }
+
+ buildSql(oldColumnArray, newColumnArray, sql, oldTable, newTable, n);
+
+ return sql.toString();
+ }
+
+ private String[] buildSql(String[] originalArray, String[] targetArray, StringBuilder sql, Table oldTable, Table newTable, int n) {
+ // Complete the first move first
+ if (!originalArray[0].equals(targetArray[0])) {
+ int a = findIndex(originalArray, targetArray[0]);
+ TableColumn column = oldTable.getColumnList().stream().filter(col -> StringUtils.equals(col.getName(), originalArray[a])).findFirst().get();
+ String[] newArray = moveElement(originalArray, a, 0, targetArray, new AtomicInteger(0));
+ sql.append(" MODIFY COLUMN ");
+ DuckDBColumnTypeEnum typeEnum = DuckDBColumnTypeEnum.getByType(column.getColumnType());
+ sql.append(typeEnum.buildModifyColumn(column));
+ sql.append(" FIRST;\n");
+ n++;
+ if (Arrays.equals(newArray, targetArray)) {
+ return newArray;
+ }
+ String[] resultArray = buildSql(newArray, targetArray, sql, oldTable, newTable, n);
+ if (Arrays.equals(resultArray, targetArray)) {
+ return resultArray;
+ }
+ }
+
+ // After completing the last move
+ int max = originalArray.length - 1;
+ if (!originalArray[max].equals(targetArray[max])) {
+ int a = findIndex(originalArray, targetArray[max]);
+ //System.out.println("Move " + originalArray[a] + " after " + (a > 0 ? originalArray[max] : "start"));
+ TableColumn column = oldTable.getColumnList().stream().filter(col -> StringUtils.equals(col.getName(), originalArray[a])).findFirst().get();
+ String[] newArray = moveElement(originalArray, a, max, targetArray, new AtomicInteger(0));
+ if (n > 0) {
+ sql.append("ALTER TABLE ");
+ if (StringUtils.isNotBlank(oldTable.getDatabaseName())) {
+ sql.append("`").append(oldTable.getDatabaseName()).append("`").append(".");
+ }
+ sql.append("`").append(oldTable.getName()).append("`").append("\n");
+ }
+ sql.append(" MODIFY COLUMN ");
+ DuckDBColumnTypeEnum typeEnum = DuckDBColumnTypeEnum.getByType(column.getColumnType());
+ sql.append(typeEnum.buildModifyColumn(column));
+ sql.append(" AFTER ");
+ sql.append(oldTable.getColumnList().get(max).getName());
+ sql.append(";\n");
+ n++;
+ if (Arrays.equals(newArray, targetArray)) {
+ return newArray;
+ }
+ String[] resultArray = buildSql(newArray, targetArray, sql, oldTable, newTable, n);
+ if (Arrays.equals(resultArray, targetArray)) {
+ return resultArray;
+ }
+ }
+
+
+ for (int i = 0; i < originalArray.length; i++) {
+ int a = findIndex(targetArray, originalArray[i]);
+ if (i != a && isMoveValid(originalArray, targetArray, i, a)) {
+ // Find name a in oldTable.getColumnList
+ int finalI = i;
+ TableColumn column = oldTable.getColumnList().stream().filter(col -> StringUtils.equals(col.getName(), originalArray[finalI])).findFirst().get();
+ if (n > 0) {
+ sql.append("ALTER TABLE ");
+ if (StringUtils.isNotBlank(oldTable.getDatabaseName())) {
+ sql.append("`").append(oldTable.getDatabaseName()).append("`").append(".");
+ }
+ sql.append("`").append(oldTable.getName()).append("`").append("\n");
+ }
+ sql.append(" MODIFY COLUMN ");
+ DuckDBColumnTypeEnum typeEnum = DuckDBColumnTypeEnum.getByType(column.getColumnType());
+ sql.append(typeEnum.buildModifyColumn(column));
+ sql.append(" AFTER ");
+ AtomicInteger continuousDataCount = new AtomicInteger(0);
+ String[] newArray = moveElement(originalArray, i, a, targetArray, continuousDataCount);
+ if (i < a) {
+ sql.append(originalArray[a + continuousDataCount.get()]);
+ } else {
+ sql.append(originalArray[a - 1]);
+ }
+
+ sql.append(";\n");
+ n++;
+
+ if (Arrays.equals(newArray, targetArray)) {
+ return newArray;
+ }
+ String[] resultArray = buildSql(newArray, targetArray, sql, oldTable, newTable, n);
+ if (Arrays.equals(resultArray, targetArray)) {
+ return resultArray;
+ }
+ }
+ }
+ return null;
+ }
+
+ private static int findIndex(String[] array, String element) {
+ for (int i = 0; i < array.length; i++) {
+ if (array[i].equals(element)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private static boolean isMoveValid(String[] originalArray, String[] targetArray, int i, int a) {
+ return ((i == 0 || a == 0 || !originalArray[i - 1].equals(targetArray[a - 1])) &&
+ (i >= originalArray.length - 1 || a >= targetArray.length - 1 || !originalArray[i + 1].equals(targetArray[a + 1])))
+ || (i > 0 && a > 0 && !originalArray[i - 1].equals(targetArray[a - 1]));
+ }
+
+ private static String[] moveElement(String[] originalArray, int from, int to, String[] targetArray, AtomicInteger continuousDataCount) {
+ String[] newArray = new String[originalArray.length];
+ System.arraycopy(originalArray, 0, newArray, 0, originalArray.length);
+ String temp = newArray[from];
+ // 是否有连续移动数据
+ boolean isContinuousData = false;
+ // 连续数据数量
+ if (from < to) {
+ for (int i = to; i < originalArray.length - 1; i++) {
+ if (originalArray[i + 1].equals(targetArray[findIndex(targetArray, originalArray[i]) + 1])) {
+ continuousDataCount.set(continuousDataCount.incrementAndGet());
+ } else {
+ break;
+ }
+ }
+ if (continuousDataCount.get() > 0) {
+ System.arraycopy(originalArray, from + 1, newArray, from, to - from + 1);
+ isContinuousData = true;
+ } else {
+ System.arraycopy(originalArray, from + 1, newArray, from, to - from);
+ }
+ } else {
+ System.arraycopy(originalArray, to, newArray, to + 1, from - to);
+ }
+ if (isContinuousData) {
+ newArray[to + continuousDataCount.get()] = temp;
+ } else {
+ newArray[to] = temp;
+ }
+ return newArray;
+ }
+
+
+ @Override
+ protected void buildTableName(String databaseName, String schemaName, String tableName, StringBuilder script) {
+ if (StringUtils.isNotBlank(databaseName)) {
+ script.append(SqlUtils.quoteObjectName(databaseName, "`")).append('.');
+ }
+ script.append(SqlUtils.quoteObjectName(tableName, "`"));
+ }
+
+ /**
+ * @param columnList
+ * @param script
+ */
+ @Override
+ protected void buildColumns(List columnList, StringBuilder script) {
+ if (CollectionUtils.isNotEmpty(columnList)) {
+ script.append(" (")
+ .append(columnList.stream().map(s -> SqlUtils.quoteObjectName(s, "`")).collect(Collectors.joining(",")))
+ .append(") ");
+ }
+ }
+
+}
diff --git a/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/builder/form.json b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/builder/form.json
new file mode 100644
index 000000000..e24020fbe
--- /dev/null
+++ b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/builder/form.json
@@ -0,0 +1,45 @@
+{
+ "baseInfo": {
+ "items": [
+ {
+ "defaultValue": "@localhost",
+ "inputType": "INPUT",
+ "labelNameCN": "名称",
+ "labelNameEN": "Name",
+ "name": "alias",
+ "required": true,
+ "width": 100,
+ },
+ {
+ "defaultValue": "localhost",
+ "inputType": "INPUT",
+ "labelNameCN": "主机",
+ "labelNameEN": "Host",
+ "name": "host",
+ "required": true,
+ "width": 70,
+ },
+ {
+ "defaultValue": "",
+ "inputType": "INPUT",
+ "labelNameCN": "数据库",
+ "labelNameEN": "Database",
+ "name": "database",
+ "required": false,
+ "width": 100
+ },
+ {
+ "defaultValue": "jdbc:duckdb:{file}",
+ "inputType": "INPUT",
+ "labelNameCN": "URL",
+ "labelNameEN": "URL",
+ "name": "url",
+ "required": true,
+ "width": 100
+ }
+ ],
+ "pattern": "/jdbc:duckdb:\/\/(\\w+)",
+ "template": "jdbc:duckdb://{host}"
+ },
+ "type":"DuckDB"
+}
\ No newline at end of file
diff --git a/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/duckDB.json b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/duckDB.json
new file mode 100644
index 000000000..0c4dec309
--- /dev/null
+++ b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/duckDB.json
@@ -0,0 +1,18 @@
+{
+ "dbType": "DUCKDB",
+ "supportDatabase": true,
+ "supportSchema": true,
+ "driverConfigList": [
+ {
+ "url": "jdbc:duckdb://",
+ "defaultDriver": true,
+ "custom": false,
+ "downloadJdbcDriverUrls": [
+ "https://cdn.chat2db-ai.com/lib/duckdb_jdbc-1.1.3.jar"
+ ],
+ "jdbcDriver": "duckdb_jdbc-1.1.3.jar",
+ "jdbcDriverClass": "org.duckdb.DuckDBDriver"
+ }
+ ],
+ "name": "DuckDB"
+}
diff --git a/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/type/DuckDBColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/type/DuckDBColumnTypeEnum.java
new file mode 100644
index 000000000..749f0d8c3
--- /dev/null
+++ b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/type/DuckDBColumnTypeEnum.java
@@ -0,0 +1,316 @@
+package ai.chat2db.plugin.duckdb.type;
+
+import ai.chat2db.spi.ColumnBuilder;
+import ai.chat2db.spi.enums.EditStatus;
+import ai.chat2db.spi.model.ColumnType;
+import ai.chat2db.spi.model.TableColumn;
+import ai.chat2db.spi.util.SqlUtils;
+import com.google.common.collect.Maps;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+public enum DuckDBColumnTypeEnum implements ColumnBuilder {
+
+ JSON("JSON", false, false, true, false, false, false, true, true, false, false),
+
+ BIGINT("BIGINT", false, false, true, false, false, false, true, true, false, false),
+
+ BINARY("BINARY", false, false, true, false, false, false, true, true, false, false),
+
+ BIT("BIT", false, false, true, false, false, false, true, true, false, false),
+
+ BLOB("BLOB", false, false, true, false, false, false, true, true, false, false),
+
+ BOOL("BOOL", false, false, true, false, false, false, true, true, false, false),
+
+ BOOLEAN("BOOLEAN", false, false, true, false, false, false, true, true, false, false),
+
+ BPCHAR("BPCHAR", false, false, true, false, false, false, true, true, false, false),
+
+ BYTEA("BYTEA", false, false, true, false, false, false, true, true, false, false),
+
+ CHAR("CHAR", true, false, true, false, false, false, true, true, false, false),
+
+ DATE("DATE", false, false, true, false, false, false, true, true, false, false),
+
+ DATETIME("DATETIME", false, false, true, false, false, false, true, true, false, false),
+
+ DEC("DEC", false, false, true, false, false, false, true, true, false, false),
+
+ DECIMAL("DECIMAL", true, true, true, false, false, false, true, true, false, false),
+
+ DOUBLE("DOUBLE", false, false, true, false, false, false, true, true, false, false),
+
+ ENUM("ENUM", false, false, true, false, false, false, true, true, false, false),
+
+ FLOAT("FLOAT", true, false, true, false, false, false, true, true, false, false),
+
+ FLOAT4("FLOAT4", true, false, true, false, false, false, true, true, false, false),
+
+ FLOAT8("FLOAT8", true, false, true, false, false, false, true, true, false, false),
+
+ GUID("GUID", false, false, true, false, false, false, true, true, false, false),
+
+ HUGEINT("HUGEINT", false, false, true, true, false, false, true, true, false, false),
+
+ INT("INT", false, false, true, false, false, false, true, true, false, false),
+ INT1("INT1", false, false, true, false, false, false, true, true, false, false),
+ INT128("INT128", false, false, true, false, false, false, true, true, false, false),
+ INT16("INT16", false, false, true, false, false, false, true, true, false, false),
+ INT2("INT2", false, false, true, false, false, false, true, true, false, false),
+ INT32("INT32", false, false, true, false, false, false, true, true, false, false),
+ INT4("INT4", false, false, true, false, false, false, true, true, false, false),
+ INT64("INT64", false, false, true, false, false, false, true, true, false, false),
+ INT8("INT8", false, false, true, false, false, false, true, true, false, false),
+
+ INTEGER("INTEGER", false, false, true, false, false, false, true, true, false, false),
+
+ INTEGRAL("INTEGRAL", false, false, true, false, false, false, true, true, false, false),
+ INTERVAL("INTERVAL", false, false, true, false, false, false, true, true, false, false),
+
+ LIST("LIST", false, false, true, false, false, false, true, true, false, false),
+
+ LOGICAL("LOGICAL", false, false, true, false, false, false, true, true, false, false),
+
+ LONG("LONG", false, false, true, false, false, false, true, true, false, false),
+
+ MAP("MAP", false, false, true, false, false, false, true, true, false, false),
+
+ NULL("NULL", false, false, true, false, false, false, true, true, false, false),
+
+ NUMERIC("NUMERIC", false, false, true, false, false, false, true, true, false, false),
+
+ NVARCHAR("NVARCHAR", true, false, true, false, false, false, true, true, false, false),
+
+ OID("OID", false, false, true, false, false, false, true, true, false, false),
+
+ REAL("REAL", false, false, true, false, false, false, true, true, false, false),
+
+ ROW("ROW", false, false, true, false, false, false, true, true, false, false),
+
+ SHORT("SHORT", false, false, true, false, false, false, true, true, false, false),
+
+ SIGNED("SIGNED", false, false, true, false, false, false, true, true, false, false),
+
+ SMALLINT("SMALLINT", false, false, true, false, false, false, true, true, false, false),
+
+ STRING("STRING", false, false, true, false, false, false, true, true, false, false),
+
+ STRUCT("STRUCT", false, false, true, false, false, false, true, true, false, false),
+
+ TEXT("TEXT", false, false, true, false, false, false, true, true, false, false),
+
+ TIME("TIME", false, false, true, false, false, false, true, true, false, false),
+
+ TIMESTAMP("TIMESTAMP", false, false, true, false, false, false, true, true, false, false),
+
+ TIMSTAMP_MS("TIMSTAMP_MS", false, false, true, false, false, false, true, true, false, false),
+
+ TIMSTAMP_NS("TIMSTAMP_NS", false, false, true, false, false, false, true, true, false, false),
+ TIMSTAMP_S("TIMSTAMP_S", false, false, true, false, false, false, true, true, false, false),
+ TIMSTAMP_US("TIMSTAMP_US", false, false, true, false, false, false, true, true, false, false),
+
+ TIMESTAMP_WITH_TIME_ZONE("TIMESTAMP WITH TIME ZONE", false, false, true, false, false, false, true, true, false, false),
+
+ TIME_WITH_TIME_ZONE("TIME WITH TIME ZONE", false, false, true, false, false, false, true, true, false, false),
+
+
+ TINYINT("TINYINT", false, false, true, false, false, false, true, true, false, false),
+
+ UBIGINT("UBIGINT", false, false, true, false, false, false, true, true, false, false),
+
+ UHUGEINT("UHUGEINT", false, false, true, false, false, false, true, true, false, false),
+
+ UINT128("UINT128", false, false, true, false, false, false, true, true, false, false),
+
+ UINT16("UINT16", false, false, true, false, false, false, true, true, false, false),
+
+ UINT32("UINT32", false, false, true, false, false, false, true, true, false, false),
+
+ UINT64("UINT64", false, false, true, false, false, false, true, true, false, false),
+
+ UINT8("UINT8", false, false, true, false, false, false, true, true, false, false),
+
+ UINTEGER("UINTEGER", false, false, true, false, false, false, true, true, false, false),
+
+ UNION("UNION", false, false, true, false, false, false, true, true, false, false),
+
+ USMALLINT("USMALLINT", false, false, true, false, false, false, true, true, false, false),
+
+ UTINYINT("UTINYINT", false, false, true, false, false, false, true, true, false, false),
+
+ UUID("UUID", false, false, true, false, false, false, true, true, false, false),
+
+ VARBINARY("VARBINARY", false, false, true, false, false, false, true, true, false, false),
+
+ VARCHAR("VARCHAR", true, false, true, false, false, false, true, true, false, true),
+
+ VARINT("VARINT", false, false, true, false, false, false, true, true, false, false),
+
+ ARRAY("ARRAY", false, false, true, false, false, false, true, true, false, false),
+ ;
+ private ColumnType columnType;
+
+ public static DuckDBColumnTypeEnum getByType(String dataType) {
+ String type = SqlUtils.removeDigits(dataType.toUpperCase());
+ return COLUMN_TYPE_MAP.get(type);
+ }
+
+ private static Map COLUMN_TYPE_MAP = Maps.newHashMap();
+
+ static {
+ for (DuckDBColumnTypeEnum value : DuckDBColumnTypeEnum.values()) {
+ COLUMN_TYPE_MAP.put(value.getColumnType().getTypeName(), value);
+ }
+ }
+
+ public ColumnType getColumnType() {
+ return columnType;
+ }
+
+
+ DuckDBColumnTypeEnum(String dataTypeName, boolean supportLength, boolean supportScale, boolean supportNullable, boolean supportAutoIncrement, boolean supportCharset, boolean supportCollation, boolean supportComments, boolean supportDefaultValue, boolean supportExtent, boolean supportUnit) {
+ this.columnType = new ColumnType(dataTypeName, supportLength, supportScale, supportNullable, supportAutoIncrement, supportCharset, supportCollation, supportComments, supportDefaultValue, supportExtent, false, supportUnit);
+ }
+
+ @Override
+ public String buildCreateColumnSql(TableColumn column) {
+ DuckDBColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType().toUpperCase());
+ if (type == null) {
+ return "";
+ }
+ StringBuilder script = new StringBuilder();
+
+ script.append("\"").append(column.getName()).append("\"").append(" ");
+
+ script.append(buildDataType(column, type)).append(" ");
+
+ script.append(buildDefaultValue(column, type)).append(" ");
+
+ script.append(buildAutoIncrement(column,type)).append(" ");
+
+ script.append(buildNullable(column, type)).append(" ");
+
+ return script.toString();
+ }
+
+ private String buildAutoIncrement(TableColumn column, DuckDBColumnTypeEnum type) {
+ if(!type.getColumnType().isSupportAutoIncrement()){
+ return "";
+ }
+ if (column.getAutoIncrement() != null && column.getAutoIncrement()
+ && column.getSeed() != null && column.getSeed() > 0 && column.getIncrement() != null && column.getIncrement() > 0) {
+ return "IDENTITY(" + column.getSeed() + "," + column.getIncrement() + ")";
+ }
+ if (column.getAutoIncrement() != null && column.getAutoIncrement()) {
+ return "IDENTITY(1,1)";
+ }
+ return "";
+ }
+
+ private String buildNullable(TableColumn column, DuckDBColumnTypeEnum type) {
+ if (!type.getColumnType().isSupportNullable()) {
+ return "";
+ }
+ if (column.getNullable() != null && 1 == column.getNullable()) {
+ return "NULL";
+ } else {
+ return "NOT NULL";
+ }
+ }
+
+ private String buildDefaultValue(TableColumn column, DuckDBColumnTypeEnum type) {
+ if (!type.getColumnType().isSupportDefaultValue() || StringUtils.isEmpty(column.getDefaultValue())) {
+ return "";
+ }
+
+ if ("EMPTY_STRING".equalsIgnoreCase(column.getDefaultValue().trim())) {
+ return StringUtils.join("DEFAULT ''");
+ }
+
+ if ("NULL".equalsIgnoreCase(column.getDefaultValue().trim())) {
+ return StringUtils.join("DEFAULT NULL");
+ }
+
+ return StringUtils.join("DEFAULT ", column.getDefaultValue());
+ }
+
+ private String buildDataType(TableColumn column, DuckDBColumnTypeEnum type) {
+ String columnType = type.columnType.getTypeName();
+ if (Arrays.asList(VARCHAR, STRING, BPCHAR, NVARCHAR, TEXT).contains(type)) {
+ StringBuilder script = new StringBuilder();
+ script.append(columnType);
+ if (column.getColumnSize() != null && StringUtils.isEmpty(column.getUnit())) {
+ script.append("(").append(column.getColumnSize()).append(")");
+ } else if (column.getColumnSize() != null && !StringUtils.isEmpty(column.getUnit())) {
+ script.append("(").append(column.getColumnSize()).append(" ").append(column.getUnit()).append(")");
+ }
+ return script.toString();
+ }
+
+ if (Arrays.asList(DECIMAL, FLOAT, TIMESTAMP).contains(type)) {
+ StringBuilder script = new StringBuilder();
+ script.append(columnType);
+ if (column.getColumnSize() != null && column.getDecimalDigits() == null) {
+ script.append("(").append(column.getColumnSize()).append(")");
+ } else if (column.getColumnSize() != null && column.getDecimalDigits() != null) {
+ script.append("(").append(column.getColumnSize()).append(",").append(column.getDecimalDigits()).append(")");
+ }
+ return script.toString();
+ }
+
+ if (Arrays.asList(TIME_WITH_TIME_ZONE, TIMSTAMP_US).contains(type)) {
+ StringBuilder script = new StringBuilder();
+ if (column.getColumnSize() == null) {
+ script.append(columnType);
+ } else {
+ String[] split = columnType.split("TIMESTAMP");
+ script.append("TIMESTAMP").append("(").append(column.getColumnSize()).append(")").append(split[1]);
+ }
+ return script.toString();
+ }
+ return columnType;
+ }
+
+
+ @Override
+ public String buildModifyColumn(TableColumn tableColumn) {
+
+ if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) {
+ StringBuilder script = new StringBuilder();
+ script.append("ALTER TABLE ").append("\"").append(tableColumn.getSchemaName()).append("\".\"").append(tableColumn.getTableName()).append("\"");
+ script.append(" ").append("DROP COLUMN ").append("\"").append(tableColumn.getName()).append("\"");
+ return script.toString();
+ }
+ if (EditStatus.ADD.name().equals(tableColumn.getEditStatus())) {
+ StringBuilder script = new StringBuilder();
+ script.append("ALTER TABLE ").append("\"").append(tableColumn.getSchemaName()).append("\".\"").append(tableColumn.getTableName()).append("\"");
+ script.append(" ").append("ADD (").append(buildCreateColumnSql(tableColumn)).append(")");
+ return script.toString();
+ }
+ if (EditStatus.MODIFY.name().equals(tableColumn.getEditStatus())) {
+ StringBuilder script = new StringBuilder();
+ script.append("ALTER TABLE ").append("\"").append(tableColumn.getSchemaName()).append("\".\"").append(tableColumn.getTableName()).append("\"");
+ script.append(" ").append("MODIFY (").append(buildCreateColumnSql(tableColumn)).append(") \n");
+
+ if (!StringUtils.equalsIgnoreCase(tableColumn.getOldName(), tableColumn.getName())) {
+ script.append(";");
+ script.append("ALTER TABLE ").append("\"").append(tableColumn.getSchemaName()).append("\".\"").append(tableColumn.getTableName()).append("\"");
+ script.append(" ").append("RENAME COLUMN ").append("\"").append(tableColumn.getOldName()).append("\"").append(" TO ").append("\"").append(tableColumn.getName()).append("\"");
+
+ }
+ return script.toString();
+
+ }
+ return "";
+ }
+
+ public static List getTypes() {
+ return Arrays.stream(DuckDBColumnTypeEnum.values()).map(columnTypeEnum ->
+ columnTypeEnum.getColumnType()
+ ).toList();
+ }
+}
diff --git a/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/type/DuckDBDefaultValueEnum.java b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/type/DuckDBDefaultValueEnum.java
new file mode 100644
index 000000000..ee1152776
--- /dev/null
+++ b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/type/DuckDBDefaultValueEnum.java
@@ -0,0 +1,27 @@
+package ai.chat2db.plugin.duckdb.type;
+
+import ai.chat2db.spi.model.DefaultValue;
+
+import java.util.Arrays;
+import java.util.List;
+
+public enum DuckDBDefaultValueEnum {
+ EMPTY_STRING("EMPTY_STRING"),
+ NULL("NULL"),
+ ;
+ private DefaultValue defaultValue;
+
+ DuckDBDefaultValueEnum(String defaultValue) {
+ this.defaultValue = new DefaultValue(defaultValue);
+ }
+
+
+ public DefaultValue getDefaultValue() {
+ return defaultValue;
+ }
+
+ public static List getDefaultValues() {
+ return Arrays.stream(DuckDBDefaultValueEnum.values()).map(DuckDBDefaultValueEnum::getDefaultValue).collect(java.util.stream.Collectors.toList());
+ }
+
+}
diff --git a/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/type/DuckDBIndexTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/type/DuckDBIndexTypeEnum.java
new file mode 100644
index 000000000..20e5ad388
--- /dev/null
+++ b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/type/DuckDBIndexTypeEnum.java
@@ -0,0 +1,129 @@
+package ai.chat2db.plugin.duckdb.type;
+
+import ai.chat2db.spi.enums.EditStatus;
+import ai.chat2db.spi.model.IndexType;
+import ai.chat2db.spi.model.TableIndex;
+import ai.chat2db.spi.model.TableIndexColumn;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Arrays;
+import java.util.List;
+
+public enum DuckDBIndexTypeEnum {
+
+ PRIMARY_KEY("Primary", "PRIMARY KEY"),
+
+ NORMAL("Normal", "INDEX"),
+
+ UNIQUE("Unique", "UNIQUE INDEX"),
+
+ BITMAP("BITMAP", "BITMAP INDEX");
+
+
+
+ public IndexType getIndexType() {
+ return indexType;
+ }
+
+ public void setIndexType(IndexType indexType) {
+ this.indexType = indexType;
+ }
+
+ private IndexType indexType;
+
+
+ public String getName() {
+ return name;
+ }
+
+ private String name;
+
+
+ public String getKeyword() {
+ return keyword;
+ }
+
+ private String keyword;
+
+ DuckDBIndexTypeEnum(String name, String keyword) {
+ this.name = name;
+ this.keyword = keyword;
+ this.indexType = new IndexType(name);
+ }
+
+
+ public static DuckDBIndexTypeEnum getByType(String type) {
+ for (DuckDBIndexTypeEnum value : DuckDBIndexTypeEnum.values()) {
+ if (value.name.equalsIgnoreCase(type)) {
+ return value;
+ }
+ }
+ return null;
+ }
+
+ public String buildIndexScript(TableIndex tableIndex) {
+ StringBuilder script = new StringBuilder();
+ if (PRIMARY_KEY.equals(this)) {
+ script.append("ALTER TABLE \"").append(tableIndex.getSchemaName()).append("\".\"").append(tableIndex.getTableName()).append("\" ADD PRIMARY KEY ").append(buildIndexColumn(tableIndex));
+ } else {
+ if (UNIQUE.equals(this)) {
+ script.append("CREATE UNIQUE INDEX ");
+ } else {
+ script.append("CREATE INDEX ");
+ }
+ script.append(buildIndexName(tableIndex)).append(" ON \"").append(tableIndex.getSchemaName()).append("\".\"").append(tableIndex.getTableName()).append("\" ").append(buildIndexColumn(tableIndex));
+ }
+ return script.toString();
+ }
+
+
+ private String buildIndexColumn(TableIndex tableIndex) {
+ StringBuilder script = new StringBuilder();
+ script.append("(");
+ for (TableIndexColumn column : tableIndex.getColumnList()) {
+ if (StringUtils.isNotBlank(column.getColumnName())) {
+ script.append("\"").append(column.getColumnName()).append("\"");
+ if (!StringUtils.isBlank(column.getAscOrDesc()) && !PRIMARY_KEY.equals(this)) {
+ script.append(" ").append(column.getAscOrDesc());
+ }
+ script.append(",");
+ }
+ }
+ script.deleteCharAt(script.length() - 1);
+ script.append(")");
+ return script.toString();
+ }
+
+ private String buildIndexName(TableIndex tableIndex) {
+ return "\"" + tableIndex.getSchemaName() + "\"." + "\"" + tableIndex.getName() + "\"";
+ }
+
+ public String buildModifyIndex(TableIndex tableIndex) {
+ if (EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) {
+ return buildDropIndex(tableIndex);
+ }
+ if (EditStatus.MODIFY.name().equals(tableIndex.getEditStatus())) {
+ return StringUtils.join(buildDropIndex(tableIndex), ";\n", buildIndexScript(tableIndex));
+ }
+ if (EditStatus.ADD.name().equals(tableIndex.getEditStatus())) {
+ return StringUtils.join(buildIndexScript(tableIndex));
+ }
+ return "";
+ }
+
+ private String buildDropIndex(TableIndex tableIndex) {
+ if (DuckDBIndexTypeEnum.PRIMARY_KEY.getName().equals(tableIndex.getType())) {
+ String tableName = "\"" + tableIndex.getSchemaName() + "\"." + "\"" + tableIndex.getTableName() + "\"";
+ return StringUtils.join("ALTER TABLE ",tableName," DROP PRIMARY KEY");
+ }
+ StringBuilder script = new StringBuilder();
+ script.append("DROP INDEX ");
+ script.append(buildIndexName(tableIndex));
+
+ return script.toString();
+ }
+
+ public static List getIndexTypes() {
+ return Arrays.asList(DuckDBIndexTypeEnum.values()).stream().map(DuckDBIndexTypeEnum::getIndexType).collect(java.util.stream.Collectors.toList());
+ }
+}
diff --git a/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin
new file mode 100644
index 000000000..46d0d0d83
--- /dev/null
+++ b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin
@@ -0,0 +1 @@
+ai.chat2db.plugin.duckdb.DuckDBPlugin
\ No newline at end of file
diff --git a/chat2db-server/chat2db-plugins/pom.xml b/chat2db-server/chat2db-plugins/pom.xml
index a98b555cb..878eddd17 100644
--- a/chat2db-server/chat2db-plugins/pom.xml
+++ b/chat2db-server/chat2db-plugins/pom.xml
@@ -30,6 +30,7 @@
chat2db-hive
chat2db-kingbase
chat2db-timeplus
+ chat2db-duckdb
diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/pom.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/pom.xml
index 39d5c2a2d..7fdb052b2 100644
--- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/pom.xml
+++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/pom.xml
@@ -126,6 +126,11 @@
chat2db-timeplus
${revision}
+
+ ai.chat2db
+ chat2db-duckdb
+ ${version}
+
commons-codec
commons-codec
From 13acc0e98afb5d5c9f6ad64d09772b10c29d8d74 Mon Sep 17 00:00:00 2001
From: tmlx1990
Date: Fri, 29 Nov 2024 00:27:16 +0800
Subject: [PATCH 8/8] add duckdb
---
.../ConnectionEdit/config/dataSource.ts | 1 -
.../plugin/duckdb/DuckDBCommandExecutor.java | 17 +
.../chat2db/plugin/duckdb/DuckDBManage.java | 2 +-
.../chat2db/plugin/duckdb/DuckDBMetaData.java | 187 +++--------
.../duckdb/builder/DuckDBSqlBuilder.java | 305 ++----------------
.../duckdb/type/DuckDBColumnTypeEnum.java | 129 +++++++-
.../duckdb/type/DuckDBIndexTypeEnum.java | 20 +-
.../domain/core/impl/TableServiceImpl.java | 2 +-
8 files changed, 218 insertions(+), 445 deletions(-)
create mode 100644 chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/DuckDBCommandExecutor.java
diff --git a/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts b/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts
index ec9ec7486..28f51acd7 100644
--- a/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts
+++ b/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts
@@ -2140,7 +2140,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [
],
pattern: /jdbc:duckdb:\/\/(\w+)/,
template: 'jdbc:duckdb://{host}',
- excludes: [OperationColumn.ViewDDL, OperationColumn.CreateTable, OperationColumn.EditTable],
},
ssh: sshConfig,
},
diff --git a/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/DuckDBCommandExecutor.java b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/DuckDBCommandExecutor.java
new file mode 100644
index 000000000..faf5a0bd7
--- /dev/null
+++ b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/DuckDBCommandExecutor.java
@@ -0,0 +1,17 @@
+package ai.chat2db.plugin.duckdb;
+
+import ai.chat2db.spi.model.Command;
+import ai.chat2db.spi.model.ExecuteResult;
+import ai.chat2db.spi.sql.SQLExecutor;
+
+import java.util.List;
+
+public class DuckDBCommandExecutor extends SQLExecutor {
+
+ @Override
+ public List executeSelectTable(Command command) {
+ String sql = "select * from " +command.getSchemaName() + "." + command.getTableName();
+ command.setScript(sql);
+ return execute(command);
+ }
+}
diff --git a/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/DuckDBManage.java b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/DuckDBManage.java
index 2014da51b..b4c117534 100644
--- a/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/DuckDBManage.java
+++ b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/DuckDBManage.java
@@ -204,7 +204,7 @@ public void connectDatabase(Connection connection, String database) {
return;
}
try {
- SQLExecutor.getInstance().execute(connection, "use `" + database + "`;");
+ SQLExecutor.getInstance().execute(connection, "use " + database + ";");
} catch (SQLException e) {
throw new RuntimeException(e);
}
diff --git a/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/DuckDBMetaData.java b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/DuckDBMetaData.java
index 1e0bfb194..50ef4bd34 100644
--- a/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/DuckDBMetaData.java
+++ b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/DuckDBMetaData.java
@@ -4,6 +4,7 @@
import ai.chat2db.plugin.duckdb.type.DuckDBColumnTypeEnum;
import ai.chat2db.plugin.duckdb.type.DuckDBDefaultValueEnum;
import ai.chat2db.plugin.duckdb.type.DuckDBIndexTypeEnum;
+import ai.chat2db.spi.CommandExecutor;
import ai.chat2db.spi.MetaData;
import ai.chat2db.spi.SqlBuilder;
import ai.chat2db.spi.jdbc.DefaultMetaService;
@@ -22,7 +23,7 @@
public class DuckDBMetaData extends DefaultMetaService implements MetaData {
- private List systemDatabases = Arrays.asList("information_schema", "performance_schema", "mysql", "sys");
+ private List systemDatabases = Arrays.asList("information_schema", "temp", "main", "system");
@Override
public List databases(Connection connection) {
@@ -30,12 +31,17 @@ public List databases(Connection connection) {
return sortDatabase(databases, systemDatabases, connection);
}
+ @Override
+ public CommandExecutor getCommandExecutor() {
+ return new DuckDBCommandExecutor();
+ }
+
private static String TABLES_SQL
- = "SELECT TABLE_SCHEMA, TABLE_NAME, ENGINE, VERSION, TABLE_ROWS, DATA_LENGTH, AUTO_INCREMENT, CREATE_TIME, UPDATE_TIME, TABLE_COLLATION, TABLE_COMMENT FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA = '%s'";
+ = "SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_CATALOG = '%s' AND TABLE_SCHEMA = '%s'";
@Override
public List tables(Connection connection, @NotEmpty String databaseName, String schemaName, String tableName) {
- String sql = String.format(TABLES_SQL, databaseName);
+ String sql = String.format(TABLES_SQL, databaseName, schemaName);
if(StringUtils.isNotBlank(tableName)){
sql += " AND TABLE_NAME = '" + tableName + "'";
}
@@ -45,13 +51,13 @@ public List tables(Connection connection, @NotEmpty String databaseName,
Table table = new Table();
table.setDatabaseName(databaseName);
table.setSchemaName(schemaName);
- table.setName(resultSet.getString("TABLE_NAME"));
- table.setEngine(resultSet.getString("ENGINE"));
- table.setRows(resultSet.getLong("TABLE_ROWS"));
- table.setDataLength(resultSet.getLong("DATA_LENGTH"));
- table.setCreateTime(resultSet.getString("CREATE_TIME"));
- table.setUpdateTime(resultSet.getString("UPDATE_TIME"));
- table.setCollate(resultSet.getString("TABLE_COLLATION"));
+ table.setName(resultSet.getString("table_name"));
+ //table.setEngine(resultSet.getString("ENGINE"));
+ //table.setRows(resultSet.getLong("TABLE_ROWS"));
+ //table.setDataLength(resultSet.getLong("DATA_LENGTH"));
+ //table.setCreateTime(resultSet.getString("CREATE_TIME"));
+ //table.setUpdateTime(resultSet.getString("UPDATE_TIME"));
+ //table.setCollate(resultSet.getString("TABLE_COLLATION"));
table.setComment(resultSet.getString("TABLE_COMMENT"));
tables.add(table);
}
@@ -63,157 +69,46 @@ public List tables(Connection connection, @NotEmpty String databaseName,
@Override
public String tableDDL(Connection connection, @NotEmpty String databaseName, String schemaName,
@NotEmpty String tableName) {
- String sql = "SHOW CREATE TABLE " + format(databaseName) + "."
- + format(tableName);
+ String sql = "SELECT sql FROM duckdb_tables() WHERE database_name = " + format(databaseName)
+ + " AND schema_name = " + format(schemaName) + " AND table_name = " + format(tableName);
return SQLExecutor.getInstance().execute(connection, sql, resultSet -> {
if (resultSet.next()) {
- return resultSet.getString("Create Table");
+ return resultSet.getString("sql");
}
return null;
});
}
public static String format(String tableName) {
- return "`" + tableName + "`";
- }
-
- private static String ROUTINES_SQL
- =
- "SELECT SPECIFIC_NAME, ROUTINE_COMMENT, ROUTINE_DEFINITION FROM information_schema.routines WHERE "
- + "routine_type = '%s' AND ROUTINE_SCHEMA ='%s' AND "
- + "routine_name = '%s';";
-
- @Override
- public Function function(Connection connection, @NotEmpty String databaseName, String schemaName,
- String functionName) {
-
- String functionInfoSql = String.format(ROUTINES_SQL, "FUNCTION", databaseName, functionName);
- Function function = SQLExecutor.getInstance().execute(connection, functionInfoSql, resultSet -> {
- Function f = new Function();
- f.setDatabaseName(databaseName);
- f.setSchemaName(schemaName);
- f.setFunctionName(functionName);
- if (resultSet.next()) {
- f.setSpecificName(resultSet.getString("SPECIFIC_NAME"));
- f.setRemarks(resultSet.getString("ROUTINE_COMMENT"));
- }
- return f;
- });
- String functionDDlSql = String.format("SHOW CREATE FUNCTION %s", functionName);
- SQLExecutor.getInstance().execute(connection, functionDDlSql, resultSet -> {
- if (resultSet.next()) {
- function.setFunctionBody(resultSet.getString("Create Function"));
- }
- });
- return function;
-
- }
-
- private static String TRIGGER_SQL
- = "SELECT TRIGGER_NAME,EVENT_MANIPULATION, ACTION_STATEMENT FROM INFORMATION_SCHEMA.TRIGGERS where "
- + "TRIGGER_SCHEMA = '%s' AND TRIGGER_NAME = '%s';";
-
- private static String TRIGGER_SQL_LIST
- = "SELECT TRIGGER_NAME FROM INFORMATION_SCHEMA.TRIGGERS where TRIGGER_SCHEMA = '%s';";
-
- @Override
- public List triggers(Connection connection, String databaseName, String schemaName) {
- List triggers = new ArrayList<>();
- String sql = String.format(TRIGGER_SQL_LIST, databaseName);
- return SQLExecutor.getInstance().execute(connection, sql, resultSet -> {
- while (resultSet.next()) {
- Trigger trigger = new Trigger();
- trigger.setTriggerName(resultSet.getString("TRIGGER_NAME"));
- trigger.setSchemaName(schemaName);
- trigger.setDatabaseName(databaseName);
- triggers.add(trigger);
- }
- return triggers;
- });
- }
-
-
- @Override
- public Trigger trigger(Connection connection, @NotEmpty String databaseName, String schemaName,
- String triggerName) {
-
- String sql = String.format(TRIGGER_SQL, databaseName, triggerName);
- return SQLExecutor.getInstance().execute(connection, sql, resultSet -> {
- Trigger trigger = new Trigger();
- trigger.setDatabaseName(databaseName);
- trigger.setSchemaName(schemaName);
- trigger.setTriggerName(triggerName);
- if (resultSet.next()) {
- trigger.setTriggerBody(resultSet.getString("ACTION_STATEMENT"));
- }
- return trigger;
- });
- }
-
- @Override
- public List procedures(Connection connection, String databaseName, String schemaName) {
- String sql = "SHOW PROCEDURE STATUS WHERE Db = DATABASE()";
- return SQLExecutor.getInstance().execute(connection, sql, resultSet -> {
- ArrayList procedures = new ArrayList<>();
- while (resultSet.next()) {
- Procedure procedure = new Procedure();
- procedure.setProcedureName(resultSet.getString("Name"));
- procedures.add(procedure);
- }
- return procedures;
- });
- }
-
- @Override
- public Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName,
- String procedureName) {
- String routinesSql = String.format(ROUTINES_SQL, "PROCEDURE", databaseName, procedureName);
- String showCreateProcedureSql = "SHOW CREATE PROCEDURE " + procedureName;
- Procedure procedure = SQLExecutor.getInstance().execute(connection, routinesSql, resultSet -> {
- Procedure p = new Procedure();
- p.setDatabaseName(databaseName);
- p.setSchemaName(schemaName);
- p.setProcedureName(procedureName);
- if (resultSet.next()) {
- p.setSpecificName(resultSet.getString("SPECIFIC_NAME"));
- p.setRemarks(resultSet.getString("ROUTINE_COMMENT"));
- }
- return p;
- });
- SQLExecutor.getInstance().execute(connection, showCreateProcedureSql, resultSet -> {
- if (resultSet.next()) {
- procedure.setProcedureBody(resultSet.getString("Create Procedure"));
- }
- });
- return procedure;
+ return "'" + tableName + "'";
}
private static String SELECT_TABLE_COLUMNS = "SELECT * FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s' order by ORDINAL_POSITION";
@Override
public List columns(Connection connection, String databaseName, String schemaName, String tableName) {
- String sql = String.format(SELECT_TABLE_COLUMNS, databaseName, tableName);
+ String sql = String.format(SELECT_TABLE_COLUMNS, schemaName, tableName);
List tableColumns = new ArrayList<>();
return SQLExecutor.getInstance().execute(connection, sql, resultSet -> {
while (resultSet.next()) {
TableColumn column = new TableColumn();
column.setDatabaseName(databaseName);
column.setTableName(tableName);
- column.setOldName(resultSet.getString("COLUMN_NAME"));
- column.setName(resultSet.getString("COLUMN_NAME"));
+ column.setOldName(resultSet.getString("column_name"));
+ column.setName(resultSet.getString("column_name"));
//column.setColumnType(resultSet.getString("COLUMN_TYPE"));
- column.setColumnType(resultSet.getString("DATA_TYPE").toUpperCase());
+ column.setColumnType(resultSet.getString("data_type").toUpperCase());
//column.setDataType(resultSet.getInt("DATA_TYPE"));
- column.setDefaultValue(resultSet.getString("COLUMN_DEFAULT"));
- column.setAutoIncrement(resultSet.getString("EXTRA").contains("auto_increment"));
+ column.setDefaultValue(resultSet.getString("column_default"));
+ //column.setAutoIncrement(resultSet.getString("EXTRA").contains("auto_increment"));
column.setComment(resultSet.getString("COLUMN_COMMENT"));
- column.setPrimaryKey("PRI".equalsIgnoreCase(resultSet.getString("COLUMN_KEY")));
- column.setNullable("YES".equalsIgnoreCase(resultSet.getString("IS_NULLABLE")) ? 1 : 0);
- column.setOrdinalPosition(resultSet.getInt("ORDINAL_POSITION"));
- column.setDecimalDigits(resultSet.getInt("NUMERIC_SCALE"));
- column.setCharSetName(resultSet.getString("CHARACTER_SET_NAME"));
- column.setCollationName(resultSet.getString("COLLATION_NAME"));
- setColumnSize(column, resultSet.getString("COLUMN_TYPE"));
+ //column.setPrimaryKey("PRI".equalsIgnoreCase(resultSet.getString("COLUMN_KEY")));
+ column.setNullable("YES".equalsIgnoreCase(resultSet.getString("is_nullable")) ? 1 : 0);
+ column.setOrdinalPosition(resultSet.getInt("ordinal_position"));
+ column.setDecimalDigits(resultSet.getInt("numeric_precision"));
+ column.setCharSetName(resultSet.getString("character_set_name"));
+ column.setCollationName(resultSet.getString("collation_name"));
+ setColumnSize(column, resultSet.getString("data_type"));
tableColumns.add(column);
}
return tableColumns;
@@ -244,18 +139,18 @@ private void setColumnSize(TableColumn column, String columnType) {
}
}
- private static String VIEW_DDL_SQL = "show create view %s";
+ private static String VIEW_DDL_SQL = "SELECT sql FROM duckdb_views() WHERE database_name = '%s' AND schema_name = '%s' AND view_name = '%s'";
@Override
public Table view(Connection connection, String databaseName, String schemaName, String viewName) {
- String sql = String.format(VIEW_DDL_SQL, viewName);
+ String sql = String.format(VIEW_DDL_SQL, databaseName, schemaName, viewName);
return SQLExecutor.getInstance().execute(connection, sql, resultSet -> {
Table table = new Table();
table.setDatabaseName(databaseName);
table.setSchemaName(schemaName);
table.setName(viewName);
if (resultSet.next()) {
- table.setDdl(resultSet.getString("Create View"));
+ table.setDdl(resultSet.getString("sql"));
}
return table;
});
@@ -264,10 +159,10 @@ public Table view(Connection connection, String databaseName, String schemaName,
@Override
public List indexes(Connection connection, String databaseName, String schemaName, String tableName) {
- StringBuilder queryBuf = new StringBuilder("SHOW INDEX FROM ");
- queryBuf.append("`").append(tableName).append("`");
- queryBuf.append(" FROM ");
- queryBuf.append("`").append(databaseName).append("`");
+ StringBuilder queryBuf = new StringBuilder("SELECT * FROM duckdb_indexes WHERE schema_name = ");
+ queryBuf.append("'").append(schemaName).append("'");
+ queryBuf.append(" and table_name = ");
+ queryBuf.append("'").append(tableName).append("'");
return SQLExecutor.getInstance().execute(connection, queryBuf.toString(), resultSet -> {
LinkedHashMap map = new LinkedHashMap();
while (resultSet.next()) {
@@ -339,7 +234,7 @@ public TableMeta getTableMeta(String databaseName, String schemaName, String tab
@Override
public String getMetaDataName(String... names) {
- return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> "`" + name + "`").collect(Collectors.joining("."));
+ return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).collect(Collectors.joining("."));
}
// @Override
diff --git a/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/builder/DuckDBSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/builder/DuckDBSqlBuilder.java
index d6474d9b8..6a981ad6c 100644
--- a/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/builder/DuckDBSqlBuilder.java
+++ b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/builder/DuckDBSqlBuilder.java
@@ -22,10 +22,10 @@ public class DuckDBSqlBuilder extends DefaultSqlBuilder {
public String buildCreateTableSql(Table table) {
StringBuilder script = new StringBuilder();
script.append("CREATE TABLE ");
- if (StringUtils.isNotBlank(table.getDatabaseName())) {
- script.append("`").append(table.getDatabaseName()).append("`").append(".");
+ if (StringUtils.isNotBlank(table.getSchemaName())) {
+ script.append(table.getSchemaName()).append(".");
}
- script.append("`").append(table.getName()).append("`").append(" (").append("\n");
+ script.append(table.getName()).append(" (").append("\n");
// append column
for (TableColumn column : table.getColumnList()) {
@@ -48,36 +48,18 @@ public String buildCreateTableSql(Table table) {
if (mysqlIndexTypeEnum == null) {
continue;
}
- script.append("\t").append("").append(mysqlIndexTypeEnum.buildIndexScript(tableIndex)).append(",\n");
+ script.append("\t").append(mysqlIndexTypeEnum.buildCreateIndexScript(tableIndex)).append(",\n");
}
script = new StringBuilder(script.substring(0, script.length() - 2));
- script.append("\n)");
+ script.append("\n);\n");
- if (StringUtils.isNotBlank(table.getEngine())) {
- script.append(" ENGINE=").append(table.getEngine());
- }
-
- if (StringUtils.isNotBlank(table.getCharset())) {
- script.append(" DEFAULT CHARACTER SET=").append(table.getCharset());
- }
-
- if (StringUtils.isNotBlank(table.getCollate())) {
- script.append(" COLLATE=").append(table.getCollate());
- }
-
- if (table.getIncrementValue() != null) {
- script.append(" AUTO_INCREMENT=").append(table.getIncrementValue());
- }
-
if (StringUtils.isNotBlank(table.getComment())) {
- script.append(" COMMENT='").append(table.getComment()).append("'");
+ script.append(" COMMENT ON TABLE ").append(table.getSchemaName()).append(".").append(table.getName())
+ .append(" IS '").append(table.getComment()).append("'");
}
- if (StringUtils.isNotBlank(table.getPartition())) {
- script.append(" \n").append(table.getPartition());
- }
script.append(";");
return script.toString();
@@ -86,88 +68,55 @@ public String buildCreateTableSql(Table table) {
@Override
public String buildModifyTaleSql(Table oldTable, Table newTable) {
StringBuilder tableBuilder = new StringBuilder();
- tableBuilder.append("ALTER TABLE ");
- if (StringUtils.isNotBlank(oldTable.getDatabaseName())) {
- tableBuilder.append("`").append(oldTable.getDatabaseName()).append("`").append(".");
- }
- tableBuilder.append("`").append(oldTable.getName()).append("`").append("\n");
- StringBuilder script = new StringBuilder();
if (!StringUtils.equalsIgnoreCase(oldTable.getName(), newTable.getName())) {
- script.append("\t").append("RENAME TO ").append("`").append(newTable.getName()).append("`").append(",\n");
- }
- if (!StringUtils.equalsIgnoreCase(oldTable.getComment(), newTable.getComment())) {
- script.append("\t").append("COMMENT=").append("'").append(newTable.getComment()).append("'").append(",\n");
- }
- if (oldTable.getIncrementValue() != newTable.getIncrementValue()) {
- script.append("\t").append("AUTO_INCREMENT=").append(newTable.getIncrementValue()).append(",\n");
+ tableBuilder.append("ALTER TABLE ").append(oldTable.getSchemaName()).append(".").append(oldTable.getName())
+ .append(" RENAME TO ").append("'").append(newTable.getName()).append("'").append(";\n");
}
- // 判断新增字段
- List addColumnList = new ArrayList<>();
- for (TableColumn tableColumn : newTable.getColumnList()) {
- if (tableColumn.getEditStatus() != null ? tableColumn.getEditStatus().equals("ADD") : false) {
- addColumnList.add(tableColumn);
- }
+ if (!StringUtils.equalsIgnoreCase(oldTable.getComment(), newTable.getComment())) {
+ tableBuilder.append("COMMENT ON TABLE ").append(oldTable.getSchemaName()).append(".").append(oldTable.getName())
+ .append(" IS ").append("'").append(newTable.getComment()).append("'").append(";\n");
}
- // 判断移动的字段
- List moveColumnList = movedElements(oldTable.getColumnList(), newTable.getColumnList());
// append modify column
for (TableColumn tableColumn : newTable.getColumnList()) {
if ((StringUtils.isNotBlank(tableColumn.getEditStatus()) && StringUtils.isNotBlank(tableColumn.getColumnType())
- && StringUtils.isNotBlank(tableColumn.getName())) || moveColumnList.contains(tableColumn) || addColumnList.contains(tableColumn)) {
+ && StringUtils.isNotBlank(tableColumn.getName()))) {
DuckDBColumnTypeEnum typeEnum = DuckDBColumnTypeEnum.getByType(tableColumn.getColumnType());
if (typeEnum == null) {
continue;
}
- if (moveColumnList.contains(tableColumn) || addColumnList.contains(tableColumn)) {
- script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(",\n");
- } else {
- script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(",\n");
- }
+ tableBuilder.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append("\n");
+
}
}
// append modify index
for (TableIndex tableIndex : newTable.getIndexList()) {
if (StringUtils.isNotBlank(tableIndex.getEditStatus()) && StringUtils.isNotBlank(tableIndex.getType())) {
- DuckDBIndexTypeEnum mysqlIndexTypeEnum = DuckDBIndexTypeEnum.getByType(tableIndex.getType());
- if (mysqlIndexTypeEnum == null) {
+ DuckDBIndexTypeEnum duckDBIndexTypeEnum = DuckDBIndexTypeEnum.getByType(tableIndex.getType());
+ if (duckDBIndexTypeEnum == null) {
continue;
}
- script.append("\t").append(mysqlIndexTypeEnum.buildModifyIndex(tableIndex)).append(",\n");
+ tableBuilder.append("\t").append(duckDBIndexTypeEnum.buildModifyIndex(tableIndex)).append(";\n");
}
}
// append reorder column
// script.append(buildGenerateReorderColumnSql(oldTable, newTable));
- if (script.length() > 2) {
- script = new StringBuilder(script.substring(0, script.length() - 2));
- script.append(";");
- return tableBuilder.append(script).toString();
+ if (tableBuilder.length() > 2) {
+ tableBuilder = new StringBuilder(tableBuilder.substring(0, tableBuilder.length() - 2));
+ tableBuilder.append(";");
+ return tableBuilder.toString();
} else {
return StringUtils.EMPTY;
}
}
- private String findPrevious(TableColumn tableColumn, Table newTable) {
- int index = newTable.getColumnList().indexOf(tableColumn);
- if (index == 0) {
- return "-1";
- }
- // Find the previous column that is not deleted
- for (int i = index - 1; i >= 0; i--) {
- if (newTable.getColumnList().get(i).getEditStatus() == null || !newTable.getColumnList().get(i).getEditStatus().equals(EditStatus.DELETE.name())) {
- return newTable.getColumnList().get(i).getName();
- }
- }
- return "-1";
- }
-
@Override
public String pageLimit(String sql, int offset, int pageNo, int pageSize) {
StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
@@ -188,7 +137,7 @@ public String pageLimit(String sql, int offset, int pageNo, int pageSize) {
@Override
public String buildCreateDatabaseSql(Database database) {
StringBuilder sqlBuilder = new StringBuilder();
- sqlBuilder.append("CREATE DATABASE `" + database.getName() + "`");
+ sqlBuilder.append("CREATE DATABASE " + database.getName());
if (StringUtils.isNotBlank(database.getCharset())) {
sqlBuilder.append(" DEFAULT CHARACTER SET=").append(database.getCharset());
}
@@ -198,214 +147,16 @@ public String buildCreateDatabaseSql(Database database) {
return sqlBuilder.toString();
}
- public static List movedElements(List original, List modified) {
- int[][] dp = new int[original.size() + 1][modified.size() + 1];
-
- // 构建DP表
- for (int i = 1; i <= original.size(); i++) {
- for (int j = 1; j <= modified.size(); j++) {
- if (original.get(i - 1).getName().equals(modified.get(j - 1).getOldName())) {
- dp[i][j] = dp[i - 1][j - 1] + 1;
- } else {
- dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
- }
- }
- }
-
- // 追踪LCS,找出移动了位置的元素
- List moved = new ArrayList<>();
- int i = original.size();
- int j = modified.size();
- while (i > 0 && j > 0) {
- if (original.get(i - 1).equals(modified.get(j - 1))) {
- i--;
- j--;
- } else if (dp[i - 1][j] >= dp[i][j - 1]) {
- moved.add(original.get(i - 1));
- // modified List中找到original.get(i-1)的位置
-// System.out.println("Moved elements:"+ original.get(i-1).getName() + " after " + modified.indexOf(original.get(i-1)) );
- i--;
- } else {
- j--;
- }
- }
-
- // 这里添加原始列表中未被包含在LCS中的元素
- while (i > 0) {
- moved.add(original.get(i - 1));
- i--;
- }
-
- return moved;
- }
-
- public String buildGenerateReorderColumnSql(Table oldTable, Table newTable) {
- StringBuilder sql = new StringBuilder();
- int n = 0;
- // Create a map to store the index of each column in the old table's column list
- Map oldColumnIndexMap = new HashMap<>();
- for (int i = 0; i < oldTable.getColumnList().size(); i++) {
- oldColumnIndexMap.put(oldTable.getColumnList().get(i).getName(), i);
- }
- String[] oldColumnArray = oldTable.getColumnList().stream().map(TableColumn::getName).toArray(String[]::new);
- String[] newColumnArray = newTable.getColumnList().stream().map(TableColumn::getName).toArray(String[]::new);
-
- Set oldColumnSet = new HashSet<>(Arrays.asList(oldColumnArray));
- Set newColumnSet = new HashSet<>(Arrays.asList(newColumnArray));
- if (!oldColumnSet.equals(newColumnSet)) {
- return "";
- }
-
- buildSql(oldColumnArray, newColumnArray, sql, oldTable, newTable, n);
-
- return sql.toString();
- }
-
- private String[] buildSql(String[] originalArray, String[] targetArray, StringBuilder sql, Table oldTable, Table newTable, int n) {
- // Complete the first move first
- if (!originalArray[0].equals(targetArray[0])) {
- int a = findIndex(originalArray, targetArray[0]);
- TableColumn column = oldTable.getColumnList().stream().filter(col -> StringUtils.equals(col.getName(), originalArray[a])).findFirst().get();
- String[] newArray = moveElement(originalArray, a, 0, targetArray, new AtomicInteger(0));
- sql.append(" MODIFY COLUMN ");
- DuckDBColumnTypeEnum typeEnum = DuckDBColumnTypeEnum.getByType(column.getColumnType());
- sql.append(typeEnum.buildModifyColumn(column));
- sql.append(" FIRST;\n");
- n++;
- if (Arrays.equals(newArray, targetArray)) {
- return newArray;
- }
- String[] resultArray = buildSql(newArray, targetArray, sql, oldTable, newTable, n);
- if (Arrays.equals(resultArray, targetArray)) {
- return resultArray;
- }
- }
-
- // After completing the last move
- int max = originalArray.length - 1;
- if (!originalArray[max].equals(targetArray[max])) {
- int a = findIndex(originalArray, targetArray[max]);
- //System.out.println("Move " + originalArray[a] + " after " + (a > 0 ? originalArray[max] : "start"));
- TableColumn column = oldTable.getColumnList().stream().filter(col -> StringUtils.equals(col.getName(), originalArray[a])).findFirst().get();
- String[] newArray = moveElement(originalArray, a, max, targetArray, new AtomicInteger(0));
- if (n > 0) {
- sql.append("ALTER TABLE ");
- if (StringUtils.isNotBlank(oldTable.getDatabaseName())) {
- sql.append("`").append(oldTable.getDatabaseName()).append("`").append(".");
- }
- sql.append("`").append(oldTable.getName()).append("`").append("\n");
- }
- sql.append(" MODIFY COLUMN ");
- DuckDBColumnTypeEnum typeEnum = DuckDBColumnTypeEnum.getByType(column.getColumnType());
- sql.append(typeEnum.buildModifyColumn(column));
- sql.append(" AFTER ");
- sql.append(oldTable.getColumnList().get(max).getName());
- sql.append(";\n");
- n++;
- if (Arrays.equals(newArray, targetArray)) {
- return newArray;
- }
- String[] resultArray = buildSql(newArray, targetArray, sql, oldTable, newTable, n);
- if (Arrays.equals(resultArray, targetArray)) {
- return resultArray;
- }
- }
-
-
- for (int i = 0; i < originalArray.length; i++) {
- int a = findIndex(targetArray, originalArray[i]);
- if (i != a && isMoveValid(originalArray, targetArray, i, a)) {
- // Find name a in oldTable.getColumnList
- int finalI = i;
- TableColumn column = oldTable.getColumnList().stream().filter(col -> StringUtils.equals(col.getName(), originalArray[finalI])).findFirst().get();
- if (n > 0) {
- sql.append("ALTER TABLE ");
- if (StringUtils.isNotBlank(oldTable.getDatabaseName())) {
- sql.append("`").append(oldTable.getDatabaseName()).append("`").append(".");
- }
- sql.append("`").append(oldTable.getName()).append("`").append("\n");
- }
- sql.append(" MODIFY COLUMN ");
- DuckDBColumnTypeEnum typeEnum = DuckDBColumnTypeEnum.getByType(column.getColumnType());
- sql.append(typeEnum.buildModifyColumn(column));
- sql.append(" AFTER ");
- AtomicInteger continuousDataCount = new AtomicInteger(0);
- String[] newArray = moveElement(originalArray, i, a, targetArray, continuousDataCount);
- if (i < a) {
- sql.append(originalArray[a + continuousDataCount.get()]);
- } else {
- sql.append(originalArray[a - 1]);
- }
-
- sql.append(";\n");
- n++;
-
- if (Arrays.equals(newArray, targetArray)) {
- return newArray;
- }
- String[] resultArray = buildSql(newArray, targetArray, sql, oldTable, newTable, n);
- if (Arrays.equals(resultArray, targetArray)) {
- return resultArray;
- }
- }
- }
- return null;
- }
-
- private static int findIndex(String[] array, String element) {
- for (int i = 0; i < array.length; i++) {
- if (array[i].equals(element)) {
- return i;
- }
- }
- return -1;
- }
-
- private static boolean isMoveValid(String[] originalArray, String[] targetArray, int i, int a) {
- return ((i == 0 || a == 0 || !originalArray[i - 1].equals(targetArray[a - 1])) &&
- (i >= originalArray.length - 1 || a >= targetArray.length - 1 || !originalArray[i + 1].equals(targetArray[a + 1])))
- || (i > 0 && a > 0 && !originalArray[i - 1].equals(targetArray[a - 1]));
- }
-
- private static String[] moveElement(String[] originalArray, int from, int to, String[] targetArray, AtomicInteger continuousDataCount) {
- String[] newArray = new String[originalArray.length];
- System.arraycopy(originalArray, 0, newArray, 0, originalArray.length);
- String temp = newArray[from];
- // 是否有连续移动数据
- boolean isContinuousData = false;
- // 连续数据数量
- if (from < to) {
- for (int i = to; i < originalArray.length - 1; i++) {
- if (originalArray[i + 1].equals(targetArray[findIndex(targetArray, originalArray[i]) + 1])) {
- continuousDataCount.set(continuousDataCount.incrementAndGet());
- } else {
- break;
- }
- }
- if (continuousDataCount.get() > 0) {
- System.arraycopy(originalArray, from + 1, newArray, from, to - from + 1);
- isContinuousData = true;
- } else {
- System.arraycopy(originalArray, from + 1, newArray, from, to - from);
- }
- } else {
- System.arraycopy(originalArray, to, newArray, to + 1, from - to);
- }
- if (isContinuousData) {
- newArray[to + continuousDataCount.get()] = temp;
- } else {
- newArray[to] = temp;
- }
- return newArray;
- }
-
@Override
protected void buildTableName(String databaseName, String schemaName, String tableName, StringBuilder script) {
if (StringUtils.isNotBlank(databaseName)) {
- script.append(SqlUtils.quoteObjectName(databaseName, "`")).append('.');
+ script.append(SqlUtils.quoteObjectName(databaseName, "'")).append('.');
+ }
+ if (StringUtils.isNotBlank(schemaName)) {
+ script.append(SqlUtils.quoteObjectName(schemaName, "'")).append('.');
}
- script.append(SqlUtils.quoteObjectName(tableName, "`"));
+ script.append(SqlUtils.quoteObjectName(tableName, "'"));
}
/**
diff --git a/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/type/DuckDBColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/type/DuckDBColumnTypeEnum.java
index 749f0d8c3..9b4374284 100644
--- a/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/type/DuckDBColumnTypeEnum.java
+++ b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/type/DuckDBColumnTypeEnum.java
@@ -184,7 +184,7 @@ public String buildCreateColumnSql(TableColumn column) {
}
StringBuilder script = new StringBuilder();
- script.append("\"").append(column.getName()).append("\"").append(" ");
+ script.append(column.getName()).append(" ");
script.append(buildDataType(column, type)).append(" ");
@@ -192,7 +192,7 @@ public String buildCreateColumnSql(TableColumn column) {
script.append(buildAutoIncrement(column,type)).append(" ");
- script.append(buildNullable(column, type)).append(" ");
+ script.append(buildCreateNullable(column, type)).append(" ");
return script.toString();
}
@@ -216,7 +216,18 @@ private String buildNullable(TableColumn column, DuckDBColumnTypeEnum type) {
return "";
}
if (column.getNullable() != null && 1 == column.getNullable()) {
- return "NULL";
+ return "DROP NOT NULL";
+ } else {
+ return "SET NOT NULL";
+ }
+ }
+
+ private String buildCreateNullable(TableColumn column, DuckDBColumnTypeEnum type) {
+ if (!type.getColumnType().isSupportNullable()) {
+ return "";
+ }
+ if (column.getNullable() != null && 1 == column.getNullable()) {
+ return "";
} else {
return "NOT NULL";
}
@@ -227,15 +238,18 @@ private String buildDefaultValue(TableColumn column, DuckDBColumnTypeEnum type)
return "";
}
+ StringBuilder script = new StringBuilder();
+ script.append("ALTER TABLE ").append(column.getSchemaName()).append(".").append(column.getTableName());
+ script.append(column.getOldName()).append(" ");
if ("EMPTY_STRING".equalsIgnoreCase(column.getDefaultValue().trim())) {
- return StringUtils.join("DEFAULT ''");
+ return script.append("SET DEFAULT '';\n").toString();
}
if ("NULL".equalsIgnoreCase(column.getDefaultValue().trim())) {
- return StringUtils.join("DEFAULT NULL");
+ return script.append("SET DEFAULT NULL;\n").toString();
}
- return StringUtils.join("DEFAULT ", column.getDefaultValue());
+ return script.append("SET DEFAULT ").append(column.getDefaultValue()).append(";\n").toString();
}
private String buildDataType(TableColumn column, DuckDBColumnTypeEnum type) {
@@ -275,32 +289,67 @@ private String buildDataType(TableColumn column, DuckDBColumnTypeEnum type) {
return columnType;
}
+ private String buildModifyDataType(TableColumn column, DuckDBColumnTypeEnum type) {
+ String columnType = type.columnType.getTypeName();
+ if (Arrays.asList(VARCHAR, STRING, BPCHAR, NVARCHAR, TEXT).contains(type)) {
+ StringBuilder script = new StringBuilder();
+ script.append(columnType);
+ if (column.getColumnSize() != null && StringUtils.isEmpty(column.getUnit())) {
+ script.append("(").append(column.getColumnSize()).append(")");
+ } else if (column.getColumnSize() != null && !StringUtils.isEmpty(column.getUnit())) {
+ script.append("(").append(column.getColumnSize()).append(" ").append(column.getUnit()).append(")");
+ }
+ return script.toString();
+ }
+
+ if (Arrays.asList(DECIMAL, FLOAT, TIMESTAMP).contains(type)) {
+ StringBuilder script = new StringBuilder();
+ script.append(columnType);
+ if (column.getColumnSize() != null && column.getDecimalDigits() == null) {
+ script.append("(").append(column.getColumnSize()).append(")");
+ } else if (column.getColumnSize() != null && column.getDecimalDigits() != null) {
+ script.append("(").append(column.getColumnSize()).append(",").append(column.getDecimalDigits()).append(")");
+ }
+ return script.toString();
+ }
+
+ if (Arrays.asList(TIME_WITH_TIME_ZONE, TIMSTAMP_US).contains(type)) {
+ StringBuilder script = new StringBuilder();
+ if (column.getColumnSize() == null) {
+ script.append(columnType);
+ } else {
+ String[] split = columnType.split("TIMESTAMP");
+ script.append("TIMESTAMP").append("(").append(column.getColumnSize()).append(")").append(split[1]);
+ }
+ return script.toString();
+ }
+ return columnType;
+ }
+
+
@Override
public String buildModifyColumn(TableColumn tableColumn) {
if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) {
StringBuilder script = new StringBuilder();
- script.append("ALTER TABLE ").append("\"").append(tableColumn.getSchemaName()).append("\".\"").append(tableColumn.getTableName()).append("\"");
- script.append(" ").append("DROP COLUMN ").append("\"").append(tableColumn.getName()).append("\"");
+ script.append("ALTER TABLE ").append(tableColumn.getSchemaName()).append(".").append(tableColumn.getTableName());
+ script.append(" ").append("DROP ").append(tableColumn.getName()).append(";\n");
return script.toString();
}
if (EditStatus.ADD.name().equals(tableColumn.getEditStatus())) {
StringBuilder script = new StringBuilder();
- script.append("ALTER TABLE ").append("\"").append(tableColumn.getSchemaName()).append("\".\"").append(tableColumn.getTableName()).append("\"");
- script.append(" ").append("ADD (").append(buildCreateColumnSql(tableColumn)).append(")");
+ script.append(buildModifyADDColumnSql(tableColumn)).append(";\n");
return script.toString();
}
if (EditStatus.MODIFY.name().equals(tableColumn.getEditStatus())) {
StringBuilder script = new StringBuilder();
- script.append("ALTER TABLE ").append("\"").append(tableColumn.getSchemaName()).append("\".\"").append(tableColumn.getTableName()).append("\"");
- script.append(" ").append("MODIFY (").append(buildCreateColumnSql(tableColumn)).append(") \n");
- if (!StringUtils.equalsIgnoreCase(tableColumn.getOldName(), tableColumn.getName())) {
- script.append(";");
- script.append("ALTER TABLE ").append("\"").append(tableColumn.getSchemaName()).append("\".\"").append(tableColumn.getTableName()).append("\"");
- script.append(" ").append("RENAME COLUMN ").append("\"").append(tableColumn.getOldName()).append("\"").append(" TO ").append("\"").append(tableColumn.getName()).append("\"");
+ script.append(buildModifyColumnSql(tableColumn, tableColumn.getOldColumn())).append(" \n");
+ if (!StringUtils.equalsIgnoreCase(tableColumn.getOldName(), tableColumn.getName())) {
+ script.append("ALTER TABLE ").append(tableColumn.getSchemaName()).append(".").append(tableColumn.getTableName());
+ script.append(" ").append("RENAME ").append(tableColumn.getOldName()).append(" TO ").append(tableColumn.getName()).append(";\n");
}
return script.toString();
@@ -308,6 +357,54 @@ public String buildModifyColumn(TableColumn tableColumn) {
return "";
}
+ public String buildModifyColumnSql(TableColumn column, TableColumn oldColumn) {
+ DuckDBColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType().toUpperCase());
+ if (type == null) {
+ return "";
+ }
+ StringBuilder script = new StringBuilder();
+
+ if (!column.getColumnType().equals(oldColumn.getColumnType())) {
+ script.append("ALTER TABLE ").append(column.getSchemaName()).append(".").append(column.getTableName()).append(" ");
+ script.append("ALTER ").append(oldColumn.getName()).append(" SET DATA TYPE ").append(buildModifyDataType(column, type)).append(";\n");
+ }
+
+ script.append(buildDefaultValue(column, type)).append(" ");
+
+ if (oldColumn.getNullable() != column.getNullable()) {
+ script.append("ALTER TABLE ").append(column.getSchemaName()).append(".").append(column.getTableName()).append(" ");
+ script.append("ALTER COLUMN ").append(column.getName()).append(" ").append(buildNullable(column, type)).append(";\n");
+ }
+
+ return script.toString();
+ }
+
+ public String buildModifyADDColumnSql(TableColumn column) {
+ DuckDBColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType().toUpperCase());
+ if (type == null) {
+ return "";
+ }
+ StringBuilder script = new StringBuilder();
+ script.append("ALTER TABLE ").append(column.getSchemaName()).append(".").append(column.getTableName());
+ script.append(" ").append("ADD COLUMN ").append(column.getName()).append(" ").append(buildModifyDataType(column, type));
+ if (!type.getColumnType().isSupportDefaultValue() || StringUtils.isEmpty(column.getDefaultValue())) {
+ return script.append(";\n").toString();
+ } else {
+ if ("EMPTY_STRING".equalsIgnoreCase(column.getDefaultValue().trim())) {
+ return script.append(" ").append("DEFAULT '';\n").toString();
+ }
+
+ if ("NULL".equalsIgnoreCase(column.getDefaultValue().trim())) {
+ return script.append(" ").append("DEFAULT NULL;\n").toString();
+ }
+ script.append(" ").append("DEFAULT ").append(column.getDefaultValue()).append(";\n");
+ }
+
+
+
+ return script.toString();
+ }
+
public static List getTypes() {
return Arrays.stream(DuckDBColumnTypeEnum.values()).map(columnTypeEnum ->
columnTypeEnum.getColumnType()
diff --git a/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/type/DuckDBIndexTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/type/DuckDBIndexTypeEnum.java
index 20e5ad388..eaea01003 100644
--- a/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/type/DuckDBIndexTypeEnum.java
+++ b/chat2db-server/chat2db-plugins/chat2db-duckdb/src/main/java/ai/chat2db/plugin/duckdb/type/DuckDBIndexTypeEnum.java
@@ -64,7 +64,7 @@ public static DuckDBIndexTypeEnum getByType(String type) {
public String buildIndexScript(TableIndex tableIndex) {
StringBuilder script = new StringBuilder();
if (PRIMARY_KEY.equals(this)) {
- script.append("ALTER TABLE \"").append(tableIndex.getSchemaName()).append("\".\"").append(tableIndex.getTableName()).append("\" ADD PRIMARY KEY ").append(buildIndexColumn(tableIndex));
+ script.append("ALTER TABLE ").append(tableIndex.getSchemaName()).append(".").append(tableIndex.getTableName()).append(" ADD PRIMARY KEY ").append(buildIndexColumn(tableIndex));
} else {
if (UNIQUE.equals(this)) {
script.append("CREATE UNIQUE INDEX ");
@@ -76,13 +76,27 @@ public String buildIndexScript(TableIndex tableIndex) {
return script.toString();
}
+ public String buildCreateIndexScript(TableIndex tableIndex) {
+ StringBuilder script = new StringBuilder();
+ if (PRIMARY_KEY.equals(this)) {
+ script.append("CONSTRAINT ").append(tableIndex.getTableName()).append("_").append("PK PRIMARY KEY ").append(buildIndexColumn(tableIndex));
+ } else {
+ if (UNIQUE.equals(this)) {
+ script.append("CREATE UNIQUE INDEX ");
+ } else {
+ script.append("CREATE INDEX ");
+ }
+ script.append(buildIndexName(tableIndex)).append(" ON \"").append(tableIndex.getSchemaName()).append("\".\"").append(tableIndex.getTableName()).append("\" ").append(buildIndexColumn(tableIndex));
+ }
+ return script.toString();
+ }
private String buildIndexColumn(TableIndex tableIndex) {
StringBuilder script = new StringBuilder();
script.append("(");
for (TableIndexColumn column : tableIndex.getColumnList()) {
if (StringUtils.isNotBlank(column.getColumnName())) {
- script.append("\"").append(column.getColumnName()).append("\"");
+ script.append(column.getColumnName());
if (!StringUtils.isBlank(column.getAscOrDesc()) && !PRIMARY_KEY.equals(this)) {
script.append(" ").append(column.getAscOrDesc());
}
@@ -113,7 +127,7 @@ public String buildModifyIndex(TableIndex tableIndex) {
private String buildDropIndex(TableIndex tableIndex) {
if (DuckDBIndexTypeEnum.PRIMARY_KEY.getName().equals(tableIndex.getType())) {
- String tableName = "\"" + tableIndex.getSchemaName() + "\"." + "\"" + tableIndex.getTableName() + "\"";
+ String tableName = "" + tableIndex.getSchemaName() + "." + tableIndex.getTableName() ;
return StringUtils.join("ALTER TABLE ",tableName," DROP PRIMARY KEY");
}
StringBuilder script = new StringBuilder();
diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java
index 18038e95a..9834cdf10 100644
--- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java
+++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java
@@ -424,7 +424,7 @@ private long addDBCache(Long dataSourceId, String databaseName, String schemaNam
Connection connection = Chat2DBContext.getConnection();
long n = 0;
try (ResultSet resultSet = connection.getMetaData().getTables(databaseName, schemaName, null,
- new String[]{"TABLE", "SYSTEM TABLE"})) {
+ new String[]{"TABLE", "SYSTEM TABLE", "BASE TABLE"})) {
List cacheDOS = new ArrayList<>();
while (resultSet.next()) {
TableCacheDO tableCacheDO = new TableCacheDO();