mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-06 20:03:05 +08:00
✨ feat(driver): 新增 OceanBase 与 OpenGauss Agent 数据源
- 数据源支持:新增 OceanBase 与 OpenGauss optional driver-agent 实现 - 连接适配:复用 MySQL/PostgreSQL 兼容链路并补齐查询、DDL、同步能力 - 前端入口:补充连接表单、侧边栏、图标、SQL 方言和危险操作识别 - 驱动管理:更新 driver manifest、安装提示和 revision 自动生成链路 - 构建发布:支持多平台 driver-agent 打包并优化 release 构建失败提示
This commit is contained in:
2
.github/workflows/dev-build.yml
vendored
2
.github/workflows/dev-build.yml
vendored
@@ -261,7 +261,7 @@ jobs:
|
||||
TARGET_PLATFORM="${{ matrix.platform }}"
|
||||
GOOS="${TARGET_PLATFORM%%/*}"
|
||||
GOARCH="${TARGET_PLATFORM##*/}"
|
||||
DRIVERS=(mariadb doris sphinx sqlserver sqlite duckdb dameng kingbase highgo vastbase mongodb tdengine clickhouse)
|
||||
DRIVERS=(mariadb oceanbase doris sphinx sqlserver sqlite duckdb dameng kingbase highgo vastbase opengauss mongodb tdengine clickhouse)
|
||||
OUTDIR="drivers/${{ matrix.os_name }}"
|
||||
mkdir -p "$OUTDIR"
|
||||
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -252,7 +252,7 @@ jobs:
|
||||
TARGET_PLATFORM="${{ matrix.platform }}"
|
||||
GOOS="${TARGET_PLATFORM%%/*}"
|
||||
GOARCH="${TARGET_PLATFORM##*/}"
|
||||
DRIVERS=(mariadb doris sphinx sqlserver sqlite duckdb dameng kingbase highgo vastbase mongodb tdengine clickhouse)
|
||||
DRIVERS=(mariadb oceanbase doris sphinx sqlserver sqlite duckdb dameng kingbase highgo vastbase opengauss mongodb tdengine clickhouse)
|
||||
OUTDIR="drivers/${{ matrix.os_name }}"
|
||||
mkdir -p "$OUTDIR"
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@ set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
DEFAULT_DRIVERS=(mariadb doris sphinx sqlserver sqlite duckdb dameng kingbase highgo vastbase mongodb tdengine clickhouse)
|
||||
DEFAULT_DRIVERS=(mariadb oceanbase doris sphinx sqlserver sqlite duckdb dameng kingbase highgo vastbase opengauss mongodb tdengine clickhouse)
|
||||
DEFAULT_PLATFORMS=(darwin/amd64 darwin/arm64 windows/amd64 windows/arm64 linux/amd64 linux/arm64)
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
@@ -14,8 +15,8 @@ usage() {
|
||||
|
||||
选项:
|
||||
--drivers <列表> 指定驱动列表(逗号分隔),例如:kingbase,mongodb
|
||||
--platform <GOOS/GOARCH>
|
||||
目标平台,默认使用当前 Go 环境(go env GOOS/GOARCH)
|
||||
--platform <目标> 目标平台:current、all、GOOS/GOARCH,或逗号分隔列表
|
||||
默认 current(当前 Go 环境)
|
||||
--out-dir <目录> 输出目录根路径,默认:dist/driver-agents
|
||||
--bundle-name <文件名> 驱动总包 zip 名称,默认:GoNavi-DriverAgents.zip
|
||||
--strict 任一驱动构建失败即中断(默认失败后继续,最后汇总)
|
||||
@@ -25,6 +26,8 @@ usage() {
|
||||
./build-driver-agents.sh
|
||||
./build-driver-agents.sh --drivers kingbase
|
||||
./build-driver-agents.sh --platform windows/amd64 --drivers kingbase,mongodb
|
||||
./build-driver-agents.sh --platform all
|
||||
./build-driver-agents.sh --platform darwin/arm64,windows/amd64,linux/amd64
|
||||
EOF
|
||||
}
|
||||
|
||||
@@ -33,7 +36,8 @@ normalize_driver() {
|
||||
name="$(echo "${1:-}" | tr '[:upper:]' '[:lower:]' | xargs)"
|
||||
case "$name" in
|
||||
doris|diros) echo "doris" ;;
|
||||
mariadb|sphinx|sqlserver|sqlite|duckdb|dameng|kingbase|highgo|vastbase|mongodb|tdengine|clickhouse)
|
||||
open_gauss|open-gauss) echo "opengauss" ;;
|
||||
mariadb|oceanbase|sphinx|sqlserver|sqlite|duckdb|dameng|kingbase|highgo|vastbase|opengauss|mongodb|tdengine|clickhouse)
|
||||
echo "$name"
|
||||
;;
|
||||
*)
|
||||
@@ -58,6 +62,83 @@ platform_dir_name() {
|
||||
esac
|
||||
}
|
||||
|
||||
current_platform() {
|
||||
echo "$(go env GOOS)/$(go env GOARCH)"
|
||||
}
|
||||
|
||||
append_platform() {
|
||||
local candidate
|
||||
candidate="$1"
|
||||
if [[ "$platform_seen" == *"|$candidate|"* ]]; then
|
||||
return 0
|
||||
fi
|
||||
platforms+=("$candidate")
|
||||
platform_seen="${platform_seen}${candidate}|"
|
||||
}
|
||||
|
||||
normalize_platform() {
|
||||
local value goos goarch platform_dir
|
||||
value="$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')"
|
||||
case "$value" in
|
||||
current|"")
|
||||
current_platform
|
||||
;;
|
||||
*/*)
|
||||
goos="${value%%/*}"
|
||||
goarch="${value##*/}"
|
||||
platform_dir="$(platform_dir_name "$goos")"
|
||||
if [[ -z "$goos" || -z "$goarch" || "$platform_dir" == "Unknown" ]]; then
|
||||
return 1
|
||||
fi
|
||||
echo "$goos/$goarch"
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
zip_bundle() {
|
||||
local bundle_zip_path="$1"
|
||||
local bundle_stage_dir="$2"
|
||||
local -a bundle_dirs=()
|
||||
local dir
|
||||
|
||||
for dir in "$bundle_stage_dir"/*; do
|
||||
[[ -d "$dir" ]] || continue
|
||||
bundle_dirs+=("$(basename "$dir")")
|
||||
done
|
||||
|
||||
if [[ ${#bundle_dirs[@]} -eq 0 ]]; then
|
||||
echo "❌ 驱动总包 staging 目录为空。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -f "$bundle_zip_path"
|
||||
if command -v zip >/dev/null 2>&1; then
|
||||
(
|
||||
cd "$bundle_stage_dir"
|
||||
zip -qry "$bundle_zip_path" "${bundle_dirs[@]}"
|
||||
)
|
||||
elif command -v python3 >/dev/null 2>&1; then
|
||||
BUNDLE_STAGE_DIR="$bundle_stage_dir" BUNDLE_ZIP_PATH="$bundle_zip_path" python3 - <<'PY'
|
||||
import os
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
stage = Path(os.environ["BUNDLE_STAGE_DIR"])
|
||||
target = Path(os.environ["BUNDLE_ZIP_PATH"])
|
||||
with zipfile.ZipFile(target, "w", compression=zipfile.ZIP_DEFLATED) as zf:
|
||||
for path in stage.rglob("*"):
|
||||
if path.is_file():
|
||||
zf.write(path, path.relative_to(stage).as_posix())
|
||||
PY
|
||||
else
|
||||
echo "❌ 未找到 zip 或 python3,无法生成驱动总包 zip。"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
driver_csv=""
|
||||
target_platform=""
|
||||
out_root="dist/driver-agents"
|
||||
@@ -103,20 +184,6 @@ if ! command -v go >/dev/null 2>&1; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$target_platform" ]]; then
|
||||
target_platform="$(go env GOOS)/$(go env GOARCH)"
|
||||
fi
|
||||
|
||||
if [[ "$target_platform" != */* ]]; then
|
||||
echo "❌ --platform 参数格式错误,应为 GOOS/GOARCH,例如 darwin/arm64"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
goos="${target_platform%%/*}"
|
||||
goarch="${target_platform##*/}"
|
||||
platform_key="${goos}-${goarch}"
|
||||
platform_dir="$(platform_dir_name "$goos")"
|
||||
|
||||
declare -a drivers=()
|
||||
if [[ -n "$driver_csv" ]]; then
|
||||
IFS=',' read -r -a raw_drivers <<<"$driver_csv"
|
||||
@@ -131,68 +198,114 @@ else
|
||||
drivers=("${DEFAULT_DRIVERS[@]}")
|
||||
fi
|
||||
|
||||
output_dir="${out_root%/}/${platform_key}"
|
||||
declare -a platforms=()
|
||||
platform_seen="|"
|
||||
if [[ -z "$target_platform" ]]; then
|
||||
target_platform="current"
|
||||
fi
|
||||
IFS=',' read -r -a raw_platforms <<<"$target_platform"
|
||||
for item in "${raw_platforms[@]}"; do
|
||||
normalized_platform="$(printf '%s' "$item" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')"
|
||||
if [[ "$normalized_platform" == "all" ]]; then
|
||||
for default_platform in "${DEFAULT_PLATFORMS[@]}"; do
|
||||
append_platform "$default_platform"
|
||||
done
|
||||
continue
|
||||
fi
|
||||
normalized_platform="$(normalize_platform "$item")" || {
|
||||
echo "❌ --platform 参数格式错误,应为 current、all、GOOS/GOARCH 或逗号分隔列表,例如 darwin/arm64,windows/amd64"
|
||||
exit 1
|
||||
}
|
||||
append_platform "$normalized_platform"
|
||||
done
|
||||
|
||||
if [[ ${#platforms[@]} -eq 0 ]]; then
|
||||
echo "❌ 未指定有效目标平台。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$out_root"
|
||||
out_root_abs="$(cd "$out_root" && pwd)"
|
||||
bundle_stage_dir="$(mktemp -d "${TMPDIR:-/tmp}/gonavi-driver-bundle.XXXXXX")"
|
||||
bundle_platform_dir="$bundle_stage_dir/$platform_dir"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$bundle_stage_dir"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
mkdir -p "$output_dir" "$bundle_platform_dir"
|
||||
output_dir_abs="$(cd "$output_dir" && pwd)"
|
||||
bundle_zip_path="$output_dir_abs/$bundle_name"
|
||||
if [[ ${#platforms[@]} -eq 1 ]]; then
|
||||
single_platform="${platforms[0]}"
|
||||
single_platform_key="${single_platform/\//-}"
|
||||
single_output_dir="${out_root%/}/$single_platform_key"
|
||||
mkdir -p "$single_output_dir"
|
||||
bundle_zip_path="$(cd "$single_output_dir" && pwd)/$bundle_name"
|
||||
else
|
||||
bundle_zip_path="$out_root_abs/$bundle_name"
|
||||
fi
|
||||
|
||||
declare -a built_assets=()
|
||||
declare -a failed_drivers=()
|
||||
declare -a skipped_drivers=()
|
||||
|
||||
echo "🚀 开始构建 optional-driver-agent"
|
||||
echo " 平台:$goos/$goarch"
|
||||
echo " 输出目录:$output_dir_abs"
|
||||
echo " 平台:${platforms[*]}"
|
||||
echo " 输出根目录:$out_root_abs"
|
||||
echo " 驱动列表:${drivers[*]}"
|
||||
echo "🧭 生成 driver-agent revision 指纹"
|
||||
"$SCRIPT_DIR/tools/generate-driver-agent-revisions.sh" --platform "$target_platform"
|
||||
|
||||
for driver in "${drivers[@]}"; do
|
||||
if [[ "$driver" == "duckdb" && "$goos" == "windows" && "$goarch" != "amd64" ]]; then
|
||||
echo "⚠️ 跳过 duckdb(仅支持 windows/amd64)"
|
||||
skipped_drivers+=("$driver")
|
||||
continue
|
||||
fi
|
||||
for platform in "${platforms[@]}"; do
|
||||
goos="${platform%%/*}"
|
||||
goarch="${platform##*/}"
|
||||
platform_key="${goos}-${goarch}"
|
||||
platform_dir="$(platform_dir_name "$goos")"
|
||||
output_dir="${out_root%/}/${platform_key}"
|
||||
bundle_platform_dir="$bundle_stage_dir/$platform_dir"
|
||||
|
||||
build_driver="$(build_driver_name "$driver")"
|
||||
tag="gonavi_${build_driver}_driver"
|
||||
asset_name="${driver}-driver-agent-${goos}-${goarch}"
|
||||
if [[ "$goos" == "windows" ]]; then
|
||||
asset_name="${asset_name}.exe"
|
||||
fi
|
||||
output_path="$output_dir_abs/$asset_name"
|
||||
mkdir -p "$output_dir" "$bundle_platform_dir"
|
||||
output_dir_abs="$(cd "$output_dir" && pwd)"
|
||||
|
||||
cgo_enabled=0
|
||||
if [[ "$driver" == "duckdb" ]]; then
|
||||
cgo_enabled=1
|
||||
fi
|
||||
echo ""
|
||||
echo "🧭 生成 driver-agent revision 指纹:$platform"
|
||||
"$SCRIPT_DIR/tools/generate-driver-agent-revisions.sh" --platform "$platform"
|
||||
|
||||
echo "🔧 构建 $driver -> $asset_name (tag=$tag, CGO_ENABLED=$cgo_enabled)"
|
||||
set +e
|
||||
CGO_ENABLED="$cgo_enabled" GOOS="$goos" GOARCH="$goarch" GOTOOLCHAIN=auto \
|
||||
go build -tags "$tag" -trimpath -ldflags "-s -w" -o "$output_path" ./cmd/optional-driver-agent
|
||||
build_exit=$?
|
||||
set -e
|
||||
|
||||
if [[ $build_exit -ne 0 ]]; then
|
||||
echo "❌ 构建失败:$driver"
|
||||
failed_drivers+=("$driver")
|
||||
if [[ "$strict_mode" == "true" ]]; then
|
||||
exit $build_exit
|
||||
for driver in "${drivers[@]}"; do
|
||||
if [[ "$driver" == "duckdb" && "$goos" == "windows" && "$goarch" != "amd64" ]]; then
|
||||
echo "⚠️ 跳过 duckdb($platform 仅支持 windows/amd64)"
|
||||
skipped_drivers+=("duckdb($platform)")
|
||||
continue
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
|
||||
cp "$output_path" "$bundle_platform_dir/$asset_name"
|
||||
built_assets+=("$asset_name")
|
||||
build_driver="$(build_driver_name "$driver")"
|
||||
tag="gonavi_${build_driver}_driver"
|
||||
asset_name="${driver}-driver-agent-${goos}-${goarch}"
|
||||
if [[ "$goos" == "windows" ]]; then
|
||||
asset_name="${asset_name}.exe"
|
||||
fi
|
||||
output_path="$output_dir_abs/$asset_name"
|
||||
|
||||
cgo_enabled=0
|
||||
if [[ "$driver" == "duckdb" ]]; then
|
||||
cgo_enabled=1
|
||||
fi
|
||||
|
||||
echo "🔧 构建 $driver -> $asset_name (platform=$platform, tag=$tag, CGO_ENABLED=$cgo_enabled)"
|
||||
set +e
|
||||
CGO_ENABLED="$cgo_enabled" GOOS="$goos" GOARCH="$goarch" GOTOOLCHAIN=auto \
|
||||
go build -tags "$tag" -trimpath -ldflags "-s -w" -o "$output_path" ./cmd/optional-driver-agent
|
||||
build_exit=$?
|
||||
set -e
|
||||
|
||||
if [[ $build_exit -ne 0 ]]; then
|
||||
echo "❌ 构建失败:$driver ($platform)"
|
||||
failed_drivers+=("$driver($platform)")
|
||||
if [[ "$strict_mode" == "true" ]]; then
|
||||
exit $build_exit
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
|
||||
cp "$output_path" "$bundle_platform_dir/$asset_name"
|
||||
built_assets+=("$platform_dir/$asset_name")
|
||||
done
|
||||
done
|
||||
|
||||
if [[ ${#built_assets[@]} -eq 0 ]]; then
|
||||
@@ -200,25 +313,11 @@ if [[ ${#built_assets[@]} -eq 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -f "$bundle_zip_path"
|
||||
if command -v zip >/dev/null 2>&1; then
|
||||
(
|
||||
cd "$bundle_stage_dir"
|
||||
zip -qry "$bundle_zip_path" "$platform_dir"
|
||||
)
|
||||
elif command -v ditto >/dev/null 2>&1; then
|
||||
(
|
||||
cd "$bundle_stage_dir"
|
||||
ditto -c -k --sequesterRsrc --keepParent "$platform_dir" "$bundle_zip_path"
|
||||
)
|
||||
else
|
||||
echo "❌ 未找到 zip/ditto,无法生成驱动总包 zip。"
|
||||
exit 1
|
||||
fi
|
||||
zip_bundle "$bundle_zip_path" "$bundle_stage_dir"
|
||||
|
||||
echo ""
|
||||
echo "✅ 构建完成"
|
||||
echo " 单文件输出目录:$output_dir_abs"
|
||||
echo " 单文件输出根目录:$out_root_abs"
|
||||
echo " 驱动总包:$bundle_zip_path"
|
||||
echo " 已构建:${built_assets[*]}"
|
||||
if [[ ${#skipped_drivers[@]} -gt 0 ]]; then
|
||||
|
||||
@@ -46,6 +46,13 @@ RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
BUILD_FAILURES=()
|
||||
|
||||
record_build_failure() {
|
||||
local target="$1"
|
||||
BUILD_FAILURES+=("$target")
|
||||
}
|
||||
|
||||
get_file_size_bytes() {
|
||||
local target="$1"
|
||||
if [ ! -f "$target" ]; then
|
||||
@@ -159,6 +166,7 @@ package_macos_release() {
|
||||
wails build -platform "darwin/${platform}" -clean -ldflags "$LDFLAGS"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED} ❌ macOS ${platform} 构建失败。${NC}"
|
||||
record_build_failure "macOS ${platform}"
|
||||
return
|
||||
fi
|
||||
|
||||
@@ -213,6 +221,7 @@ if command -v x86_64-w64-mingw32-gcc &> /dev/null; then
|
||||
echo " ✅ 已生成 ${APP_NAME}-${VERSION}-windows-amd64.exe"
|
||||
else
|
||||
echo -e "${RED} ❌ Windows amd64 构建失败。${NC}"
|
||||
record_build_failure "Windows amd64"
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW} ⚠️ 未找到 MinGW 工具 (x86_64-w64-mingw32-gcc),跳过 Windows amd64 构建。${NC}"
|
||||
@@ -230,6 +239,7 @@ if command -v aarch64-w64-mingw32-gcc &> /dev/null; then
|
||||
echo " ✅ 已生成 ${APP_NAME}-${VERSION}-windows-arm64.exe"
|
||||
else
|
||||
echo -e "${RED} ❌ Windows arm64 构建失败。${NC}"
|
||||
record_build_failure "Windows arm64"
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW} ⚠️ 未找到 MinGW ARM64 工具 (aarch64-w64-mingw32-gcc),跳过 Windows arm64 构建。${NC}"
|
||||
@@ -259,6 +269,7 @@ if [ "$CURRENT_OS" = "Linux" ] && [ "$CURRENT_ARCH" = "x86_64" ]; then
|
||||
echo " ✅ 已生成 ${APP_NAME}-${VERSION}-linux-amd64.tar.gz"
|
||||
else
|
||||
echo -e "${RED} ❌ Linux amd64 构建失败。${NC}"
|
||||
record_build_failure "Linux amd64"
|
||||
fi
|
||||
elif command -v x86_64-linux-gnu-gcc &> /dev/null; then
|
||||
# macOS 或其他系统,尝试交叉编译
|
||||
@@ -279,6 +290,7 @@ elif command -v x86_64-linux-gnu-gcc &> /dev/null; then
|
||||
echo " ✅ 已生成 ${APP_NAME}-${VERSION}-linux-amd64.tar.gz"
|
||||
else
|
||||
echo -e "${RED} ❌ Linux amd64 交叉编译失败。${NC}"
|
||||
record_build_failure "Linux amd64"
|
||||
fi
|
||||
unset CC CXX CGO_ENABLED
|
||||
else
|
||||
@@ -304,6 +316,7 @@ if [ "$CURRENT_OS" = "Linux" ] && [ "$CURRENT_ARCH" = "aarch64" ]; then
|
||||
echo " ✅ 已生成 ${APP_NAME}-${VERSION}-linux-arm64.tar.gz"
|
||||
else
|
||||
echo -e "${RED} ❌ Linux arm64 构建失败。${NC}"
|
||||
record_build_failure "Linux arm64"
|
||||
fi
|
||||
elif command -v aarch64-linux-gnu-gcc &> /dev/null; then
|
||||
# 交叉编译
|
||||
@@ -324,6 +337,7 @@ elif command -v aarch64-linux-gnu-gcc &> /dev/null; then
|
||||
echo " ✅ 已生成 ${APP_NAME}-${VERSION}-linux-arm64.tar.gz"
|
||||
else
|
||||
echo -e "${RED} ❌ Linux arm64 交叉编译失败。${NC}"
|
||||
record_build_failure "Linux arm64"
|
||||
fi
|
||||
unset CC CXX CGO_ENABLED
|
||||
else
|
||||
@@ -357,12 +371,21 @@ else
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}🎉 所有任务完成!构建产物在 'dist/' 目录下:${NC}"
|
||||
if [ "${#BUILD_FAILURES[@]}" -gt 0 ]; then
|
||||
echo -e "${RED}❌ 构建未完全成功,失败平台:${BUILD_FAILURES[*]}${NC}"
|
||||
echo -e "${YELLOW}📦 已成功生成的产物在 'dist/' 目录下:${NC}"
|
||||
else
|
||||
echo -e "${GREEN}🎉 所有任务完成!构建产物在 'dist/' 目录下:${NC}"
|
||||
fi
|
||||
ls -lh "$DIST_DIR"
|
||||
echo ""
|
||||
echo -e "${GREEN}📋 支持的平台:${NC}"
|
||||
echo " • macOS (Intel/Apple Silicon): .dmg"
|
||||
echo " • macOS (Intel/Apple Silicon): .zip"
|
||||
echo " • Windows (x64/ARM64): .exe"
|
||||
echo " • Linux (x64/ARM64): .tar.gz"
|
||||
echo ""
|
||||
echo -e "${YELLOW}💡 提示:Linux AppImage 包请使用 GitHub Actions CI/CD 构建。${NC}"
|
||||
|
||||
if [ "${#BUILD_FAILURES[@]}" -gt 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
12
cmd/optional-driver-agent/provider_oceanbase.go
Normal file
12
cmd/optional-driver-agent/provider_oceanbase.go
Normal file
@@ -0,0 +1,12 @@
|
||||
//go:build gonavi_oceanbase_driver
|
||||
|
||||
package main
|
||||
|
||||
import "GoNavi-Wails/internal/db"
|
||||
|
||||
func init() {
|
||||
agentDriverType = "oceanbase"
|
||||
agentDatabaseFactory = func() db.Database {
|
||||
return &db.OceanBaseDB{}
|
||||
}
|
||||
}
|
||||
12
cmd/optional-driver-agent/provider_opengauss.go
Normal file
12
cmd/optional-driver-agent/provider_opengauss.go
Normal file
@@ -0,0 +1,12 @@
|
||||
//go:build gonavi_opengauss_driver
|
||||
|
||||
package main
|
||||
|
||||
import "GoNavi-Wails/internal/db"
|
||||
|
||||
func init() {
|
||||
agentDriverType = "opengauss"
|
||||
agentDatabaseFactory = func() db.Database {
|
||||
return &db.OpenGaussDB{}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,12 @@
|
||||
"checksumPolicy": "off",
|
||||
"downloadUrl": "builtin://activate/mariadb"
|
||||
},
|
||||
"oceanbase": {
|
||||
"engine": "go",
|
||||
"version": "1.9.3",
|
||||
"checksumPolicy": "off",
|
||||
"downloadUrl": "builtin://activate/oceanbase"
|
||||
},
|
||||
"doris": {
|
||||
"engine": "go",
|
||||
"version": "1.9.3",
|
||||
@@ -61,6 +67,12 @@
|
||||
"checksumPolicy": "off",
|
||||
"downloadUrl": "builtin://activate/vastbase"
|
||||
},
|
||||
"opengauss": {
|
||||
"engine": "go",
|
||||
"version": "1.11.1",
|
||||
"checksumPolicy": "off",
|
||||
"downloadUrl": "builtin://activate/opengauss"
|
||||
},
|
||||
"mongodb": {
|
||||
"engine": "go",
|
||||
"version": "2.5.0",
|
||||
|
||||
@@ -1 +1 @@
|
||||
d0464f9da25e9356e61652e638c99ffe
|
||||
0295a42fd931778d85157816d79d29e5
|
||||
@@ -154,6 +154,8 @@ const getDefaultPortByType = (type: string) => {
|
||||
return 9010;
|
||||
case "mysql":
|
||||
return 3306;
|
||||
case "oceanbase":
|
||||
return 2881;
|
||||
case "doris":
|
||||
case "diros":
|
||||
return 9030;
|
||||
@@ -162,6 +164,7 @@ const getDefaultPortByType = (type: string) => {
|
||||
case "clickhouse":
|
||||
return 9000;
|
||||
case "postgres":
|
||||
case "opengauss":
|
||||
return 5432;
|
||||
case "redis":
|
||||
return 6379;
|
||||
@@ -194,6 +197,7 @@ const getDefaultPortByType = (type: string) => {
|
||||
|
||||
const singleHostUriSchemesByType: Record<string, string[]> = {
|
||||
postgres: ["postgresql", "postgres"],
|
||||
opengauss: ["opengauss", "jdbc:opengauss", "postgresql", "postgres"],
|
||||
clickhouse: ["clickhouse"],
|
||||
oracle: ["oracle"],
|
||||
sqlserver: ["sqlserver"],
|
||||
@@ -208,6 +212,7 @@ const singleHostUriSchemesByType: Record<string, string[]> = {
|
||||
const sslSupportedTypes = new Set([
|
||||
"mysql",
|
||||
"mariadb",
|
||||
"oceanbase",
|
||||
"doris",
|
||||
"diros",
|
||||
"sphinx",
|
||||
@@ -219,6 +224,7 @@ const sslSupportedTypes = new Set([
|
||||
"kingbase",
|
||||
"highgo",
|
||||
"vastbase",
|
||||
"opengauss",
|
||||
"mongodb",
|
||||
"redis",
|
||||
"tdengine",
|
||||
@@ -237,6 +243,7 @@ const isFileDatabaseType = (type: string) =>
|
||||
const isMySQLCompatibleType = (type: string) =>
|
||||
type === "mysql" ||
|
||||
type === "mariadb" ||
|
||||
type === "oceanbase" ||
|
||||
type === "doris" ||
|
||||
type === "diros" ||
|
||||
type === "sphinx";
|
||||
@@ -247,6 +254,7 @@ const supportsConnectionParamsForType = (type: string) =>
|
||||
type === "kingbase" ||
|
||||
type === "highgo" ||
|
||||
type === "vastbase" ||
|
||||
type === "opengauss" ||
|
||||
type === "oracle" ||
|
||||
type === "sqlserver" ||
|
||||
type === "clickhouse" ||
|
||||
@@ -271,6 +279,12 @@ const normalizeDriverType = (value: string): string => {
|
||||
.toLowerCase();
|
||||
if (normalized === "postgresql") return "postgres";
|
||||
if (normalized === "doris") return "diros";
|
||||
if (
|
||||
normalized === "open_gauss" ||
|
||||
normalized === "open-gauss" ||
|
||||
normalized === "opengauss"
|
||||
)
|
||||
return "opengauss";
|
||||
return normalized;
|
||||
};
|
||||
|
||||
@@ -1276,6 +1290,8 @@ const ConnectionModal: React.FC<{
|
||||
const parsed =
|
||||
parseMultiHostUri(trimmedUri, "mysql") ||
|
||||
parseMultiHostUri(trimmedUri, "jdbc:mysql") ||
|
||||
parseMultiHostUri(trimmedUri, "oceanbase") ||
|
||||
parseMultiHostUri(trimmedUri, "jdbc:oceanbase") ||
|
||||
parseMultiHostUri(trimmedUri, "diros") ||
|
||||
parseMultiHostUri(trimmedUri, "doris");
|
||||
if (!parsed) {
|
||||
@@ -1524,7 +1540,8 @@ const ConnectionModal: React.FC<{
|
||||
type === "postgres" ||
|
||||
type === "kingbase" ||
|
||||
type === "highgo" ||
|
||||
type === "vastbase"
|
||||
type === "vastbase" ||
|
||||
type === "opengauss"
|
||||
) {
|
||||
const sslMode = String(parsed.params.get("sslmode") || "")
|
||||
.trim()
|
||||
@@ -1681,7 +1698,8 @@ const ConnectionModal: React.FC<{
|
||||
const getUriPlaceholder = () => {
|
||||
if (isMySQLCompatibleType(dbType)) {
|
||||
const defaultPort = getDefaultPortByType(dbType);
|
||||
const scheme = dbType === "diros" ? "doris" : "mysql";
|
||||
const scheme =
|
||||
dbType === "diros" ? "doris" : dbType === "oceanbase" ? "oceanbase" : "mysql";
|
||||
return `${scheme}://user:pass@127.0.0.1:${defaultPort},127.0.0.2:${defaultPort}/db_name?topology=replica`;
|
||||
}
|
||||
if (isFileDatabaseType(dbType)) {
|
||||
@@ -1701,6 +1719,9 @@ const ConnectionModal: React.FC<{
|
||||
if (dbType === "oracle") {
|
||||
return "oracle://user:pass@127.0.0.1:1521/ORCLPDB1";
|
||||
}
|
||||
if (dbType === "opengauss") {
|
||||
return "opengauss://user:pass@127.0.0.1:5432/db_name";
|
||||
}
|
||||
return "例如: postgres://user:pass@127.0.0.1:5432/db_name";
|
||||
};
|
||||
|
||||
@@ -1713,6 +1734,7 @@ const ConnectionModal: React.FC<{
|
||||
case "kingbase":
|
||||
case "highgo":
|
||||
case "vastbase":
|
||||
case "opengauss":
|
||||
return "application_name=GoNavi&statement_timeout=30000";
|
||||
case "oracle":
|
||||
return "PREFETCH_ROWS=5000&TRACE FILE=/tmp/go-ora.trc";
|
||||
@@ -1775,7 +1797,8 @@ const ConnectionModal: React.FC<{
|
||||
mergeConnectionParams(params, values.connectionParams);
|
||||
const dbPath = database ? `/${encodeURIComponent(database)}` : "/";
|
||||
const query = params.toString();
|
||||
const scheme = type === "diros" ? "doris" : "mysql";
|
||||
const scheme =
|
||||
type === "diros" ? "doris" : type === "oceanbase" ? "oceanbase" : "mysql";
|
||||
return `${scheme}://${encodedAuth}${hosts.join(",")}${dbPath}${query ? `?${query}` : ""}`;
|
||||
}
|
||||
|
||||
@@ -1903,7 +1926,8 @@ const ConnectionModal: React.FC<{
|
||||
type === "postgres" ||
|
||||
type === "kingbase" ||
|
||||
type === "highgo" ||
|
||||
type === "vastbase"
|
||||
type === "vastbase" ||
|
||||
type === "opengauss"
|
||||
) {
|
||||
params.set("sslmode", "require");
|
||||
} else if (type === "sqlserver") {
|
||||
@@ -1942,7 +1966,8 @@ const ConnectionModal: React.FC<{
|
||||
type === "postgres" ||
|
||||
type === "kingbase" ||
|
||||
type === "highgo" ||
|
||||
type === "vastbase"
|
||||
type === "vastbase" ||
|
||||
type === "opengauss"
|
||||
) {
|
||||
params.set("sslmode", "disable");
|
||||
} else if (type === "sqlserver") {
|
||||
@@ -2131,6 +2156,7 @@ const ConnectionModal: React.FC<{
|
||||
const mysqlReplicaHosts =
|
||||
configType === "mysql" ||
|
||||
configType === "mariadb" ||
|
||||
configType === "oceanbase" ||
|
||||
configType === "diros" ||
|
||||
configType === "sphinx"
|
||||
? normalizedHosts.slice(1)
|
||||
@@ -3528,6 +3554,11 @@ const ConnectionModal: React.FC<{
|
||||
{
|
||||
label: "国产数据库",
|
||||
items: [
|
||||
{
|
||||
key: "oceanbase",
|
||||
name: "OceanBase",
|
||||
icon: getDbIcon("oceanbase", undefined, 36),
|
||||
},
|
||||
{
|
||||
key: "dameng",
|
||||
name: "Dameng (达梦)",
|
||||
@@ -3548,6 +3579,11 @@ const ConnectionModal: React.FC<{
|
||||
name: "Vastbase (海量)",
|
||||
icon: getDbIcon("vastbase", undefined, 36),
|
||||
},
|
||||
{
|
||||
key: "opengauss",
|
||||
name: "OpenGauss",
|
||||
icon: getDbIcon("opengauss", undefined, 36),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -4598,7 +4634,8 @@ const ConnectionModal: React.FC<{
|
||||
{(dbType === "postgres" ||
|
||||
dbType === "kingbase" ||
|
||||
dbType === "highgo" ||
|
||||
dbType === "vastbase") &&
|
||||
dbType === "vastbase" ||
|
||||
dbType === "opengauss") &&
|
||||
renderConfigSectionCard({
|
||||
sectionKey: "service",
|
||||
icon: <DatabaseOutlined />,
|
||||
|
||||
@@ -50,7 +50,7 @@ const quoteSqlIdent = (dbType: string, ident: string): string => {
|
||||
const raw = String(ident || '').trim();
|
||||
if (!raw) return raw;
|
||||
const t = String(dbType || '').toLowerCase();
|
||||
if (t === 'mysql' || t === 'mariadb' || t === 'diros' || t === 'sphinx' || t === 'clickhouse' || t === 'tdengine') {
|
||||
if (t === 'mysql' || t === 'mariadb' || t === 'oceanbase' || t === 'diros' || t === 'sphinx' || t === 'clickhouse' || t === 'tdengine') {
|
||||
return `\`${raw.replace(/`/g, '``')}\``;
|
||||
}
|
||||
if (t === 'sqlserver') {
|
||||
|
||||
@@ -457,7 +457,7 @@ const DataViewer: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAct
|
||||
|
||||
const dbType = resolveDataSourceType(config);
|
||||
const dbTypeLower = String(dbType || '').trim().toLowerCase();
|
||||
const isMySQLFamily = dbTypeLower === 'mysql' || dbTypeLower === 'mariadb' || dbTypeLower === 'diros';
|
||||
const isMySQLFamily = dbTypeLower === 'mysql' || dbTypeLower === 'mariadb' || dbTypeLower === 'oceanbase' || dbTypeLower === 'diros';
|
||||
const normalizedQuickWhereCondition = normalizeQuickWhereCondition(quickWhereCondition);
|
||||
const quickWhereValidation = validateQuickWhereCondition(normalizedQuickWhereCondition);
|
||||
if (!quickWhereValidation.ok) {
|
||||
|
||||
@@ -12,6 +12,7 @@ export interface DbIconProps {
|
||||
const DB_DEFAULT_COLORS: Record<string, string> = {
|
||||
mysql: '#00758F',
|
||||
mariadb: '#003545',
|
||||
oceanbase: '#0052CC',
|
||||
postgres: '#336791',
|
||||
redis: '#DC382D',
|
||||
mongodb: '#47A248',
|
||||
@@ -24,6 +25,7 @@ const DB_DEFAULT_COLORS: Record<string, string> = {
|
||||
sqlite: '#003B57',
|
||||
duckdb: '#FFC107',
|
||||
vastbase: '#0066CC',
|
||||
opengauss: '#2446A8',
|
||||
highgo: '#00A86B',
|
||||
tdengine: '#2962FF',
|
||||
diros: '#0050B3',
|
||||
@@ -90,6 +92,9 @@ const MySQLIcon: React.FC<DbIconProps> = ({ size = 16, color }) => (
|
||||
const MariaDBIcon: React.FC<DbIconProps> = ({ size = 16, color }) => (
|
||||
<BrandSvgIcon type="mariadb" size={size} color={color} />
|
||||
);
|
||||
const OceanBaseIcon: React.FC<DbIconProps> = ({ size = 16, color }) => (
|
||||
<ColorBadge size={size} color={color || DB_DEFAULT_COLORS.oceanbase} label="OB" />
|
||||
);
|
||||
const PostgresIcon: React.FC<DbIconProps> = ({ size = 16, color }) => (
|
||||
<BrandSvgIcon type="postgres" size={size} color={color} />
|
||||
);
|
||||
@@ -131,6 +136,9 @@ const DamengIcon: React.FC<DbIconProps> = ({ size = 16, color }) => (
|
||||
const VastBaseIcon: React.FC<DbIconProps> = ({ size = 16, color }) => (
|
||||
<ColorBadge size={size} color={color || DB_DEFAULT_COLORS.vastbase} label="VB" />
|
||||
);
|
||||
const OpenGaussIcon: React.FC<DbIconProps> = ({ size = 16, color }) => (
|
||||
<ColorBadge size={size} color={color || DB_DEFAULT_COLORS.opengauss} label="OG" />
|
||||
);
|
||||
const HighGoIcon: React.FC<DbIconProps> = ({ size = 16, color }) => (
|
||||
<ColorBadge size={size} color={color || DB_DEFAULT_COLORS.highgo} label="HG" />
|
||||
);
|
||||
@@ -165,6 +173,7 @@ const SphinxIconFallback: React.FC<DbIconProps> = ({ size = 16, color }) => (
|
||||
const DB_ICON_MAP: Record<string, React.FC<DbIconProps>> = {
|
||||
mysql: MySQLIcon,
|
||||
mariadb: MariaDBIcon,
|
||||
oceanbase: OceanBaseIcon,
|
||||
diros: DorisIcon,
|
||||
sphinx: SphinxIcon,
|
||||
postgres: PostgresIcon,
|
||||
@@ -179,6 +188,7 @@ const DB_ICON_MAP: Record<string, React.FC<DbIconProps>> = {
|
||||
sqlite: SQLiteIcon,
|
||||
duckdb: DuckDBIcon,
|
||||
vastbase: VastBaseIcon,
|
||||
opengauss: OpenGaussIcon,
|
||||
highgo: HighGoIcon,
|
||||
tdengine: TDengineIcon,
|
||||
custom: CustomIcon,
|
||||
@@ -186,9 +196,9 @@ const DB_ICON_MAP: Record<string, React.FC<DbIconProps>> = {
|
||||
|
||||
/** 可选图标类型列表(用于图标选择器 UI) */
|
||||
export const DB_ICON_TYPES: string[] = [
|
||||
'mysql', 'mariadb', 'postgres', 'redis', 'mongodb', 'jvm',
|
||||
'mysql', 'mariadb', 'oceanbase', 'postgres', 'redis', 'mongodb', 'jvm',
|
||||
'oracle', 'sqlserver', 'sqlite', 'duckdb', 'clickhouse',
|
||||
'kingbase', 'dameng', 'vastbase', 'highgo', 'tdengine', 'custom',
|
||||
'kingbase', 'dameng', 'vastbase', 'opengauss', 'highgo', 'tdengine', 'custom',
|
||||
];
|
||||
|
||||
/** 该类型是否有品牌 SVG 文件 */
|
||||
@@ -204,12 +214,12 @@ export const getDbIcon = (type: string, color?: string, size?: number): React.Re
|
||||
/** 获取数据库图标显示名称(中文) */
|
||||
export const getDbIconLabel = (type: string): string => {
|
||||
const labels: Record<string, string> = {
|
||||
mysql: 'MySQL', mariadb: 'MariaDB', postgres: 'PostgreSQL',
|
||||
mysql: 'MySQL', mariadb: 'MariaDB', oceanbase: 'OceanBase', postgres: 'PostgreSQL',
|
||||
redis: 'Redis', mongodb: 'MongoDB', jvm: 'JVM',
|
||||
oracle: 'Oracle',
|
||||
sqlserver: 'SQL Server', clickhouse: 'ClickHouse', sqlite: 'SQLite',
|
||||
duckdb: 'DuckDB', kingbase: '金仓', dameng: '达梦',
|
||||
vastbase: 'VastBase', highgo: '瀚高', tdengine: 'TDengine',
|
||||
vastbase: 'VastBase', opengauss: 'OpenGauss', highgo: '瀚高', tdengine: 'TDengine',
|
||||
custom: '自定义',
|
||||
};
|
||||
return labels[type?.toLowerCase()] || type;
|
||||
|
||||
@@ -43,9 +43,11 @@ const DefinitionViewer: React.FC<DefinitionViewerProps> = ({ tab }) => {
|
||||
if (type === 'custom') {
|
||||
const driver = String(conn?.config?.driver || '').trim().toLowerCase();
|
||||
if (driver === 'diros' || driver === 'doris') return 'mysql';
|
||||
if (driver === 'oceanbase') return 'mysql';
|
||||
if (driver === 'opengauss' || driver === 'open_gauss' || driver === 'open-gauss') return 'opengauss';
|
||||
return driver;
|
||||
}
|
||||
if (type === 'mariadb' || type === 'diros' || type === 'sphinx') return 'mysql';
|
||||
if (type === 'mariadb' || type === 'oceanbase' || type === 'diros' || type === 'sphinx') return 'mysql';
|
||||
if (type === 'dameng') return 'dm';
|
||||
return type;
|
||||
};
|
||||
@@ -133,7 +135,8 @@ const DefinitionViewer: React.FC<DefinitionViewerProps> = ({ tab }) => {
|
||||
case 'postgres':
|
||||
case 'kingbase':
|
||||
case 'highgo':
|
||||
case 'vastbase': {
|
||||
case 'vastbase':
|
||||
case 'opengauss': {
|
||||
const schemaRef = schema || 'public';
|
||||
return [`SELECT pg_get_viewdef('${escapeSQLLiteral(schemaRef)}.${safeName}'::regclass, true) AS view_definition`];
|
||||
}
|
||||
@@ -179,7 +182,8 @@ const DefinitionViewer: React.FC<DefinitionViewerProps> = ({ tab }) => {
|
||||
case 'postgres':
|
||||
case 'kingbase':
|
||||
case 'highgo':
|
||||
case 'vastbase': {
|
||||
case 'vastbase':
|
||||
case 'opengauss': {
|
||||
const schemaRef = schema || 'public';
|
||||
return [`SELECT pg_get_functiondef(p.oid) AS routine_definition FROM pg_proc p JOIN pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = '${escapeSQLLiteral(schemaRef)}' AND p.proname = '${safeName}' LIMIT 1`];
|
||||
}
|
||||
|
||||
@@ -494,12 +494,14 @@ describe('QueryEditor external SQL save', () => {
|
||||
it.each([
|
||||
'mysql',
|
||||
'mariadb',
|
||||
'oceanbase',
|
||||
'diros',
|
||||
'sphinx',
|
||||
'postgres',
|
||||
'kingbase',
|
||||
'highgo',
|
||||
'vastbase',
|
||||
'opengauss',
|
||||
'sqlserver',
|
||||
'sqlite',
|
||||
'duckdb',
|
||||
|
||||
@@ -97,6 +97,11 @@ const normalizeDriverType = (value: string): string => {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
if (normalized === 'postgresql') return 'postgres';
|
||||
if (normalized === 'doris') return 'diros';
|
||||
if (
|
||||
normalized === 'open_gauss' ||
|
||||
normalized === 'open-gauss' ||
|
||||
normalized === 'opengauss'
|
||||
) return 'opengauss';
|
||||
return normalized;
|
||||
};
|
||||
|
||||
@@ -525,6 +530,9 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }>
|
||||
'kingbase',
|
||||
'highgo',
|
||||
'vastbase',
|
||||
'opengauss',
|
||||
'open_gauss',
|
||||
'open-gauss',
|
||||
'sqlserver',
|
||||
'oracle',
|
||||
'dameng',
|
||||
@@ -535,6 +543,9 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }>
|
||||
'kingbase',
|
||||
'highgo',
|
||||
'vastbase',
|
||||
'opengauss',
|
||||
'open_gauss',
|
||||
'open-gauss',
|
||||
'sqlserver',
|
||||
'oracle',
|
||||
'dm',
|
||||
@@ -563,9 +574,11 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }>
|
||||
if (type === 'custom') {
|
||||
const driver = String(conn?.config?.driver || '').trim().toLowerCase();
|
||||
if (driver === 'diros' || driver === 'doris') return 'mysql';
|
||||
if (driver === 'oceanbase') return 'mysql';
|
||||
if (driver === 'opengauss' || driver === 'open_gauss' || driver === 'open-gauss') return 'opengauss';
|
||||
return driver;
|
||||
}
|
||||
if (type === 'mariadb' || type === 'diros' || type === 'sphinx') return 'mysql';
|
||||
if (type === 'mariadb' || type === 'oceanbase' || type === 'diros' || type === 'sphinx') return 'mysql';
|
||||
if (type === 'dameng') return 'dm';
|
||||
return type;
|
||||
};
|
||||
@@ -730,6 +743,7 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }>
|
||||
case 'kingbase':
|
||||
case 'highgo':
|
||||
case 'vastbase':
|
||||
case 'opengauss':
|
||||
return [{ sql: `SELECT schemaname AS schema_name, viewname AS view_name FROM pg_catalog.pg_views WHERE schemaname != 'information_schema' AND schemaname NOT LIKE 'pg_%' ORDER BY schemaname, viewname` }];
|
||||
case 'sqlserver': {
|
||||
const safeDb = quoteSqlServerIdentifier(dbName || 'master');
|
||||
@@ -774,6 +788,7 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }>
|
||||
case 'kingbase':
|
||||
case 'highgo':
|
||||
case 'vastbase':
|
||||
case 'opengauss':
|
||||
return [{ sql: `SELECT DISTINCT event_object_schema AS schema_name, event_object_table AS table_name, trigger_name FROM information_schema.triggers WHERE trigger_schema NOT IN ('pg_catalog', 'information_schema') AND trigger_schema NOT LIKE 'pg_%' ORDER BY event_object_schema, event_object_table, trigger_name` }];
|
||||
case 'sqlserver': {
|
||||
const safeDb = quoteSqlServerIdentifier(dbName || 'master');
|
||||
@@ -821,6 +836,7 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }>
|
||||
case 'kingbase':
|
||||
case 'highgo':
|
||||
case 'vastbase':
|
||||
case 'opengauss':
|
||||
return normalizeMetadataQuerySpecs([
|
||||
{
|
||||
// PostgreSQL 11+ / 部分 PG-like:通过 prokind 区分 FUNCTION/PROCEDURE
|
||||
@@ -2921,7 +2937,7 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }>
|
||||
case 'mysql':
|
||||
query = `SHOW CREATE VIEW \`${viewName.replace(/`/g, '``')}\``;
|
||||
break;
|
||||
case 'postgres': case 'kingbase': case 'highgo': case 'vastbase': {
|
||||
case 'postgres': case 'kingbase': case 'highgo': case 'vastbase': case 'opengauss': {
|
||||
const parts = viewName.split('.');
|
||||
const schema = parts.length > 1 ? parts[0] : 'public';
|
||||
const name = parts.length > 1 ? parts[1] : viewName;
|
||||
@@ -2977,7 +2993,7 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }>
|
||||
case 'mysql':
|
||||
template = `CREATE VIEW \`view_name\` AS\nSELECT column1, column2\nFROM table_name\nWHERE condition;`;
|
||||
break;
|
||||
case 'postgres': case 'kingbase': case 'highgo': case 'vastbase':
|
||||
case 'postgres': case 'kingbase': case 'highgo': case 'vastbase': case 'opengauss':
|
||||
template = `CREATE OR REPLACE VIEW view_name AS\nSELECT column1, column2\nFROM table_name\nWHERE condition;`;
|
||||
break;
|
||||
case 'sqlserver':
|
||||
@@ -3088,7 +3104,7 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }>
|
||||
case 'mysql':
|
||||
query = `SHOW CREATE ${routineType} \`${name.replace(/`/g, '``')}\``;
|
||||
break;
|
||||
case 'postgres': case 'kingbase': case 'highgo': case 'vastbase': {
|
||||
case 'postgres': case 'kingbase': case 'highgo': case 'vastbase': case 'opengauss': {
|
||||
const schemaRef = schema || 'public';
|
||||
query = `SELECT pg_get_functiondef(p.oid) AS routine_definition FROM pg_proc p JOIN pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = '${escapeSQLLiteral(schemaRef)}' AND p.proname = '${escapeSQLLiteral(name)}' LIMIT 1`;
|
||||
break;
|
||||
@@ -3158,7 +3174,7 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }>
|
||||
? `DELIMITER $$\nCREATE PROCEDURE proc_name(IN param1 INT)\nBEGIN\n SELECT * FROM table_name WHERE id = param1;\nEND$$\nDELIMITER ;`
|
||||
: `DELIMITER $$\nCREATE FUNCTION func_name(param1 INT)\nRETURNS INT\nDETERMINISTIC\nBEGIN\n RETURN param1 * 2;\nEND$$\nDELIMITER ;`;
|
||||
break;
|
||||
case 'postgres': case 'kingbase': case 'highgo': case 'vastbase':
|
||||
case 'postgres': case 'kingbase': case 'highgo': case 'vastbase': case 'opengauss':
|
||||
template = isProc
|
||||
? `CREATE OR REPLACE PROCEDURE proc_name(param1 integer)\nLANGUAGE plpgsql\nAS $$\nBEGIN\n -- procedure body\nEND;\n$$;`
|
||||
: `CREATE OR REPLACE FUNCTION func_name(param1 integer)\nRETURNS integer\nLANGUAGE plpgsql\nAS $$\nBEGIN\n RETURN param1 * 2;\nEND;\n$$;`;
|
||||
|
||||
@@ -836,6 +836,7 @@ const TableDesigner: React.FC<{ tab: TabData }> = ({ tab }) => {
|
||||
if (normalized === 'postgresql' || normalized === 'pg') return 'postgres';
|
||||
if (normalized === 'mssql' || normalized === 'sql_server' || normalized === 'sql-server') return 'sqlserver';
|
||||
if (normalized === 'doris') return 'diros';
|
||||
if (normalized === 'open_gauss' || normalized === 'open-gauss') return 'opengauss';
|
||||
return normalized;
|
||||
};
|
||||
|
||||
@@ -871,6 +872,7 @@ const TableDesigner: React.FC<{ tab: TabData }> = ({ tab }) => {
|
||||
switch (dbType) {
|
||||
case 'mysql':
|
||||
case 'mariadb':
|
||||
case 'oceanbase':
|
||||
case 'diros':
|
||||
return `CREATE TRIGGER trigger_name
|
||||
BEFORE INSERT ON \`${tblName}\`
|
||||
@@ -882,6 +884,7 @@ END;`;
|
||||
case 'kingbase':
|
||||
case 'highgo':
|
||||
case 'vastbase':
|
||||
case 'opengauss':
|
||||
return `CREATE OR REPLACE FUNCTION trigger_function_name()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
@@ -931,12 +934,14 @@ END;`;
|
||||
switch (dbType) {
|
||||
case 'mysql':
|
||||
case 'mariadb':
|
||||
case 'oceanbase':
|
||||
case 'diros':
|
||||
return `DROP TRIGGER IF EXISTS \`${triggerName}\``;
|
||||
case 'postgres':
|
||||
case 'kingbase':
|
||||
case 'highgo':
|
||||
case 'vastbase':
|
||||
case 'opengauss':
|
||||
return `DROP TRIGGER IF EXISTS "${triggerName}" ON "${tblName}"`;
|
||||
case 'sqlserver':
|
||||
return `DROP TRIGGER IF EXISTS [${triggerName}]`;
|
||||
|
||||
@@ -57,9 +57,11 @@ const getMetadataDialect = (connType: string, driver?: string): string => {
|
||||
if (type === 'custom') {
|
||||
const d = (driver || '').trim().toLowerCase();
|
||||
if (d === 'diros' || d === 'doris') return 'mysql';
|
||||
if (d === 'oceanbase') return 'mysql';
|
||||
if (d === 'opengauss' || d === 'open_gauss' || d === 'open-gauss') return 'opengauss';
|
||||
return d;
|
||||
}
|
||||
if (type === 'mariadb' || type === 'diros' || type === 'sphinx') return 'mysql';
|
||||
if (type === 'mariadb' || type === 'oceanbase' || type === 'diros' || type === 'sphinx') return 'mysql';
|
||||
if (type === 'dameng') return 'dm';
|
||||
return type;
|
||||
};
|
||||
@@ -85,7 +87,8 @@ ORDER BY table_name`;
|
||||
case 'postgres':
|
||||
case 'kingbase':
|
||||
case 'vastbase':
|
||||
case 'highgo': {
|
||||
case 'highgo':
|
||||
case 'opengauss': {
|
||||
const schema = schemaName || 'public';
|
||||
return `
|
||||
SELECT
|
||||
|
||||
@@ -29,9 +29,11 @@ const TriggerViewer: React.FC<TriggerViewerProps> = ({ tab }) => {
|
||||
if (type === 'custom') {
|
||||
const driver = String(conn?.config?.driver || '').trim().toLowerCase();
|
||||
if (driver === 'diros' || driver === 'doris') return 'mysql';
|
||||
if (driver === 'oceanbase') return 'mysql';
|
||||
if (driver === 'opengauss' || driver === 'open_gauss' || driver === 'open-gauss') return 'opengauss';
|
||||
return driver;
|
||||
}
|
||||
if (type === 'mariadb' || type === 'diros' || type === 'sphinx') return 'mysql';
|
||||
if (type === 'mariadb' || type === 'oceanbase' || type === 'diros' || type === 'sphinx') return 'mysql';
|
||||
if (type === 'dameng') return 'dm';
|
||||
return type;
|
||||
};
|
||||
@@ -62,6 +64,7 @@ const TriggerViewer: React.FC<TriggerViewerProps> = ({ tab }) => {
|
||||
case 'kingbase':
|
||||
case 'highgo':
|
||||
case 'vastbase':
|
||||
case 'opengauss':
|
||||
return [`SELECT pg_get_triggerdef(t.oid, true) AS trigger_definition
|
||||
FROM pg_trigger t
|
||||
JOIN pg_class c ON t.tgrelid = c.oid
|
||||
@@ -179,7 +182,8 @@ LIMIT 1`];
|
||||
case 'postgres':
|
||||
case 'kingbase':
|
||||
case 'highgo':
|
||||
case 'vastbase': {
|
||||
case 'vastbase':
|
||||
case 'opengauss': {
|
||||
return row.trigger_definition || row.TRIGGER_DEFINITION || Object.values(row)[0] || '';
|
||||
}
|
||||
case 'sqlserver': {
|
||||
|
||||
@@ -5,7 +5,9 @@ import { supportsTableTruncateAction } from './tableDataDangerActions';
|
||||
describe('tableDataDangerActions', () => {
|
||||
it('supports native truncate for known relational dialects', () => {
|
||||
expect(supportsTableTruncateAction('mysql')).toBe(true);
|
||||
expect(supportsTableTruncateAction('oceanbase')).toBe(true);
|
||||
expect(supportsTableTruncateAction('postgres')).toBe(true);
|
||||
expect(supportsTableTruncateAction('opengauss')).toBe(true);
|
||||
expect(supportsTableTruncateAction('custom', 'postgresql')).toBe(true);
|
||||
expect(supportsTableTruncateAction('custom', 'kingbase8')).toBe(true);
|
||||
});
|
||||
|
||||
@@ -9,6 +9,10 @@ const resolveCustomDriverDialect = (driver: string): string => {
|
||||
case 'pq':
|
||||
case 'pgx':
|
||||
return 'postgres';
|
||||
case 'opengauss':
|
||||
case 'open_gauss':
|
||||
case 'open-gauss':
|
||||
return 'opengauss';
|
||||
case 'dm':
|
||||
case 'dameng':
|
||||
case 'dm8':
|
||||
@@ -21,6 +25,8 @@ const resolveCustomDriverDialect = (driver: string): string => {
|
||||
case 'diros':
|
||||
case 'doris':
|
||||
return 'diros';
|
||||
case 'oceanbase':
|
||||
return 'oceanbase';
|
||||
case 'kingbase':
|
||||
case 'kingbase8':
|
||||
case 'kingbasees':
|
||||
@@ -34,7 +40,9 @@ const resolveCustomDriverDialect = (driver: string): string => {
|
||||
break;
|
||||
}
|
||||
|
||||
if (normalized.includes('opengauss') || normalized.includes('open_gauss') || normalized.includes('open-gauss')) return 'opengauss';
|
||||
if (normalized.includes('postgres')) return 'postgres';
|
||||
if (normalized.includes('oceanbase')) return 'oceanbase';
|
||||
if (normalized.includes('kingbase')) return 'kingbase';
|
||||
if (normalized.includes('highgo')) return 'highgo';
|
||||
if (normalized.includes('vastbase')) return 'vastbase';
|
||||
@@ -56,10 +64,12 @@ export const supportsTableTruncateAction = (type: string, driver?: string): bool
|
||||
switch (resolveTableDataActionDBType(type, driver)) {
|
||||
case 'mysql':
|
||||
case 'mariadb':
|
||||
case 'oceanbase':
|
||||
case 'postgres':
|
||||
case 'kingbase':
|
||||
case 'highgo':
|
||||
case 'vastbase':
|
||||
case 'opengauss':
|
||||
case 'sqlserver':
|
||||
case 'oracle':
|
||||
case 'dameng':
|
||||
|
||||
@@ -76,6 +76,7 @@ const DEFAULT_GLOBAL_PROXY: GlobalProxyConfig = {
|
||||
const SUPPORTED_CONNECTION_TYPES = new Set([
|
||||
"mysql",
|
||||
"mariadb",
|
||||
"oceanbase",
|
||||
"doris",
|
||||
"diros",
|
||||
"sphinx",
|
||||
@@ -90,6 +91,7 @@ const SUPPORTED_CONNECTION_TYPES = new Set([
|
||||
"mongodb",
|
||||
"highgo",
|
||||
"vastbase",
|
||||
"opengauss",
|
||||
"jvm",
|
||||
"sqlite",
|
||||
"duckdb",
|
||||
@@ -98,6 +100,7 @@ const SUPPORTED_CONNECTION_TYPES = new Set([
|
||||
const SSL_SUPPORTED_CONNECTION_TYPES = new Set([
|
||||
"mysql",
|
||||
"mariadb",
|
||||
"oceanbase",
|
||||
"diros",
|
||||
"sphinx",
|
||||
"dameng",
|
||||
@@ -108,6 +111,7 @@ const SSL_SUPPORTED_CONNECTION_TYPES = new Set([
|
||||
"kingbase",
|
||||
"highgo",
|
||||
"vastbase",
|
||||
"opengauss",
|
||||
"mongodb",
|
||||
"redis",
|
||||
"tdengine",
|
||||
@@ -120,6 +124,8 @@ const getDefaultPortByType = (type: string): number => {
|
||||
case "mysql":
|
||||
case "mariadb":
|
||||
return 3306;
|
||||
case "oceanbase":
|
||||
return 2881;
|
||||
case "doris":
|
||||
case "diros":
|
||||
return 9030;
|
||||
@@ -131,6 +137,7 @@ const getDefaultPortByType = (type: string): number => {
|
||||
return 9000;
|
||||
case "postgres":
|
||||
case "vastbase":
|
||||
case "opengauss":
|
||||
return 5432;
|
||||
case "redis":
|
||||
return 6379;
|
||||
@@ -270,6 +277,13 @@ const normalizeConnectionType = (value: unknown): string => {
|
||||
if (type === "doris") {
|
||||
return "diros";
|
||||
}
|
||||
if (
|
||||
type === "open_gauss" ||
|
||||
type === "open-gauss" ||
|
||||
type === "opengauss"
|
||||
) {
|
||||
return "opengauss";
|
||||
}
|
||||
return SUPPORTED_CONNECTION_TYPES.has(type) ? type : DEFAULT_CONNECTION_TYPE;
|
||||
};
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ describe('connectionModalPresentation', () => {
|
||||
const allTypes = [
|
||||
'mysql',
|
||||
'mariadb',
|
||||
'oceanbase',
|
||||
'doris',
|
||||
'diros',
|
||||
'sphinx',
|
||||
@@ -81,6 +82,7 @@ describe('connectionModalPresentation', () => {
|
||||
'kingbase',
|
||||
'highgo',
|
||||
'vastbase',
|
||||
'opengauss',
|
||||
'mongodb',
|
||||
'redis',
|
||||
'tdengine',
|
||||
|
||||
@@ -55,6 +55,7 @@ type ConnectionConfigSectionCopy = {
|
||||
const mysqlCompatibleTypes = new Set([
|
||||
'mysql',
|
||||
'mariadb',
|
||||
'oceanbase',
|
||||
'doris',
|
||||
'diros',
|
||||
'sphinx',
|
||||
@@ -64,6 +65,7 @@ const postgresCompatibleTypes = new Set([
|
||||
'kingbase',
|
||||
'highgo',
|
||||
'vastbase',
|
||||
'opengauss',
|
||||
]);
|
||||
const fileDatabaseTypes = new Set(['sqlite', 'duckdb']);
|
||||
|
||||
|
||||
@@ -9,6 +9,10 @@ const normalizeDataSourceToken = (raw: string): string => {
|
||||
return 'diros';
|
||||
case 'postgresql':
|
||||
return 'postgres';
|
||||
case 'opengauss':
|
||||
case 'open_gauss':
|
||||
case 'open-gauss':
|
||||
return 'opengauss';
|
||||
case 'dm':
|
||||
return 'dameng';
|
||||
default:
|
||||
@@ -29,12 +33,14 @@ export const resolveDataSourceType = (config: ConnectionLike): string => {
|
||||
const SQL_QUERY_EXPORT_TYPES = new Set([
|
||||
'mysql',
|
||||
'mariadb',
|
||||
'oceanbase',
|
||||
'diros',
|
||||
'sphinx',
|
||||
'postgres',
|
||||
'kingbase',
|
||||
'highgo',
|
||||
'vastbase',
|
||||
'opengauss',
|
||||
'sqlserver',
|
||||
'sqlite',
|
||||
'duckdb',
|
||||
@@ -47,12 +53,14 @@ const SQL_QUERY_EXPORT_TYPES = new Set([
|
||||
const COPY_INSERT_TYPES = new Set([
|
||||
'mysql',
|
||||
'mariadb',
|
||||
'oceanbase',
|
||||
'diros',
|
||||
'sphinx',
|
||||
'postgres',
|
||||
'kingbase',
|
||||
'highgo',
|
||||
'vastbase',
|
||||
'opengauss',
|
||||
'sqlserver',
|
||||
'sqlite',
|
||||
'duckdb',
|
||||
|
||||
@@ -17,6 +17,8 @@ describe('driver import guidance', () => {
|
||||
it('documents custom driver aliases for kingbase and related fallbacks', () => {
|
||||
expect(CUSTOM_CONNECTION_DRIVER_HELP).toContain('kingbase8');
|
||||
expect(CUSTOM_CONNECTION_DRIVER_HELP).toContain('pgx');
|
||||
expect(CUSTOM_CONNECTION_DRIVER_HELP).toContain('open_gauss');
|
||||
expect(CUSTOM_CONNECTION_DRIVER_HELP).toContain('oceanbase');
|
||||
expect(CUSTOM_CONNECTION_DRIVER_HELP).toContain('JDBC Jar');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,4 +7,4 @@ export const DRIVER_LOCAL_IMPORT_SINGLE_FILE_HELP =
|
||||
'行内“导入驱动包”仅用于单个驱动文件/总包(如 `mariadb-driver-agent`、`mariadb-driver-agent.exe`、`GoNavi-DriverAgents.zip`),不支持直接导入 JDBC Jar;批量导入请使用上方“导入驱动目录”。';
|
||||
|
||||
export const CUSTOM_CONNECTION_DRIVER_HELP =
|
||||
'已支持: mysql, postgres, sqlite, oracle, dm, kingbase;别名支持 postgresql/pgx、dm8、kingbase8/kingbasees/kingbasev8。当前不支持通过 JDBC Jar 扩展驱动。';
|
||||
'已支持: mysql, oceanbase, postgres, opengauss, sqlite, oracle, dm, kingbase;别名支持 postgresql/pgx、open_gauss/open-gauss、dm8、kingbase8/kingbasees/kingbasev8。当前不支持通过 JDBC Jar 扩展驱动。';
|
||||
|
||||
@@ -6,6 +6,7 @@ describe('applyQueryAutoLimit', () => {
|
||||
const limitDialects = [
|
||||
'mysql',
|
||||
'mariadb',
|
||||
'oceanbase',
|
||||
'diros',
|
||||
'doris',
|
||||
'sphinx',
|
||||
@@ -15,6 +16,7 @@ describe('applyQueryAutoLimit', () => {
|
||||
'kingbase8',
|
||||
'highgo',
|
||||
'vastbase',
|
||||
'opengauss',
|
||||
'sqlite',
|
||||
'sqlite3',
|
||||
'duckdb',
|
||||
|
||||
@@ -16,10 +16,14 @@ const normalizeSidebarConnectionDialect = (type: string, driver: string): string
|
||||
if (normalizedType === 'custom') {
|
||||
const normalizedDriver = String(driver || '').trim().toLowerCase();
|
||||
if (normalizedDriver === 'postgresql' || normalizedDriver === 'postgres' || normalizedDriver === 'pg') return 'postgres';
|
||||
if (normalizedDriver === 'opengauss' || normalizedDriver === 'open_gauss' || normalizedDriver === 'open-gauss') return 'opengauss';
|
||||
if (normalizedDriver === 'dameng' || normalizedDriver === 'dm' || normalizedDriver === 'dm8') return 'dm';
|
||||
if (normalizedDriver === 'oceanbase') return 'mysql';
|
||||
if (normalizedDriver.includes('oracle')) return 'oracle';
|
||||
return normalizedDriver;
|
||||
}
|
||||
if (normalizedType === 'oceanbase') return 'mysql';
|
||||
if (normalizedType === 'open_gauss' || normalizedType === 'open-gauss') return 'opengauss';
|
||||
if (normalizedType === 'dameng') return 'dm';
|
||||
return normalizedType;
|
||||
};
|
||||
|
||||
@@ -37,12 +37,12 @@ export const quoteIdentPart = (dbType: string, ident: string) => {
|
||||
if (!raw) return raw;
|
||||
const dbTypeLower = (dbType || '').toLowerCase();
|
||||
|
||||
if (dbTypeLower === 'mysql' || dbTypeLower === 'mariadb' || dbTypeLower === 'diros' || dbTypeLower === 'sphinx' || dbTypeLower === 'tdengine' || dbTypeLower === 'clickhouse') {
|
||||
if (dbTypeLower === 'mysql' || dbTypeLower === 'mariadb' || dbTypeLower === 'oceanbase' || dbTypeLower === 'diros' || dbTypeLower === 'sphinx' || dbTypeLower === 'tdengine' || dbTypeLower === 'clickhouse') {
|
||||
return `\`${raw.replace(/`/g, '``')}\``;
|
||||
}
|
||||
|
||||
// 对于 KingBase/PostgreSQL,只在必要时加引号
|
||||
if (dbTypeLower === 'kingbase' || dbTypeLower === 'postgres') {
|
||||
if (dbTypeLower === 'kingbase' || dbTypeLower === 'postgres' || dbTypeLower === 'opengauss') {
|
||||
if (needsQuote(raw)) {
|
||||
return `"${raw.replace(/"/g, '""')}"`;
|
||||
}
|
||||
@@ -153,7 +153,7 @@ export const buildOrderBySQL = (
|
||||
// MySQL/MariaDB 大表在无显式排序需求时强制 ORDER BY(即使按主键)可能触发 filesort,
|
||||
// 导致 `Error 1038 (HY001): Out of sort memory`。
|
||||
// 因此仅在用户主动点击排序时下发 ORDER BY,默认分页查询不加兜底排序。
|
||||
if (dbTypeLower === 'mysql' || dbTypeLower === 'mariadb' || dbTypeLower === 'diros') {
|
||||
if (dbTypeLower === 'mysql' || dbTypeLower === 'mariadb' || dbTypeLower === 'oceanbase' || dbTypeLower === 'diros') {
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
@@ -14,12 +14,16 @@ const names = (items: Array<{ name: string }>) => items.map((item) => item.name)
|
||||
describe('sqlDialect', () => {
|
||||
it('normalizes datasource aliases without collapsing all dialects to mysql', () => {
|
||||
expect(resolveSqlDialect('postgresql')).toBe('postgres');
|
||||
expect(resolveSqlDialect('OpenGauss')).toBe('opengauss');
|
||||
expect(resolveSqlDialect('OceanBase')).toBe('oceanbase');
|
||||
expect(resolveSqlDialect('doris')).toBe('diros');
|
||||
expect(resolveSqlDialect('dameng')).toBe('dameng');
|
||||
expect(resolveSqlDialect('custom', 'kingbase8')).toBe('kingbase');
|
||||
expect(resolveSqlDialect('custom', 'dm8')).toBe('dameng');
|
||||
expect(resolveSqlDialect('custom', 'mariadb')).toBe('mariadb');
|
||||
expect(resolveSqlDialect('custom', 'open_gauss')).toBe('opengauss');
|
||||
expect(isMysqlFamilyDialect('mariadb')).toBe(true);
|
||||
expect(isMysqlFamilyDialect('oceanbase')).toBe(true);
|
||||
expect(isMysqlFamilyDialect('oracle')).toBe(false);
|
||||
});
|
||||
|
||||
@@ -28,6 +32,8 @@ describe('sqlDialect', () => {
|
||||
expect(values(resolveColumnTypeOptions('oracle'))).not.toContain('tinyint(1)');
|
||||
expect(values(resolveColumnTypeOptions('dameng'))).toContain('VARCHAR2(255)');
|
||||
expect(values(resolveColumnTypeOptions('kingbase'))).toContain('integer');
|
||||
expect(values(resolveColumnTypeOptions('opengauss'))).toContain('integer');
|
||||
expect(values(resolveColumnTypeOptions('oceanbase'))).toContain('varchar(255)');
|
||||
expect(values(resolveColumnTypeOptions('kingbase'))).not.toContain('tinyint(1)');
|
||||
expect(values(resolveColumnTypeOptions('diros'))).toContain('LARGEINT');
|
||||
expect(values(resolveColumnTypeOptions('sphinx'))).toContain('text');
|
||||
|
||||
@@ -8,12 +8,14 @@ export type SqlFunctionCompletion = {
|
||||
export type SqlDialect =
|
||||
| 'mysql'
|
||||
| 'mariadb'
|
||||
| 'oceanbase'
|
||||
| 'diros'
|
||||
| 'sphinx'
|
||||
| 'postgres'
|
||||
| 'kingbase'
|
||||
| 'highgo'
|
||||
| 'vastbase'
|
||||
| 'opengauss'
|
||||
| 'oracle'
|
||||
| 'dameng'
|
||||
| 'sqlserver'
|
||||
@@ -46,6 +48,10 @@ export const resolveSqlDialect = (rawType: string, rawDriver = ''): SqlDialect =
|
||||
case 'pq':
|
||||
case 'pgx':
|
||||
return 'postgres';
|
||||
case 'opengauss':
|
||||
case 'open_gauss':
|
||||
case 'open-gauss':
|
||||
return 'opengauss';
|
||||
case 'mssql':
|
||||
case 'sql_server':
|
||||
case 'sql-server':
|
||||
@@ -67,6 +73,7 @@ export const resolveSqlDialect = (rawType: string, rawDriver = ''): SqlDialect =
|
||||
case 'kingbasev8':
|
||||
return 'kingbase';
|
||||
case 'mariadb':
|
||||
case 'oceanbase':
|
||||
case 'mysql':
|
||||
case 'sphinx':
|
||||
case 'kingbase':
|
||||
@@ -83,7 +90,9 @@ export const resolveSqlDialect = (rawType: string, rawDriver = ''): SqlDialect =
|
||||
break;
|
||||
}
|
||||
|
||||
if (source.includes('opengauss') || source.includes('open_gauss') || source.includes('open-gauss')) return 'opengauss';
|
||||
if (source.includes('postgres')) return 'postgres';
|
||||
if (source.includes('oceanbase')) return 'oceanbase';
|
||||
if (source.includes('mariadb')) return 'mariadb';
|
||||
if (source.includes('mysql')) return 'mysql';
|
||||
if (source.includes('doris') || source.includes('diros')) return 'diros';
|
||||
@@ -103,11 +112,11 @@ export const resolveSqlDialect = (rawType: string, rawDriver = ''): SqlDialect =
|
||||
};
|
||||
|
||||
export const isMysqlFamilyDialect = (dbType: string): boolean => (
|
||||
['mysql', 'mariadb', 'diros', 'sphinx', 'tidb', 'oceanbase', 'starrocks'].includes(resolveSqlDialect(dbType))
|
||||
['mysql', 'mariadb', 'oceanbase', 'diros', 'sphinx', 'tidb', 'starrocks'].includes(resolveSqlDialect(dbType))
|
||||
);
|
||||
|
||||
export const isPgLikeDialect = (dbType: string): boolean => (
|
||||
['postgres', 'kingbase', 'highgo', 'vastbase'].includes(resolveSqlDialect(dbType))
|
||||
['postgres', 'kingbase', 'highgo', 'vastbase', 'opengauss'].includes(resolveSqlDialect(dbType))
|
||||
);
|
||||
|
||||
export const isOracleLikeDialect = (dbType: string): boolean => (
|
||||
@@ -423,9 +432,9 @@ const COMMON_TYPES = optionValues(['int', 'varchar(255)', 'text', 'datetime', 'd
|
||||
|
||||
export const resolveColumnTypeOptions = (dbType: string): ColumnTypeOption[] => {
|
||||
const dialect = resolveSqlDialect(dbType);
|
||||
if (dialect === 'mariadb' || dialect === 'mysql') return MYSQL_TYPES;
|
||||
if (dialect === 'diros') return DORIS_TYPES;
|
||||
if (dialect === 'sphinx') return SPHINX_TYPES;
|
||||
if (isMysqlFamilyDialect(dialect)) return MYSQL_TYPES;
|
||||
if (isPgLikeDialect(dialect)) return PG_TYPES;
|
||||
if (dialect === 'oracle') return ORACLE_TYPES;
|
||||
if (dialect === 'dameng') return DAMENG_TYPES;
|
||||
|
||||
@@ -15,7 +15,7 @@ func normalizeRunConfig(config connection.ConnectionConfig, dbName string) conne
|
||||
}
|
||||
|
||||
switch strings.ToLower(strings.TrimSpace(config.Type)) {
|
||||
case "mysql", "mariadb", "diros", "sphinx", "postgres", "kingbase", "highgo", "vastbase", "sqlserver", "mongodb", "tdengine", "clickhouse":
|
||||
case "mysql", "mariadb", "oceanbase", "diros", "sphinx", "postgres", "kingbase", "highgo", "vastbase", "opengauss", "sqlserver", "mongodb", "tdengine", "clickhouse":
|
||||
// 这些类型的 dbName 表示"数据库",需要写入连接配置以选择目标库。
|
||||
runConfig.Database = name
|
||||
case "dameng":
|
||||
@@ -62,7 +62,7 @@ func normalizeSchemaAndTable(config connection.ConnectionConfig, dbName string,
|
||||
}
|
||||
|
||||
switch dbType {
|
||||
case "postgres", "kingbase", "highgo", "vastbase":
|
||||
case "postgres", "kingbase", "highgo", "vastbase", "opengauss":
|
||||
// PG/金仓/瀚高/海量:dbName 在 UI 里是"数据库",schema 需从 tableName 或使用默认 public。
|
||||
return "public", rawTable
|
||||
default:
|
||||
|
||||
@@ -203,11 +203,13 @@ func defaultPortByType(driverType string) int {
|
||||
switch strings.ToLower(strings.TrimSpace(driverType)) {
|
||||
case "mysql", "mariadb":
|
||||
return 3306
|
||||
case "oceanbase":
|
||||
return 2881
|
||||
case "diros":
|
||||
return 9030
|
||||
case "sphinx":
|
||||
return 9306
|
||||
case "postgres", "vastbase":
|
||||
case "postgres", "vastbase", "opengauss":
|
||||
return 5432
|
||||
case "redis":
|
||||
return 6379
|
||||
|
||||
@@ -117,14 +117,14 @@ func (a *App) CreateDatabase(config connection.ConnectionConfig, dbName string)
|
||||
escapedDbName := strings.ReplaceAll(dbName, "`", "``")
|
||||
query := fmt.Sprintf("CREATE DATABASE `%s` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci", escapedDbName)
|
||||
dbType := strings.ToLower(strings.TrimSpace(runConfig.Type))
|
||||
if dbType == "postgres" || dbType == "kingbase" || dbType == "highgo" || dbType == "vastbase" {
|
||||
if dbType == "postgres" || dbType == "kingbase" || dbType == "highgo" || dbType == "vastbase" || dbType == "opengauss" {
|
||||
escapedDbName = strings.ReplaceAll(dbName, `"`, `""`)
|
||||
query = fmt.Sprintf("CREATE DATABASE \"%s\"", escapedDbName)
|
||||
} else if dbType == "tdengine" {
|
||||
query = fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", quoteIdentByType(dbType, dbName))
|
||||
} else if dbType == "clickhouse" {
|
||||
query = fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", quoteIdentByType(dbType, dbName))
|
||||
} else if dbType == "mariadb" || dbType == "diros" {
|
||||
} else if dbType == "mariadb" || dbType == "diros" || dbType == "oceanbase" {
|
||||
// MariaDB uses same syntax as MySQL
|
||||
} else if dbType == "sphinx" {
|
||||
return connection.QueryResult{Success: false, Message: "Sphinx 暂不支持创建数据库"}
|
||||
@@ -150,6 +150,8 @@ func resolveDDLDBType(config connection.ConnectionConfig) string {
|
||||
switch driver {
|
||||
case "postgresql", "postgres", "pg", "pq", "pgx":
|
||||
return "postgres"
|
||||
case "opengauss", "open_gauss", "open-gauss":
|
||||
return "opengauss"
|
||||
case "dm", "dameng", "dm8":
|
||||
return "dameng"
|
||||
case "sqlite3", "sqlite":
|
||||
@@ -164,9 +166,13 @@ func resolveDDLDBType(config connection.ConnectionConfig) string {
|
||||
return "highgo"
|
||||
case "vastbase":
|
||||
return "vastbase"
|
||||
case "oceanbase":
|
||||
return "oceanbase"
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.Contains(driver, "opengauss"), strings.Contains(driver, "open_gauss"), strings.Contains(driver, "open-gauss"):
|
||||
return "opengauss"
|
||||
case strings.Contains(driver, "postgres"):
|
||||
return "postgres"
|
||||
case strings.Contains(driver, "kingbase"):
|
||||
@@ -181,6 +187,8 @@ func resolveDDLDBType(config connection.ConnectionConfig) string {
|
||||
return "sphinx"
|
||||
case strings.Contains(driver, "diros"), strings.Contains(driver, "doris"):
|
||||
return "diros"
|
||||
case strings.Contains(driver, "oceanbase"):
|
||||
return "oceanbase"
|
||||
default:
|
||||
return driver
|
||||
}
|
||||
@@ -203,7 +211,7 @@ func normalizeSchemaAndTableByType(dbType string, dbName string, tableName strin
|
||||
}
|
||||
}
|
||||
|
||||
if dbType == "postgres" || dbType == "highgo" || dbType == "vastbase" {
|
||||
if dbType == "postgres" || dbType == "highgo" || dbType == "vastbase" || dbType == "opengauss" {
|
||||
schema, table := db.SplitSQLQualifiedName(rawTable)
|
||||
if schema != "" && table != "" {
|
||||
return schema, table
|
||||
@@ -222,7 +230,7 @@ func normalizeSchemaAndTableByType(dbType string, dbName string, tableName strin
|
||||
}
|
||||
|
||||
switch dbType {
|
||||
case "postgres", "kingbase", "highgo", "vastbase":
|
||||
case "postgres", "kingbase", "highgo", "vastbase", "opengauss":
|
||||
return "public", rawTable
|
||||
default:
|
||||
return rawDB, rawTable
|
||||
@@ -243,7 +251,7 @@ func buildRunConfigForDDL(config connection.ConnectionConfig, dbType string, dbN
|
||||
if strings.EqualFold(strings.TrimSpace(config.Type), "custom") {
|
||||
// custom 连接的 dbName 语义依赖 driver,尽量在常见驱动上对齐内置类型行为。
|
||||
switch dbType {
|
||||
case "mysql", "mariadb", "diros", "sphinx", "postgres", "kingbase", "highgo", "vastbase", "dameng", "clickhouse":
|
||||
case "mysql", "mariadb", "oceanbase", "diros", "sphinx", "postgres", "kingbase", "highgo", "vastbase", "opengauss", "dameng", "clickhouse":
|
||||
if strings.TrimSpace(dbName) != "" {
|
||||
runConfig.Database = strings.TrimSpace(dbName)
|
||||
}
|
||||
@@ -264,9 +272,9 @@ func (a *App) RenameDatabase(config connection.ConnectionConfig, oldName string,
|
||||
|
||||
dbType := resolveDDLDBType(config)
|
||||
switch dbType {
|
||||
case "mysql", "mariadb", "diros", "sphinx":
|
||||
return connection.QueryResult{Success: false, Message: "MySQL/MariaDB/Doris/Sphinx 不支持直接重命名数据库,请新建库后迁移数据"}
|
||||
case "postgres", "kingbase", "highgo", "vastbase":
|
||||
case "mysql", "mariadb", "oceanbase", "diros", "sphinx":
|
||||
return connection.QueryResult{Success: false, Message: "MySQL/MariaDB/OceanBase/Doris/Sphinx 不支持直接重命名数据库,请新建库后迁移数据"}
|
||||
case "postgres", "kingbase", "highgo", "vastbase", "opengauss":
|
||||
if strings.EqualFold(strings.TrimSpace(config.Database), oldName) {
|
||||
return connection.QueryResult{Success: false, Message: "当前连接正在使用目标数据库,请先连接到其他数据库后再重命名"}
|
||||
}
|
||||
@@ -297,11 +305,11 @@ func (a *App) DropDatabase(config connection.ConnectionConfig, dbName string) co
|
||||
sql string
|
||||
)
|
||||
switch dbType {
|
||||
case "mysql", "mariadb", "diros", "tdengine", "clickhouse":
|
||||
case "mysql", "mariadb", "oceanbase", "diros", "tdengine", "clickhouse":
|
||||
runConfig = config
|
||||
runConfig.Database = ""
|
||||
sql = fmt.Sprintf("DROP DATABASE %s", quoteIdentByType(dbType, dbName))
|
||||
case "postgres", "kingbase", "highgo", "vastbase":
|
||||
case "postgres", "kingbase", "highgo", "vastbase", "opengauss":
|
||||
if strings.EqualFold(strings.TrimSpace(config.Database), dbName) {
|
||||
return connection.QueryResult{Success: false, Message: "当前连接正在使用目标数据库,请先连接到其他数据库后再删除"}
|
||||
}
|
||||
@@ -336,7 +344,7 @@ func (a *App) RenameTable(config connection.ConnectionConfig, dbName string, old
|
||||
|
||||
dbType := resolveDDLDBType(config)
|
||||
switch dbType {
|
||||
case "mysql", "mariadb", "diros", "sphinx", "postgres", "kingbase", "sqlite", "duckdb", "oracle", "dameng", "highgo", "vastbase", "sqlserver", "clickhouse":
|
||||
case "mysql", "mariadb", "oceanbase", "diros", "sphinx", "postgres", "kingbase", "sqlite", "duckdb", "oracle", "dameng", "highgo", "vastbase", "opengauss", "sqlserver", "clickhouse":
|
||||
default:
|
||||
return connection.QueryResult{Success: false, Message: fmt.Sprintf("当前数据源(%s)暂不支持重命名表", dbType)}
|
||||
}
|
||||
@@ -350,7 +358,7 @@ func (a *App) RenameTable(config connection.ConnectionConfig, dbName string, old
|
||||
|
||||
var sql string
|
||||
switch dbType {
|
||||
case "mysql", "mariadb", "diros", "sphinx", "clickhouse":
|
||||
case "mysql", "mariadb", "oceanbase", "diros", "sphinx", "clickhouse":
|
||||
newQualifiedTable := quoteTableIdentByType(dbType, schemaName, newTableName)
|
||||
sql = fmt.Sprintf("RENAME TABLE %s TO %s", oldQualifiedTable, newQualifiedTable)
|
||||
case "sqlserver":
|
||||
@@ -382,7 +390,7 @@ func (a *App) DropTable(config connection.ConnectionConfig, dbName string, table
|
||||
|
||||
dbType := resolveDDLDBType(config)
|
||||
switch dbType {
|
||||
case "mysql", "mariadb", "diros", "sphinx", "postgres", "kingbase", "sqlite", "duckdb", "oracle", "dameng", "highgo", "vastbase", "sqlserver", "tdengine", "clickhouse":
|
||||
case "mysql", "mariadb", "oceanbase", "diros", "sphinx", "postgres", "kingbase", "sqlite", "duckdb", "oracle", "dameng", "highgo", "vastbase", "opengauss", "sqlserver", "tdengine", "clickhouse":
|
||||
default:
|
||||
return connection.QueryResult{Success: false, Message: fmt.Sprintf("当前数据源(%s)暂不支持删除表", dbType)}
|
||||
}
|
||||
@@ -990,7 +998,7 @@ func resolveCreateStatementWithFallback(dbInst db.Database, config connection.Co
|
||||
|
||||
func supportsCreateStatementFallback(dbType string) bool {
|
||||
switch dbType {
|
||||
case "postgres", "kingbase", "highgo", "vastbase":
|
||||
case "postgres", "kingbase", "highgo", "vastbase", "opengauss":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
@@ -999,7 +1007,7 @@ func supportsCreateStatementFallback(dbType string) bool {
|
||||
|
||||
func supportsViewCreateStatementLookup(dbType string) bool {
|
||||
switch dbType {
|
||||
case "mysql", "mariadb", "diros", "sphinx", "postgres", "kingbase", "highgo", "vastbase", "sqlserver", "oracle", "dameng", "sqlite", "duckdb", "clickhouse":
|
||||
case "mysql", "mariadb", "oceanbase", "diros", "sphinx", "postgres", "kingbase", "highgo", "vastbase", "opengauss", "sqlserver", "oracle", "dameng", "sqlite", "duckdb", "clickhouse":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
@@ -1180,7 +1188,7 @@ func (a *App) DropView(config connection.ConnectionConfig, dbName string, viewNa
|
||||
|
||||
dbType := resolveDDLDBType(config)
|
||||
switch dbType {
|
||||
case "mysql", "mariadb", "diros", "sphinx", "postgres", "kingbase", "sqlite", "duckdb", "oracle", "dameng", "highgo", "vastbase", "sqlserver", "clickhouse":
|
||||
case "mysql", "mariadb", "oceanbase", "diros", "sphinx", "postgres", "kingbase", "sqlite", "duckdb", "oracle", "dameng", "highgo", "vastbase", "opengauss", "sqlserver", "clickhouse":
|
||||
default:
|
||||
return connection.QueryResult{Success: false, Message: fmt.Sprintf("当前数据源(%s)暂不支持删除视图", dbType)}
|
||||
}
|
||||
@@ -1215,7 +1223,7 @@ func (a *App) DropFunction(config connection.ConnectionConfig, dbName string, ro
|
||||
|
||||
dbType := resolveDDLDBType(config)
|
||||
switch dbType {
|
||||
case "mysql", "mariadb", "diros", "sphinx", "postgres", "kingbase", "oracle", "dameng", "highgo", "vastbase", "sqlserver", "duckdb":
|
||||
case "mysql", "mariadb", "oceanbase", "diros", "sphinx", "postgres", "kingbase", "oracle", "dameng", "highgo", "vastbase", "opengauss", "sqlserver", "duckdb":
|
||||
default:
|
||||
return connection.QueryResult{Success: false, Message: fmt.Sprintf("当前数据源(%s)暂不支持删除函数/存储过程", dbType)}
|
||||
}
|
||||
@@ -1269,10 +1277,10 @@ func (a *App) RenameView(config connection.ConnectionConfig, dbName string, oldN
|
||||
|
||||
var sql string
|
||||
switch dbType {
|
||||
case "mysql", "mariadb", "diros", "sphinx", "clickhouse":
|
||||
case "mysql", "mariadb", "oceanbase", "diros", "sphinx", "clickhouse":
|
||||
newQualified := quoteTableIdentByType(dbType, schemaName, newName)
|
||||
sql = fmt.Sprintf("RENAME TABLE %s TO %s", oldQualified, newQualified)
|
||||
case "postgres", "kingbase", "highgo", "vastbase":
|
||||
case "postgres", "kingbase", "highgo", "vastbase", "opengauss":
|
||||
sql = fmt.Sprintf("ALTER VIEW %s RENAME TO %s", oldQualified, newQuoted)
|
||||
case "sqlserver":
|
||||
oldFullName := schemaName + "." + pureOldName
|
||||
|
||||
@@ -310,6 +310,7 @@ const builtinDriverManifestJSON = `{
|
||||
"drivers": {
|
||||
"mysql": { "engine": "go", "version": "1.9.3", "checksumPolicy": "off" },
|
||||
"mariadb": { "engine": "go", "version": "1.9.3", "checksumPolicy": "off", "downloadUrl": "builtin://activate/mariadb" },
|
||||
"oceanbase": { "engine": "go", "version": "1.9.3", "checksumPolicy": "off", "downloadUrl": "builtin://activate/oceanbase" },
|
||||
"doris": { "engine": "go", "version": "1.9.3", "checksumPolicy": "off", "downloadUrl": "builtin://activate/doris" },
|
||||
"sphinx": { "engine": "go", "version": "1.9.3", "checksumPolicy": "off", "downloadUrl": "builtin://activate/sphinx" },
|
||||
"sqlserver": { "engine": "go", "version": "1.9.6", "checksumPolicy": "off", "downloadUrl": "builtin://activate/sqlserver" },
|
||||
@@ -319,6 +320,7 @@ const builtinDriverManifestJSON = `{
|
||||
"kingbase": { "engine": "go", "version": "0.0.0-20201021123113-29bd62a876c3", "checksumPolicy": "off", "downloadUrl": "builtin://activate/kingbase" },
|
||||
"highgo": { "engine": "go", "version": "0.0.0-local", "checksumPolicy": "off", "downloadUrl": "builtin://activate/highgo" },
|
||||
"vastbase": { "engine": "go", "version": "1.11.1", "checksumPolicy": "off", "downloadUrl": "builtin://activate/vastbase" },
|
||||
"opengauss": { "engine": "go", "version": "1.11.1", "checksumPolicy": "off", "downloadUrl": "builtin://activate/opengauss" },
|
||||
"mongodb": { "engine": "go", "version": "2.5.0", "checksumPolicy": "off", "downloadUrl": "builtin://activate/mongodb" },
|
||||
"tdengine": { "engine": "go", "version": "3.7.8", "checksumPolicy": "off", "downloadUrl": "builtin://activate/tdengine" },
|
||||
"clickhouse": { "engine": "go", "version": "2.43.1", "checksumPolicy": "off", "downloadUrl": "builtin://activate/clickhouse" }
|
||||
@@ -363,6 +365,7 @@ var pinnedDriverPackageMap = map[string]pinnedDriverPackage{
|
||||
var latestDriverVersionMap = map[string]string{
|
||||
"mysql": "1.9.3",
|
||||
"mariadb": "1.9.3",
|
||||
"oceanbase": "1.9.3",
|
||||
"diros": "1.9.3",
|
||||
"sphinx": "1.9.3",
|
||||
"sqlserver": "1.9.6",
|
||||
@@ -372,6 +375,7 @@ var latestDriverVersionMap = map[string]string{
|
||||
"kingbase": "0.0.0-20201021123113-29bd62a876c3",
|
||||
"highgo": "0.0.0-local",
|
||||
"vastbase": "1.11.2",
|
||||
"opengauss": "1.11.1",
|
||||
"mongodb": "2.5.0",
|
||||
"tdengine": "3.7.8",
|
||||
"clickhouse": "2.43.1",
|
||||
@@ -382,6 +386,7 @@ var latestDriverVersionMap = map[string]string{
|
||||
|
||||
var driverGoModulePathMap = map[string]string{
|
||||
"mariadb": "github.com/go-sql-driver/mysql",
|
||||
"oceanbase": "github.com/go-sql-driver/mysql",
|
||||
"diros": "github.com/go-sql-driver/mysql",
|
||||
"sphinx": "github.com/go-sql-driver/mysql",
|
||||
"sqlserver": "github.com/microsoft/go-mssqldb",
|
||||
@@ -391,6 +396,7 @@ var driverGoModulePathMap = map[string]string{
|
||||
"kingbase": "gitea.com/kingbase/gokb",
|
||||
"highgo": "github.com/highgo/pq-sm3",
|
||||
"vastbase": "github.com/lib/pq",
|
||||
"opengauss": "github.com/lib/pq",
|
||||
"mongodb": "go.mongodb.org/mongo-driver/v2",
|
||||
"tdengine": "github.com/taosdata/driver-go/v3",
|
||||
"clickhouse": "github.com/ClickHouse/clickhouse-go/v2",
|
||||
@@ -1366,6 +1372,8 @@ func normalizeDriverType(driverType string) string {
|
||||
return "diros"
|
||||
case "postgresql":
|
||||
return "postgres"
|
||||
case "opengauss", "open_gauss", "open-gauss":
|
||||
return "opengauss"
|
||||
default:
|
||||
return normalized
|
||||
}
|
||||
@@ -1435,6 +1443,7 @@ func allDriverDefinitionsWithPackages(packages map[string]pinnedDriverPackage) [
|
||||
|
||||
// 其他数据源需要先在驱动管理中“安装启用”。
|
||||
buildOptionalGoDriverDefinition("mariadb", "MariaDB", packages),
|
||||
buildOptionalGoDriverDefinition("oceanbase", "OceanBase", packages),
|
||||
buildOptionalGoDriverDefinition("diros", "Doris", packages),
|
||||
buildOptionalGoDriverDefinition("sphinx", "Sphinx", packages),
|
||||
buildOptionalGoDriverDefinition("sqlserver", "SQL Server", packages),
|
||||
@@ -1444,6 +1453,7 @@ func allDriverDefinitionsWithPackages(packages map[string]pinnedDriverPackage) [
|
||||
buildOptionalGoDriverDefinition("kingbase", "Kingbase", packages),
|
||||
buildOptionalGoDriverDefinition("highgo", "HighGo", packages),
|
||||
buildOptionalGoDriverDefinition("vastbase", "Vastbase", packages),
|
||||
buildOptionalGoDriverDefinition("opengauss", "OpenGauss", packages),
|
||||
buildOptionalGoDriverDefinition("mongodb", "MongoDB", packages),
|
||||
buildOptionalGoDriverDefinition("tdengine", "TDengine", packages),
|
||||
buildOptionalGoDriverDefinition("clickhouse", "ClickHouse", packages),
|
||||
@@ -3589,6 +3599,8 @@ func optionalDriverBuildTag(driverType string, selectedVersion string) (string,
|
||||
return "gonavi_mysql_driver", nil
|
||||
case "mariadb":
|
||||
return "gonavi_mariadb_driver", nil
|
||||
case "oceanbase":
|
||||
return "gonavi_oceanbase_driver", nil
|
||||
case "diros":
|
||||
return "gonavi_diros_driver", nil
|
||||
case "sphinx":
|
||||
@@ -3607,6 +3619,8 @@ func optionalDriverBuildTag(driverType string, selectedVersion string) (string,
|
||||
return "gonavi_highgo_driver", nil
|
||||
case "vastbase":
|
||||
return "gonavi_vastbase_driver", nil
|
||||
case "opengauss":
|
||||
return "gonavi_opengauss_driver", nil
|
||||
case "mongodb":
|
||||
if resolveMongoDriverMajorFromVersion(selectedVersion) == 1 {
|
||||
return "gonavi_mongodb_driver_v1", nil
|
||||
|
||||
@@ -1219,7 +1219,7 @@ const (
|
||||
|
||||
func supportsTruncateTableForDBType(dbType string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(dbType)) {
|
||||
case "mysql", "mariadb", "postgres", "kingbase", "highgo", "vastbase", "sqlserver", "oracle", "dameng", "clickhouse", "duckdb":
|
||||
case "mysql", "mariadb", "oceanbase", "postgres", "kingbase", "highgo", "vastbase", "opengauss", "sqlserver", "oracle", "dameng", "clickhouse", "duckdb":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
@@ -1353,7 +1353,7 @@ func quoteIdentByType(dbType string, ident string) string {
|
||||
}
|
||||
|
||||
switch dbType {
|
||||
case "mysql", "mariadb", "diros", "sphinx", "tdengine", "clickhouse":
|
||||
case "mysql", "mariadb", "oceanbase", "diros", "sphinx", "tdengine", "clickhouse":
|
||||
return "`" + strings.ReplaceAll(ident, "`", "``") + "`"
|
||||
case "kingbase":
|
||||
cleaned := db.NormalizeKingbaseIdentifier(ident)
|
||||
@@ -1578,7 +1578,7 @@ func buildListViewQueries(config connection.ConnectionConfig, dbName string) []s
|
||||
dbType := resolveDDLDBType(config)
|
||||
escapedDbName := escapeSQLLiteral(dbName)
|
||||
switch dbType {
|
||||
case "mysql", "mariadb", "diros", "sphinx":
|
||||
case "mysql", "mariadb", "oceanbase", "diros", "sphinx":
|
||||
queries := []string{
|
||||
fmt.Sprintf(`SELECT TABLE_SCHEMA AS schema_name, TABLE_NAME AS object_name, TABLE_TYPE AS table_type FROM information_schema.tables WHERE TABLE_TYPE='VIEW' AND TABLE_SCHEMA='%s' ORDER BY TABLE_NAME`, escapedDbName),
|
||||
}
|
||||
@@ -1586,7 +1586,7 @@ func buildListViewQueries(config connection.ConnectionConfig, dbName string) []s
|
||||
queries = append(queries, fmt.Sprintf("SHOW FULL TABLES FROM %s WHERE Table_type = 'VIEW'", quoteIdentByType("mysql", dbName)))
|
||||
}
|
||||
return queries
|
||||
case "postgres", "kingbase", "highgo", "vastbase":
|
||||
case "postgres", "kingbase", "highgo", "vastbase", "opengauss":
|
||||
return []string{
|
||||
`SELECT table_schema AS schema_name, table_name AS object_name FROM information_schema.views WHERE table_schema NOT IN ('pg_catalog', 'information_schema') ORDER BY table_schema, table_name`,
|
||||
}
|
||||
@@ -1681,7 +1681,7 @@ func buildViewCreateQueries(config connection.ConnectionConfig, dbName, schemaNa
|
||||
escapedDB := escapeSQLLiteral(dbName)
|
||||
|
||||
switch dbType {
|
||||
case "mysql", "mariadb", "diros", "sphinx":
|
||||
case "mysql", "mariadb", "oceanbase", "diros", "sphinx":
|
||||
if safeSchema == "" {
|
||||
safeSchema = strings.TrimSpace(dbName)
|
||||
}
|
||||
@@ -1693,7 +1693,7 @@ func buildViewCreateQueries(config connection.ConnectionConfig, dbName, schemaNa
|
||||
return []string{
|
||||
fmt.Sprintf("SHOW CREATE VIEW %s", quoteIdentByType("mysql", safeView)),
|
||||
}
|
||||
case "postgres", "kingbase", "highgo", "vastbase":
|
||||
case "postgres", "kingbase", "highgo", "vastbase", "opengauss":
|
||||
if safeSchema == "" {
|
||||
safeSchema = "public"
|
||||
}
|
||||
@@ -1960,7 +1960,8 @@ func formatSQLValue(dbType string, v interface{}) string {
|
||||
case time.Time:
|
||||
return "'" + val.Format("2006-01-02 15:04:05") + "'"
|
||||
case string:
|
||||
if (strings.ToLower(strings.TrimSpace(dbType)) == "mysql" || strings.ToLower(strings.TrimSpace(dbType)) == "diros") && isMySQLHexLiteral(val) {
|
||||
normalizedType := strings.ToLower(strings.TrimSpace(dbType))
|
||||
if (normalizedType == "mysql" || normalizedType == "oceanbase" || normalizedType == "diros") && isMySQLHexLiteral(val) {
|
||||
return val
|
||||
}
|
||||
escaped := strings.ReplaceAll(val, "'", "''")
|
||||
|
||||
@@ -67,7 +67,7 @@ func isReadOnlySQLQuery(dbType string, query string) bool {
|
||||
|
||||
func sanitizeSQLForPgLike(dbType string, query string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(dbType)) {
|
||||
case "postgres", "kingbase", "highgo", "vastbase":
|
||||
case "postgres", "kingbase", "highgo", "vastbase", "opengauss":
|
||||
// 有些情况下会出现多层重复引用(例如 """"schema"""" 或 ""schema"""),单次修复不一定收敛。
|
||||
// 这里做有限次数的迭代,直到输出不再变化。
|
||||
out := query
|
||||
|
||||
@@ -120,6 +120,8 @@ func normalizeDatabaseType(dbType string) string {
|
||||
return "diros"
|
||||
case "postgresql":
|
||||
return "postgres"
|
||||
case "opengauss", "open_gauss", "open-gauss":
|
||||
return "opengauss"
|
||||
default:
|
||||
return normalized
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ package db
|
||||
|
||||
func registerOptionalDatabaseFactories() {
|
||||
registerDatabaseFactory(newOptionalDriverAgentDatabase("mariadb"), "mariadb")
|
||||
registerDatabaseFactory(newOptionalDriverAgentDatabase("oceanbase"), "oceanbase")
|
||||
registerDatabaseFactory(newOptionalDriverAgentDatabase("diros"), "diros", "doris")
|
||||
registerDatabaseFactory(newOptionalDriverAgentDatabase("sphinx"), "sphinx")
|
||||
registerDatabaseFactory(newOptionalDriverAgentDatabase("sqlserver"), "sqlserver")
|
||||
@@ -13,6 +14,7 @@ func registerOptionalDatabaseFactories() {
|
||||
registerDatabaseFactory(newOptionalDriverAgentDatabase("kingbase"), "kingbase")
|
||||
registerDatabaseFactory(newOptionalDriverAgentDatabase("highgo"), "highgo")
|
||||
registerDatabaseFactory(newOptionalDriverAgentDatabase("vastbase"), "vastbase")
|
||||
registerDatabaseFactory(newOptionalDriverAgentDatabase("opengauss"), "opengauss", "open_gauss", "open-gauss")
|
||||
registerDatabaseFactory(newOptionalDriverAgentDatabase("mongodb"), "mongodb")
|
||||
registerDatabaseFactory(newOptionalDriverAgentDatabase("tdengine"), "tdengine")
|
||||
registerDatabaseFactory(newOptionalDriverAgentDatabase("clickhouse"), "clickhouse")
|
||||
|
||||
@@ -4,6 +4,7 @@ package db
|
||||
|
||||
func registerOptionalDatabaseFactories() {
|
||||
registerDatabaseFactory(newOptionalDriverAgentDatabase("mariadb"), "mariadb")
|
||||
registerDatabaseFactory(newOptionalDriverAgentDatabase("oceanbase"), "oceanbase")
|
||||
registerDatabaseFactory(newOptionalDriverAgentDatabase("diros"), "diros", "doris")
|
||||
registerDatabaseFactory(newOptionalDriverAgentDatabase("sphinx"), "sphinx")
|
||||
registerDatabaseFactory(newOptionalDriverAgentDatabase("sqlserver"), "sqlserver")
|
||||
@@ -13,6 +14,7 @@ func registerOptionalDatabaseFactories() {
|
||||
registerDatabaseFactory(newOptionalDriverAgentDatabase("kingbase"), "kingbase")
|
||||
registerDatabaseFactory(newOptionalDriverAgentDatabase("highgo"), "highgo")
|
||||
registerDatabaseFactory(newOptionalDriverAgentDatabase("vastbase"), "vastbase")
|
||||
registerDatabaseFactory(newOptionalDriverAgentDatabase("opengauss"), "opengauss", "open_gauss", "open-gauss")
|
||||
registerDatabaseFactory(newOptionalDriverAgentDatabase("mongodb"), "mongodb")
|
||||
registerDatabaseFactory(newOptionalDriverAgentDatabase("tdengine"), "tdengine")
|
||||
registerDatabaseFactory(newOptionalDriverAgentDatabase("clickhouse"), "clickhouse")
|
||||
|
||||
@@ -4,18 +4,20 @@ package db
|
||||
|
||||
func init() {
|
||||
optionalDriverAgentRevisions = map[string]string{
|
||||
"mariadb": "src-d6c5c6717338834c",
|
||||
"diros": "src-ed4f0f64ed28d3fa",
|
||||
"sphinx": "src-f52324f0a812d7c8",
|
||||
"sqlserver": "src-ec165f18de9cd8b3",
|
||||
"sqlite": "src-9dea6c76bc931114",
|
||||
"duckdb": "src-14027ac1de3c50c7",
|
||||
"dameng": "src-1a08880ff5bbcf31",
|
||||
"kingbase": "src-28eed0e4d942b724",
|
||||
"highgo": "src-76146bf97f07f25c",
|
||||
"vastbase": "src-555b60c4863542b6",
|
||||
"mongodb": "src-2540a7350c4243aa",
|
||||
"tdengine": "src-ce3e4a9c46f6b92d",
|
||||
"clickhouse": "src-78e5ada4da56704d",
|
||||
"mariadb": "src-99785848cecbb326",
|
||||
"oceanbase": "src-3e0a7e0e0ab0b619",
|
||||
"diros": "src-3d09d05982abf8cf",
|
||||
"sphinx": "src-85601aaa25456cf1",
|
||||
"sqlserver": "src-703a625726deff98",
|
||||
"sqlite": "src-7d841dda22330f67",
|
||||
"duckdb": "src-6d3b9eefc9802162",
|
||||
"dameng": "src-8a758078ab1fd981",
|
||||
"kingbase": "src-0086ac3cb15dd34d",
|
||||
"highgo": "src-147aec3df7ef7461",
|
||||
"vastbase": "src-cb0a4f1e6633f810",
|
||||
"opengauss": "src-f9924897702b760d",
|
||||
"mongodb": "src-a4eab8b91194dc18",
|
||||
"tdengine": "src-f43c25ea8b55d81f",
|
||||
"clickhouse": "src-c24f610c0f65228f",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ var coreBuiltinDrivers = map[string]struct{}{
|
||||
// 注意:这是一种运行时门控(installed.json 标记),并不减少主二进制体积。
|
||||
var optionalGoDrivers = map[string]struct{}{
|
||||
"mariadb": {},
|
||||
"oceanbase": {},
|
||||
"diros": {},
|
||||
"sphinx": {},
|
||||
"sqlserver": {},
|
||||
@@ -31,6 +32,7 @@ var optionalGoDrivers = map[string]struct{}{
|
||||
"kingbase": {},
|
||||
"highgo": {},
|
||||
"vastbase": {},
|
||||
"opengauss": {},
|
||||
"mongodb": {},
|
||||
"tdengine": {},
|
||||
"clickhouse": {},
|
||||
@@ -53,6 +55,8 @@ func normalizeRuntimeDriverType(driverType string) string {
|
||||
return "diros"
|
||||
case "postgresql":
|
||||
return "postgres"
|
||||
case "opengauss", "open_gauss", "open-gauss":
|
||||
return "opengauss"
|
||||
default:
|
||||
return normalized
|
||||
}
|
||||
@@ -68,6 +72,8 @@ func driverDisplayName(driverType string) string {
|
||||
return "Redis"
|
||||
case "mariadb":
|
||||
return "MariaDB"
|
||||
case "oceanbase":
|
||||
return "OceanBase"
|
||||
case "diros":
|
||||
return "Doris"
|
||||
case "sphinx":
|
||||
@@ -88,6 +94,8 @@ func driverDisplayName(driverType string) string {
|
||||
return "HighGo"
|
||||
case "vastbase":
|
||||
return "Vastbase"
|
||||
case "opengauss":
|
||||
return "OpenGauss"
|
||||
case "mongodb":
|
||||
return "MongoDB"
|
||||
case "tdengine":
|
||||
|
||||
@@ -100,6 +100,24 @@ func TestManagedDriverRequiresInstallMarker(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewCompatibleDriversAreOptionalAgentDrivers(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
SetExternalDriverDownloadDirectory(tmpDir)
|
||||
|
||||
for _, driverType := range []string{"oceanbase", "opengauss", "open_gauss"} {
|
||||
if IsBuiltinDriver(driverType) {
|
||||
t.Fatalf("%s 不应是免安装内置驱动", driverType)
|
||||
}
|
||||
if !IsOptionalGoDriver(driverType) {
|
||||
t.Fatalf("%s 应走可选 driver-agent 链路", driverType)
|
||||
}
|
||||
supported, _ := DriverRuntimeSupportStatus(driverType)
|
||||
if supported {
|
||||
t.Fatalf("%s 未安装 agent 时不应可用", driverType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMySQLBuiltinRuntimeSupportAvailable(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
SetExternalDriverDownloadDirectory(tmpDir)
|
||||
|
||||
@@ -194,7 +194,7 @@ func buildMySQLCompatibleDSN(config connection.ConnectionConfig, protocol, addre
|
||||
params.Set("timeout", fmt.Sprintf("%ds", timeout))
|
||||
params.Set("tls", tlsMode)
|
||||
params.Set("multiStatements", "true")
|
||||
if parsed, ok := parseMySQLCompatibleURI(config.URI, "mysql", "doris", "diros"); ok {
|
||||
if parsed, ok := parseMySQLCompatibleURI(config.URI, "mysql", "doris", "diros", "oceanbase"); ok {
|
||||
mergeMySQLConnectionParams(params, parsed.Query())
|
||||
}
|
||||
mergeMySQLConnectionParams(params, mysqlConnectionParamsFromText(config.ConnectionParams))
|
||||
|
||||
191
internal/db/oceanbase_impl.go
Normal file
191
internal/db/oceanbase_impl.go
Normal file
@@ -0,0 +1,191 @@
|
||||
//go:build gonavi_full_drivers || gonavi_oceanbase_driver
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"GoNavi-Wails/internal/connection"
|
||||
"GoNavi-Wails/internal/ssh"
|
||||
"GoNavi-Wails/internal/utils"
|
||||
|
||||
mysqlDriver "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
const (
|
||||
oceanbaseDriverName = "oceanbase"
|
||||
defaultOceanBasePort = 2881
|
||||
)
|
||||
|
||||
// OceanBaseDB 使用独立 driver 名称接入,底层协议兼容 MySQL。
|
||||
type OceanBaseDB struct {
|
||||
MySQLDB
|
||||
}
|
||||
|
||||
func init() {
|
||||
for _, name := range sql.Drivers() {
|
||||
if name == oceanbaseDriverName {
|
||||
return
|
||||
}
|
||||
}
|
||||
sql.Register(oceanbaseDriverName, &mysqlDriver.MySQLDriver{})
|
||||
}
|
||||
|
||||
func applyOceanBaseURI(config connection.ConnectionConfig) connection.ConnectionConfig {
|
||||
uriText := strings.TrimSpace(config.URI)
|
||||
if uriText == "" {
|
||||
return config
|
||||
}
|
||||
parsed, ok := parseMySQLCompatibleURI(uriText, "oceanbase", "mysql")
|
||||
if !ok {
|
||||
return config
|
||||
}
|
||||
|
||||
if parsed.User != nil {
|
||||
if config.User == "" {
|
||||
config.User = parsed.User.Username()
|
||||
}
|
||||
if pass, ok := parsed.User.Password(); ok && config.Password == "" {
|
||||
config.Password = pass
|
||||
}
|
||||
}
|
||||
|
||||
if dbName := strings.TrimPrefix(parsed.Path, "/"); dbName != "" && config.Database == "" {
|
||||
config.Database = dbName
|
||||
}
|
||||
|
||||
defaultPort := config.Port
|
||||
if defaultPort <= 0 {
|
||||
defaultPort = defaultOceanBasePort
|
||||
}
|
||||
|
||||
hostsFromURI := make([]string, 0, 4)
|
||||
hostText := strings.TrimSpace(parsed.Host)
|
||||
if hostText != "" {
|
||||
for _, entry := range strings.Split(hostText, ",") {
|
||||
host, port, ok := parseHostPortWithDefault(entry, defaultPort)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
hostsFromURI = append(hostsFromURI, normalizeMySQLAddress(host, port))
|
||||
}
|
||||
}
|
||||
|
||||
if len(config.Hosts) == 0 && len(hostsFromURI) > 0 {
|
||||
config.Hosts = hostsFromURI
|
||||
}
|
||||
if strings.TrimSpace(config.Host) == "" && len(hostsFromURI) > 0 {
|
||||
host, port, ok := parseHostPortWithDefault(hostsFromURI[0], defaultPort)
|
||||
if ok {
|
||||
config.Host = host
|
||||
config.Port = port
|
||||
}
|
||||
}
|
||||
|
||||
if config.Topology == "" {
|
||||
topology := strings.TrimSpace(parsed.Query().Get("topology"))
|
||||
if topology != "" {
|
||||
config.Topology = strings.ToLower(topology)
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func collectOceanBaseAddresses(config connection.ConnectionConfig) []string {
|
||||
defaultPort := config.Port
|
||||
if defaultPort <= 0 {
|
||||
defaultPort = defaultOceanBasePort
|
||||
}
|
||||
|
||||
candidates := make([]string, 0, len(config.Hosts)+1)
|
||||
if len(config.Hosts) > 0 {
|
||||
candidates = append(candidates, config.Hosts...)
|
||||
} else {
|
||||
candidates = append(candidates, normalizeMySQLAddress(config.Host, defaultPort))
|
||||
}
|
||||
|
||||
result := make([]string, 0, len(candidates))
|
||||
seen := make(map[string]struct{}, len(candidates))
|
||||
for _, entry := range candidates {
|
||||
host, port, ok := parseHostPortWithDefault(entry, defaultPort)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
normalized := normalizeMySQLAddress(host, port)
|
||||
if _, exists := seen[normalized]; exists {
|
||||
continue
|
||||
}
|
||||
seen[normalized] = struct{}{}
|
||||
result = append(result, normalized)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (o *OceanBaseDB) getDSN(config connection.ConnectionConfig) (string, error) {
|
||||
database := config.Database
|
||||
protocol := "tcp"
|
||||
address := normalizeMySQLAddress(config.Host, config.Port)
|
||||
|
||||
if config.UseSSH {
|
||||
netName, err := ssh.RegisterSSHNetwork(config.SSH)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("创建 SSH 隧道失败:%w", err)
|
||||
}
|
||||
protocol = netName
|
||||
}
|
||||
|
||||
return buildMySQLCompatibleDSN(config, protocol, address, database), nil
|
||||
}
|
||||
|
||||
func (o *OceanBaseDB) Connect(config connection.ConnectionConfig) error {
|
||||
runConfig := applyOceanBaseURI(config)
|
||||
addresses := collectOceanBaseAddresses(runConfig)
|
||||
if len(addresses) == 0 {
|
||||
return fmt.Errorf("连接建立后验证失败:未找到可用的 OceanBase 地址")
|
||||
}
|
||||
|
||||
var errorDetails []string
|
||||
for index, address := range addresses {
|
||||
candidateConfig := runConfig
|
||||
host, port, ok := parseHostPortWithDefault(address, defaultOceanBasePort)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
candidateConfig.Host = host
|
||||
candidateConfig.Port = port
|
||||
candidateConfig.User, candidateConfig.Password = resolveMySQLCredential(runConfig, index)
|
||||
|
||||
dsn, err := o.getDSN(candidateConfig)
|
||||
if err != nil {
|
||||
errorDetails = append(errorDetails, fmt.Sprintf("%s 生成连接串失败: %v", address, err))
|
||||
continue
|
||||
}
|
||||
db, err := sql.Open(oceanbaseDriverName, dsn)
|
||||
if err != nil {
|
||||
errorDetails = append(errorDetails, fmt.Sprintf("%s 打开失败: %v", address, err))
|
||||
continue
|
||||
}
|
||||
|
||||
timeout := getConnectTimeout(candidateConfig)
|
||||
ctx, cancel := utils.ContextWithTimeout(timeout)
|
||||
pingErr := db.PingContext(ctx)
|
||||
cancel()
|
||||
if pingErr != nil {
|
||||
_ = db.Close()
|
||||
errorDetails = append(errorDetails, fmt.Sprintf("%s 验证失败: %v", address, pingErr))
|
||||
continue
|
||||
}
|
||||
|
||||
o.conn = db
|
||||
o.pingTimeout = timeout
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(errorDetails) == 0 {
|
||||
return fmt.Errorf("连接建立后验证失败:未找到可用的 OceanBase 地址")
|
||||
}
|
||||
return fmt.Errorf("连接建立后验证失败:%s", strings.Join(errorDetails, ";"))
|
||||
}
|
||||
83
internal/db/opengauss_impl.go
Normal file
83
internal/db/opengauss_impl.go
Normal file
@@ -0,0 +1,83 @@
|
||||
//go:build gonavi_full_drivers || gonavi_opengauss_driver
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"GoNavi-Wails/internal/connection"
|
||||
)
|
||||
|
||||
const defaultOpenGaussPort = 5432
|
||||
|
||||
// OpenGaussDB 使用 PostgreSQL wire protocol 兼容链路,通过独立 agent 类型暴露。
|
||||
type OpenGaussDB struct {
|
||||
PostgresDB
|
||||
}
|
||||
|
||||
func applyOpenGaussURI(config connection.ConnectionConfig) connection.ConnectionConfig {
|
||||
uriText := strings.TrimSpace(config.URI)
|
||||
if uriText == "" {
|
||||
return config
|
||||
}
|
||||
parsed, ok := parseConnectionURI(uriText, "opengauss", "postgres", "postgresql")
|
||||
if !ok {
|
||||
return config
|
||||
}
|
||||
|
||||
if parsed.User != nil {
|
||||
if config.User == "" {
|
||||
config.User = parsed.User.Username()
|
||||
}
|
||||
if pass, ok := parsed.User.Password(); ok && config.Password == "" {
|
||||
config.Password = pass
|
||||
}
|
||||
}
|
||||
|
||||
if dbName := strings.TrimPrefix(parsed.Path, "/"); dbName != "" && config.Database == "" {
|
||||
config.Database = dbName
|
||||
}
|
||||
|
||||
defaultPort := config.Port
|
||||
if defaultPort <= 0 {
|
||||
defaultPort = defaultOpenGaussPort
|
||||
}
|
||||
if strings.TrimSpace(config.Host) == "" && strings.TrimSpace(parsed.Host) != "" {
|
||||
host, port, ok := parseHostPortWithDefault(parsed.Host, defaultPort)
|
||||
if ok {
|
||||
config.Host = host
|
||||
config.Port = port
|
||||
}
|
||||
}
|
||||
if config.Port <= 0 {
|
||||
config.Port = defaultOpenGaussPort
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func (o *OpenGaussDB) getDSN(config connection.ConnectionConfig) string {
|
||||
runConfig := applyOpenGaussURI(config)
|
||||
if runConfig.Port <= 0 {
|
||||
runConfig.Port = defaultOpenGaussPort
|
||||
}
|
||||
if strings.TrimSpace(runConfig.Host) != "" {
|
||||
if host, port, err := net.SplitHostPort(runConfig.Host); err == nil {
|
||||
runConfig.Host = host
|
||||
if p, convErr := strconv.Atoi(port); convErr == nil && p > 0 {
|
||||
runConfig.Port = p
|
||||
}
|
||||
}
|
||||
}
|
||||
return o.PostgresDB.getDSN(runConfig)
|
||||
}
|
||||
|
||||
func (o *OpenGaussDB) Connect(config connection.ConnectionConfig) error {
|
||||
runConfig := applyOpenGaussURI(config)
|
||||
if runConfig.Port <= 0 {
|
||||
runConfig.Port = defaultOpenGaussPort
|
||||
}
|
||||
return o.PostgresDB.Connect(runConfig)
|
||||
}
|
||||
@@ -37,6 +37,7 @@ const (
|
||||
optionalAgentMethodGetTriggers = "getTriggers"
|
||||
optionalAgentMethodApplyChanges = "applyChanges"
|
||||
optionalAgentDefaultScannerMaxBytes = 8 << 20
|
||||
optionalAgentMetadataProbeTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
type optionalAgentRequest struct {
|
||||
@@ -86,7 +87,7 @@ func ProbeOptionalDriverAgentMetadata(driverType string, executablePath string)
|
||||
}()
|
||||
|
||||
var metadata OptionalDriverAgentMetadata
|
||||
if err := client.call(optionalAgentRequest{Method: optionalAgentMethodMetadata}, &metadata, nil, nil); err != nil {
|
||||
if err := client.callWithTimeout(optionalAgentRequest{Method: optionalAgentMethodMetadata}, &metadata, nil, nil, optionalAgentMetadataProbeTimeout); err != nil {
|
||||
return OptionalDriverAgentMetadata{}, err
|
||||
}
|
||||
metadata.DriverType = normalizeRuntimeDriverType(metadata.DriverType)
|
||||
@@ -243,6 +244,37 @@ func (c *optionalDriverAgentClient) call(req optionalAgentRequest, out interface
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *optionalDriverAgentClient) callWithTimeout(req optionalAgentRequest, out interface{}, fields *[]string, rowsAffected *int64, timeout time.Duration) error {
|
||||
if timeout <= 0 {
|
||||
return c.call(req, out, fields, rowsAffected)
|
||||
}
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
errCh <- c.call(req, out, fields, rowsAffected)
|
||||
}()
|
||||
|
||||
timer := time.NewTimer(timeout)
|
||||
defer timer.Stop()
|
||||
|
||||
select {
|
||||
case err := <-errCh:
|
||||
return err
|
||||
case <-timer.C:
|
||||
c.forceTerminate()
|
||||
return fmt.Errorf("%s 驱动代理 metadata 探测超时(%s),请确认导入的是正确的 driver-agent 可执行文件", driverDisplayName(c.driver), timeout)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *optionalDriverAgentClient) forceTerminate() {
|
||||
if c.stdin != nil {
|
||||
_ = c.stdin.Close()
|
||||
}
|
||||
if c.cmd != nil && c.cmd.Process != nil {
|
||||
_ = c.cmd.Process.Kill()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *optionalDriverAgentClient) close() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
@@ -64,7 +64,7 @@ func (p *PostgresDB) getDSN(config connection.ConnectionConfig) string {
|
||||
q := url.Values{}
|
||||
q.Set("sslmode", resolvePostgresSSLMode(config))
|
||||
q.Set("connect_timeout", strconv.Itoa(getConnectTimeoutSeconds(config)))
|
||||
mergeConnectionParamsFromConfig(q, config, "postgres", "postgresql")
|
||||
mergeConnectionParamsFromConfig(q, config, "postgres", "postgresql", "opengauss")
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
return u.String()
|
||||
|
||||
@@ -107,7 +107,7 @@ func isMySQLLikeType(dbType string) bool {
|
||||
|
||||
func classifyMigrationDataModel(dbType string) MigrationDataModel {
|
||||
switch normalizeMigrationDBType(dbType) {
|
||||
case "mysql", "mariadb", "postgres", "kingbase", "highgo", "vastbase", "oracle", "sqlserver", "dameng", "sqlite", "duckdb":
|
||||
case "mysql", "mariadb", "oceanbase", "postgres", "kingbase", "highgo", "vastbase", "opengauss", "oracle", "sqlserver", "dameng", "sqlite", "duckdb":
|
||||
return MigrationDataModelRelational
|
||||
case "mongodb":
|
||||
return MigrationDataModelDocument
|
||||
|
||||
@@ -12,6 +12,8 @@ func normalizeMigrationDBType(dbType string) string {
|
||||
return "diros"
|
||||
case "postgresql":
|
||||
return "postgres"
|
||||
case "opengauss", "open_gauss", "open-gauss":
|
||||
return "opengauss"
|
||||
case "dm", "dm8":
|
||||
return "dameng"
|
||||
case "sqlite3":
|
||||
@@ -31,6 +33,8 @@ func resolveMigrationDBType(config connection.ConnectionConfig) string {
|
||||
switch driver {
|
||||
case "postgresql", "postgres", "pg", "pq", "pgx":
|
||||
return "postgres"
|
||||
case "opengauss", "open_gauss", "open-gauss":
|
||||
return "opengauss"
|
||||
case "dm", "dameng", "dm8":
|
||||
return "dameng"
|
||||
case "sqlite3", "sqlite":
|
||||
@@ -45,6 +49,8 @@ func resolveMigrationDBType(config connection.ConnectionConfig) string {
|
||||
return "highgo"
|
||||
case "vastbase":
|
||||
return "vastbase"
|
||||
case "oceanbase":
|
||||
return "oceanbase"
|
||||
case "mysql", "mysql2":
|
||||
return "mysql"
|
||||
case "mariadb":
|
||||
@@ -52,6 +58,8 @@ func resolveMigrationDBType(config connection.ConnectionConfig) string {
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.Contains(driver, "opengauss"), strings.Contains(driver, "open_gauss"), strings.Contains(driver, "open-gauss"):
|
||||
return "opengauss"
|
||||
case strings.Contains(driver, "postgres"):
|
||||
return "postgres"
|
||||
case strings.Contains(driver, "kingbase"):
|
||||
@@ -68,6 +76,8 @@ func resolveMigrationDBType(config connection.ConnectionConfig) string {
|
||||
return "diros"
|
||||
case strings.Contains(driver, "maria"):
|
||||
return "mariadb"
|
||||
case strings.Contains(driver, "oceanbase"):
|
||||
return "oceanbase"
|
||||
case strings.Contains(driver, "mysql"):
|
||||
return "mysql"
|
||||
case strings.Contains(driver, "dameng"), strings.Contains(driver, "dm"):
|
||||
@@ -79,7 +89,7 @@ func resolveMigrationDBType(config connection.ConnectionConfig) string {
|
||||
|
||||
func isMySQLCoreType(dbType string) bool {
|
||||
switch normalizeMigrationDBType(dbType) {
|
||||
case "mysql", "mariadb", "diros":
|
||||
case "mysql", "mariadb", "oceanbase", "diros":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
||||
@@ -561,7 +561,7 @@ func intFromAny(v interface{}) int {
|
||||
|
||||
func isPGLikeSource(dbType string) bool {
|
||||
switch normalizeMigrationDBType(dbType) {
|
||||
case "postgres", "kingbase", "highgo", "vastbase", "duckdb":
|
||||
case "postgres", "kingbase", "highgo", "vastbase", "opengauss", "duckdb":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
@@ -852,7 +852,7 @@ func mapPGLikeDefaultToMySQL(col connection.ColumnDefinition, targetType string)
|
||||
|
||||
func isPGLikeTarget(dbType string) bool {
|
||||
switch normalizeMigrationDBType(dbType) {
|
||||
case "postgres", "kingbase", "highgo", "vastbase", "duckdb":
|
||||
case "postgres", "kingbase", "highgo", "vastbase", "opengauss", "duckdb":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
||||
@@ -277,16 +277,16 @@ func (s *SyncEngine) previewSourceQuery(config SyncConfig, limit int) (TableDiff
|
||||
|
||||
inserts, updates, deletes, _ := diffRowsByPK(ctx.PKColumn, ctx.SourceRows, ctx.TargetRows)
|
||||
out := TableDiffPreview{
|
||||
Table: ctx.TableName,
|
||||
PKColumn: ctx.PKColumn,
|
||||
ColumnTypes: make(map[string]string, len(ctx.TargetCols)),
|
||||
Table: ctx.TableName,
|
||||
PKColumn: ctx.PKColumn,
|
||||
ColumnTypes: make(map[string]string, len(ctx.TargetCols)),
|
||||
SchemaSummary: "SQL 结果集同步预览",
|
||||
TotalInserts: len(inserts),
|
||||
TotalUpdates: len(updates),
|
||||
TotalDeletes: len(deletes),
|
||||
Inserts: make([]PreviewRow, 0, minInt(limit, len(inserts))),
|
||||
Updates: make([]PreviewUpdateRow, 0, minInt(limit, len(updates))),
|
||||
Deletes: make([]PreviewRow, 0, minInt(limit, len(deletes))),
|
||||
TotalInserts: len(inserts),
|
||||
TotalUpdates: len(updates),
|
||||
TotalDeletes: len(deletes),
|
||||
Inserts: make([]PreviewRow, 0, minInt(limit, len(inserts))),
|
||||
Updates: make([]PreviewUpdateRow, 0, minInt(limit, len(updates))),
|
||||
Deletes: make([]PreviewRow, 0, minInt(limit, len(deletes))),
|
||||
}
|
||||
for _, col := range ctx.TargetCols {
|
||||
name := strings.ToLower(strings.TrimSpace(col.Name))
|
||||
@@ -433,7 +433,7 @@ func (s *SyncEngine) runSourceQuerySync(config SyncConfig) SyncResult {
|
||||
|
||||
applyTableName := ctx.TargetTable
|
||||
switch ctx.TargetType {
|
||||
case "postgres", "kingbase", "highgo", "vastbase", "sqlserver":
|
||||
case "postgres", "kingbase", "highgo", "vastbase", "opengauss", "sqlserver":
|
||||
applyTableName = ctx.TargetQueryTable
|
||||
}
|
||||
applier, ok := targetDB.(db.BatchApplier)
|
||||
|
||||
@@ -22,7 +22,7 @@ func quoteIdentByType(dbType string, ident string) string {
|
||||
}
|
||||
|
||||
switch dbType {
|
||||
case "mysql", "mariadb", "diros", "sphinx", "clickhouse", "tdengine":
|
||||
case "mysql", "mariadb", "oceanbase", "diros", "sphinx", "clickhouse", "tdengine":
|
||||
return "`" + strings.ReplaceAll(ident, "`", "``") + "`"
|
||||
case "sqlserver":
|
||||
escaped := strings.ReplaceAll(ident, "]", "]]")
|
||||
@@ -74,7 +74,7 @@ func normalizeSchemaAndTable(dbType string, dbName string, tableName string) (st
|
||||
}
|
||||
|
||||
switch strings.ToLower(strings.TrimSpace(dbType)) {
|
||||
case "postgres", "kingbase", "highgo", "vastbase":
|
||||
case "postgres", "kingbase", "highgo", "vastbase", "opengauss":
|
||||
return "public", rawTable
|
||||
case "duckdb":
|
||||
return "main", rawTable
|
||||
@@ -93,7 +93,7 @@ func qualifiedNameForQuery(dbType string, schema string, table string, original
|
||||
}
|
||||
|
||||
switch strings.ToLower(strings.TrimSpace(dbType)) {
|
||||
case "postgres", "kingbase", "highgo", "vastbase":
|
||||
case "postgres", "kingbase", "highgo", "vastbase", "opengauss":
|
||||
s := strings.TrimSpace(schema)
|
||||
if s == "" {
|
||||
s = "public"
|
||||
@@ -111,7 +111,7 @@ func qualifiedNameForQuery(dbType string, schema string, table string, original
|
||||
return raw
|
||||
}
|
||||
return s + "." + table
|
||||
case "mysql", "mariadb", "diros", "sphinx", "clickhouse", "tdengine":
|
||||
case "mysql", "mariadb", "oceanbase", "diros", "sphinx", "clickhouse", "tdengine":
|
||||
s := strings.TrimSpace(schema)
|
||||
if s == "" || table == "" {
|
||||
return table
|
||||
|
||||
@@ -206,7 +206,7 @@ func (s *SyncEngine) RunSync(config SyncConfig) SyncResult {
|
||||
sourceQueryTable, targetQueryTable := plan.SourceQueryTable, plan.TargetQueryTable
|
||||
applyTableName := targetTable
|
||||
switch targetType {
|
||||
case "postgres", "kingbase", "highgo", "vastbase", "sqlserver":
|
||||
case "postgres", "kingbase", "highgo", "vastbase", "opengauss", "sqlserver":
|
||||
applyTableName = targetQueryTable
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
DEFAULT_DRIVERS=(mariadb diros sphinx sqlserver sqlite duckdb dameng kingbase highgo vastbase mongodb tdengine clickhouse)
|
||||
DEFAULT_DRIVERS=(mariadb oceanbase diros sphinx sqlserver sqlite duckdb dameng kingbase highgo vastbase opengauss mongodb tdengine clickhouse)
|
||||
OUTPUT_FILE="internal/db/driver_agent_revisions_gen.go"
|
||||
|
||||
usage() {
|
||||
@@ -25,6 +25,8 @@ normalize_driver() {
|
||||
value="$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')"
|
||||
case "$value" in
|
||||
doris|diros) echo "diros" ;;
|
||||
oceanbase) echo "oceanbase" ;;
|
||||
opengauss|open_gauss|open-gauss) echo "opengauss" ;;
|
||||
mariadb|diros|sphinx|sqlserver|sqlite|duckdb|dameng|kingbase|highgo|vastbase|mongodb|tdengine|clickhouse)
|
||||
echo "$value"
|
||||
;;
|
||||
@@ -79,6 +81,8 @@ internal/db/timeout.go)
|
||||
|
||||
case "$driver:$identity" in
|
||||
mariadb:internal/db/mariadb_impl.go|\
|
||||
oceanbase:internal/db/oceanbase_impl.go|\
|
||||
oceanbase:internal/db/mysql_impl.go|\
|
||||
diros:internal/db/diros_impl.go|\
|
||||
diros:internal/db/mysql_impl.go|\
|
||||
sphinx:internal/db/sphinx_impl.go|\
|
||||
@@ -95,6 +99,8 @@ kingbase:internal/db/kingbase_impl.go|\
|
||||
kingbase:internal/db/kingbase_identifier_utils.go|\
|
||||
highgo:internal/db/highgo_impl.go|\
|
||||
vastbase:internal/db/vastbase_impl.go|\
|
||||
opengauss:internal/db/opengauss_impl.go|\
|
||||
opengauss:internal/db/postgres_impl.go|\
|
||||
mongodb:internal/db/mongodb_impl.go|\
|
||||
mongodb:internal/db/mongodb_impl_v1.go|\
|
||||
tdengine:internal/db/tdengine_impl.go|\
|
||||
|
||||
Reference in New Issue
Block a user