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:
Syngnat
2026-04-30 13:13:01 +08:00
parent c92959f3e8
commit 7fd6d78c83
55 changed files with 859 additions and 178 deletions

View File

@@ -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"

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View 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{}
}
}

View 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{}
}
}

View File

@@ -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",

View File

@@ -1 +1 @@
d0464f9da25e9356e61652e638c99ffe
0295a42fd931778d85157816d79d29e5

View File

@@ -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 />,

View File

@@ -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') {

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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`];
}

View File

@@ -494,12 +494,14 @@ describe('QueryEditor external SQL save', () => {
it.each([
'mysql',
'mariadb',
'oceanbase',
'diros',
'sphinx',
'postgres',
'kingbase',
'highgo',
'vastbase',
'opengauss',
'sqlserver',
'sqlite',
'duckdb',

View File

@@ -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$$;`;

View File

@@ -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}]`;

View File

@@ -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

View File

@@ -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': {

View File

@@ -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);
});

View File

@@ -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':

View File

@@ -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;
};

View File

@@ -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',

View File

@@ -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']);

View File

@@ -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',

View File

@@ -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');
});
});

View File

@@ -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 扩展驱动。';

View File

@@ -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',

View File

@@ -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;
};

View File

@@ -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 '';
}

View File

@@ -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');

View File

@@ -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;

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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, "'", "''")

View File

@@ -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

View File

@@ -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
}

View File

@@ -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")

View File

@@ -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")

View File

@@ -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",
}
}

View File

@@ -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":

View File

@@ -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)

View File

@@ -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))

View 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, ""))
}

View 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)
}

View File

@@ -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()

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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
}

View File

@@ -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|\