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 ![bang](https://oss.sqlgpt.cn/static/happy100.jpg) \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 ![bang](https://oss.sqlgpt.cn/static/happy100.jpg) \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 ![bang](https://oss.sqlgpt.cn/static/happy100.jpg) \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 ![bang](https://oss.sqlgpt.cn/static/happy100.jpg) \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 ![bang](https://oss.sqlgpt.cn/static/happy100.jpg) \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 ![bang](https://oss.sqlgpt.cn/static/happy100.jpg) \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 ![bang](https://oss.sqlgpt.cn/static/happy100.jpg) \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();