Skip to content
This repository was archived by the owner on May 25, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
12ea2da
deps: lock indirect & add pingcap/parser sjjian fork replace for loca…
LordofAvernus May 21, 2026
e030dd9
docs: README 增补沙箱本地构建命令 + go.mod replace 移除提示 (#2905)
LordofAvernus May 21, 2026
2cfe5a3
deps: promote github.com/pkg/errors to direct require (#2905)
LordofAvernus May 21, 2026
150721d
feat: implement Dialector with DSN builder for GaussDB/openGauss (#2905)
LordofAvernus May 21, 2026
f66cd20
deps: promote DATA-DOG/go-sqlmock to direct require (#2905)
LordofAvernus May 21, 2026
bebd66b
feat(driver): split stub into driver_common / driver_gaussdb / driver…
LordofAvernus May 21, 2026
665f031
feat(extractor): implement Extract entry + TABLE/VIEW DDL extraction …
LordofAvernus May 21, 2026
b97484e
feat(extractor): implement FUNCTION/PROCEDURE extraction with overloa…
LordofAvernus May 21, 2026
ae3ac2d
feat(differ): implement GenerateModifySQLs for 4 object types x 3 dif…
LordofAvernus May 21, 2026
362f892
test(differ): add map case unit tests for GenerateModifySQLs (#2905)
LordofAvernus May 21, 2026
9096b95
feat(driver): wire GetDatabaseObjectDDL/GetDatabaseDiffModifySQL to e…
LordofAvernus May 21, 2026
99a69ac
fix(extractor): empty DatabaseObjects → enumerate schema (P0 Task-Tes…
LordofAvernus May 21, 2026
ce460cc
fix(extractor): split pg_get_functiondef double-column query (P1.1 Ta…
LordofAvernus May 21, 2026
e320194
fix(differ): rewrite base schema qualifier to compared (P1.2 Task-Tes…
LordofAvernus May 21, 2026
d2f3d9e
fix(extractor): use (pg_get_functiondef).definition for GaussDB recor…
LordofAvernus May 21, 2026
d183e39
fix(extractor/procedure): strip PL/SQL trailing slash from PROCEDURE …
LordofAvernus May 21, 2026
88b501a
fix(differ): TABLE SET search_path rewrite + USTORE normalize + schem…
LordofAvernus May 21, 2026
fe9519a
fix(driver): strip ObjectName signature at plugin entry to recover UI…
LordofAvernus May 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,27 @@ PluginName 字面值必须与 sqle 主仓 `sqle/sqle/driver/v2/util.go` 中的 `
make docker_install
```

> 注:当前 `go.mod` 内 `replace ../sqle` / `replace ../sqle-pg-plugin` 是为了让本地开发
> 沙箱(无内网 GOPROXY、无法拉 actiontech 私有仓库 pseudo-version)也能直接
> `go build`。在 CI 与正式发布环境(具备内网 GOPROXY 可拉到锁定 tag)下,请在
> code_review 阶段或 vendor 落盘前**移除这两条 replace**——require 块的版本号已是
> 生产形态。

### 开发期沙箱本地构建(无内网 GOPROXY 时)

```bash
docker run --rm --network host \
-v /path/to/workspace:/workspace \
-e GOPROXY="https://goproxy.cn,direct" \
-e GOSUMDB=off \
-w /workspace/sqle-gaussdb-plugin \
golang:1.19.6 sh -c "
git config --global --add safe.directory /workspace/sqle-gaussdb-plugin && \
GOOS=linux GOARCH=amd64 go build -o bin/sqle-gaussdb-plugin ./cmd/sqle-gaussdb-plugin && \
GOOS=linux GOARCH=amd64 go build -o bin/sqle-opengauss-plugin ./cmd/sqle-opengauss-plugin
"
```

构建产物(位于 `./bin/`):

```
Expand Down
12 changes: 9 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/go-testing-interface v1.14.0 // indirect
github.com/oklog/run v1.0.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/errors v0.9.1
golang.org/x/crypto v0.42.0 // indirect
golang.org/x/net v0.44.0 // indirect
golang.org/x/sys v0.44.0 // indirect
Expand All @@ -43,9 +43,10 @@ require (
google.golang.org/protobuf v1.34.2 // indirect
)

require github.com/DATA-DOG/go-sqlmock v1.5.0

require (
github.com/360EntSecGroup-Skylar/excelize v1.4.1 // indirect
github.com/DATA-DOG/go-sqlmock v1.5.0 // indirect
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
github.com/actiontech/dms v0.0.0-20260520024857-5bc318ea23da // indirect
github.com/bwmarrin/snowflake v0.3.0 // indirect
Expand Down Expand Up @@ -103,10 +104,15 @@ require (
// slices 标准包的更新版本(典型崩溃版本:crypto v0.42.0 / sys v0.44.0)。版本号来自
// sqle-pg-plugin/go.sum 实测。
replace (

github.com/actiontech/dms => github.com/actiontech/dms v0.0.0-20251027081421-309bc24335ca
github.com/actiontech/sqle => ../sqle
github.com/actiontech/sqle-pg-plugin => ../sqle-pg-plugin

// sqle 主仓 vendor 内已锁定 sjjian/parser fork(含 parser.Token 类型扩展),
// 本仓库 module 必须同步该 replace 才能让 ../sqle 内部的 mysql/splitter 包编译过。
// 版本号取自 sqle-pg-plugin/go.mod line 79。
github.com/pingcap/parser => github.com/sjjian/parser v0.0.0-20240704052347-b6199b7bccae

golang.org/x/crypto => golang.org/x/crypto v0.14.0
golang.org/x/net => golang.org/x/net v0.17.0
golang.org/x/sync => golang.org/x/sync v0.1.0
Expand Down
929 changes: 929 additions & 0 deletions go.sum

Large diffs are not rendered by default.

193 changes: 164 additions & 29 deletions internal/dialector/dialector.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,65 @@
// Package dialector — minimal placeholder for Task-Dev-002.
// Package dialector implements the Dialector for GaussDB / openGauss data
// sources used by sqle-gaussdb-plugin.
//
// The real Dialector for GaussDB / openGauss must:
// - return DriverName "opengauss" (registered by gitee.com/opengauss/openGauss-connector-go-pq)
// - build libpq key=value DSN with branch-specific defaults (port 8000 / 5432, sslmode, etc.)
// - expose connect-timeout fallback and additional-param hooks
// Two responsibilities:
//
// All of the above belongs to Task-Dev-003 (plan §3.1). This file only ensures
// driverPkg.NewDriverBuilder(&Dialector{...}) compiles — every method is the
// smallest zero-value form that satisfies the driverPkg.Dialector interface.
// 1. Satisfy the driverPkg.Dialector interface (String / DatabaseAdditionalParam /
// Open / ShowDatabaseSQL) so that sqle's pkg/driver.DriverBuilder can use this
// dialect end-to-end (see sqle/sqle/pkg/driver/dialector.go).
// 2. Expose three plugin-internal helpers — DriverName / DefaultPort / BuildDSN —
// that encapsulate the GaussDB vs openGauss branch logic (default port,
// default sslmode, libpq key=value DSN layout). These helpers are pulled out
// of Open so that unit tests can assert the DSN purely as a string, without
// touching a real database (design §4.3 + §17.1.1).
//
// design §4.3 伪代码 uses `*driverV2.Config` as BuildDSN's parameter but accesses
// fields such as `cfg.Host` that live on `*driverV2.DSN` (the inner struct of
// Config). Real sqle code paths always pass `*driverV2.DSN` to Dialector.Open,
// so we keep BuildDSN's signature as `*driverV2.DSN`. This is semantically
// equivalent to the design pseudocode and avoids a redundant `cfg.DSN.Host`
// access in callers.
package dialector

import (
"context"
"database/sql"
"errors"
"fmt"
"strings"

driverV2 "github.com/actiontech/sqle/sqle/driver/v2"
driverPkg "github.com/actiontech/sqle/sqle/pkg/driver"
"github.com/actiontech/sqle/sqle/pkg/params"
"github.com/pkg/errors"

"github.com/actiontech/sqle-gaussdb-plugin/pkg/consts"

// Register the database/sql driver name "opengauss" via the openGauss
// official Go driver (gitee.com/opengauss/openGauss-connector-go-pq).
// design §3.4 / plan §3.1 mandate this registration.
_ "gitee.com/opengauss/openGauss-connector-go-pq"
)

// Dialector identifies which variant (GaussDB or openGauss) the current plugin
// process represents. Variant must be one of consts.PluginNameGaussDB /
// consts.PluginNameOpenGauss (set by the cmd/* main.go entrypoint).
// driverNameOpenGauss is the database/sql driver name registered by the blank
// import above. Both GaussDB (port 8000) and openGauss (port 5432) connect
// through the same libpq-compatible driver — only the DSN differs.
const driverNameOpenGauss = "opengauss"

// defaultDatabaseName is the libpq fallback database when DSN.DatabaseName is
// empty. design §4.3 伪代码 fixes this to "postgres" for both variants.
const defaultDatabaseName = "postgres"

// connectTimeoutSeconds is the libpq connect_timeout DSN parameter value used
// as a sane fallback. exploration_dev.md §1.4 R-Q1-1 mandated this so that
// hung TCP handshakes fail fast within plugin Ping budget.
const connectTimeoutSeconds = 5

// Dialector identifies which variant (GaussDB or openGauss) the current
// plugin process represents. Variant must be one of consts.PluginNameGaussDB
// or consts.PluginNameOpenGauss (set by the cmd/* main.go entrypoint).
//
// Embedding driverPkg.BaseDialector inherits the default ShowDatabaseSQL /
// DatabaseAdditionalParam / GetConn implementations from sqle; we override
// the ones that need GaussDB / openGauss specific behaviour below.
type Dialector struct {
driverPkg.BaseDialector
Variant string
Expand All @@ -32,32 +69,130 @@ type Dialector struct {
var _ driverPkg.Dialector = (*Dialector)(nil)

// String returns the dialect name. driverPkg.NewDriverBuilder copies this into
// DriverMetas.PluginName at construction time; cmd/* main.go overrides
// builder.Meta.PluginName afterwards to the exact "GaussDB" / "openGauss"
// literal (design §4.4 strict-match contract).
// DriverMetas.PluginName at construction time; cmd/* main.go then overrides
// builder.Meta.PluginName to the strict "GaussDB" / "openGauss" literal
// required by design §4.4 (PluginName strict-match contract).
func (d *Dialector) String() string {
return d.Variant
}

// DatabaseAdditionalParam returns plugin-specific extra connection parameters.
// Task-Dev-002 returns an empty slice; real SSL / authtype params land in
// Task-Dev-003 per design §4.3.
// DatabaseAdditionalParam returns plugin-level extra connection parameters
// surfaced to the DMS data-source UI. Currently empty: SSL / authtype are
// supplied via DSN.AdditionalParams at runtime (design §4.3 表格 + §4.3 DSN
// 拼装规则). Future per-variant additions land here.
func (d *Dialector) DatabaseAdditionalParam() params.Params {
return params.Params{}
}

// Open is the minimum placeholder that satisfies the driverPkg.Dialector
// interface. It deliberately returns an error so that any code path that
// reaches it during the skeleton stage fails fast with a clear message. The
// real implementation (DSN construction + libpq driver name="opengauss") lands
// in Task-Dev-003.
// ShowDatabaseSQL returns the SQL used by sqle to enumerate databases on the
// data source. openGauss / GaussDB share the same pg_database catalog as
// upstream PostgreSQL, so the statement matches sqle-pg-plugin's variant.
func (d *Dialector) ShowDatabaseSQL() string {
return "SELECT datname FROM pg_database WHERE datname NOT IN ('template0', 'template1');"
}

// DriverName returns the database/sql driver name registered by the openGauss
// official Go driver (see the blank import at the top of this file). Both
// variants share the same driver — the variant only changes DSN content.
func (d *Dialector) DriverName() string {
return driverNameOpenGauss
}

// DefaultPort returns the default TCP port for the current variant. design
// §4.3 表格:
//
// GaussDB → 8000 (commercial GaussDB default)
// openGauss → 5432 (openGauss 6.0 default)
//
// Any unrecognised Variant returns 0. The empty Variant case is exercised by
// TestDialector_DefaultPort to lock the fallback contract.
func (d *Dialector) DefaultPort() int {
switch d.Variant {
case consts.PluginNameGaussDB:
return consts.DefaultPortGaussDB
case consts.PluginNameOpenGauss:
return consts.DefaultPortOpenGauss
default:
return 0
}
}

// BuildDSN constructs a libpq key=value DSN string for the openGauss driver.
// design §4.3 关键片段 specifies the layout:
//
// - 5 baseline fields: host / port / user / password / dbname
// - connect_timeout fallback (5 s)
// - GaussDB defaults sslmode=disable; openGauss does not declare sslmode
// (leaves the driver default "prefer" untouched)
// - User-supplied AdditionalParams override defaults — they are appended last,
// and libpq's parser keeps the last occurrence of a duplicate key
//
// Values are escaped per libpq single-quoted form: backslashes are doubled and
// single quotes are escaped as `\'` so that passwords like `it's` survive
// transit to the driver. This matches the escape strategy used by
// github.com/lib/pq's url.go (the openGauss Go driver inherits the same logic).
func (d *Dialector) BuildDSN(dsn *driverV2.DSN) string {
if dsn == nil {
return ""
}

dbName := dsn.DatabaseName
if dbName == "" {
dbName = defaultDatabaseName
}

parts := []string{
fmt.Sprintf("host=%s", escape(dsn.Host)),
fmt.Sprintf("port=%s", escape(dsn.Port)),
fmt.Sprintf("user=%s", escape(dsn.User)),
fmt.Sprintf("password=%s", escape(dsn.Password)),
fmt.Sprintf("dbname=%s", escape(dbName)),
fmt.Sprintf("connect_timeout=%d", connectTimeoutSeconds),
}

// SSL: GaussDB commercial 默认 sslmode=disable 即可握手 (exploration §1.2
// 实测 OK)。openGauss 默认走驱动行为 (sslmode=prefer),无需显式声明。
if d.Variant == consts.PluginNameGaussDB {
parts = append(parts, "sslmode=disable")
}

// AdditionalParams 由 DBA 在 DMS 数据源 UI 显式填写。它们追加在末尾,
// 使得 libpq 解析时同 key 会以**最后一个**为准,从而覆盖前面的默认值
// (e.g. 用户填 sslmode=verify-full 会覆盖 GaussDB 的 sslmode=disable)。
for _, p := range dsn.AdditionalParams {
if p == nil || p.Key == "" {
continue
}
parts = append(parts, fmt.Sprintf("%s=%s", p.Key, escape(p.Value)))
}

return strings.Join(parts, " ")
}

// Open implements the driverPkg.Dialector interface. It composes BuildDSN with
// BaseDialector.GetConn so that the same DSN logic flows through both unit
// tests (which call BuildDSN directly) and runtime connections.
func (d *Dialector) Open(dsn *driverV2.DSN) (*sql.DB, *sql.Conn, error) {
_ = context.TODO
return nil, nil, errors.New("sqle-gaussdb-plugin: Dialector.Open is not implemented yet (Task-Dev-003)")
_ = context.TODO // retained for parity with design §5.1 reference
if dsn == nil {
return nil, nil, errors.New("sqle-gaussdb-plugin: Dialector.Open requires non-nil DSN")
}
dataSourceName := d.BuildDSN(dsn)
db, conn, err := d.BaseDialector.GetConn(d.DriverName(), dataSourceName)
if err != nil {
return nil, nil, errors.Wrap(err, "sqle-gaussdb-plugin: open connection")
}
return db, conn, nil
}

// ShowDatabaseSQL returns the SQL used by sqle to enumerate databases on the
// data source. Placeholder for Task-Dev-002.
func (d *Dialector) ShowDatabaseSQL() string {
return "SELECT datname FROM pg_database WHERE datname NOT IN ('template0', 'template1')"
// escape returns v wrapped in single quotes with embedded backslashes and
// single quotes escaped per libpq's connection-string syntax. The wrapping
// is unconditional so that empty values still surface as `''`, which libpq
// accepts as "use default / unset".
func escape(v string) string {
// Order matters: replace backslashes first so the escapes we introduce
// for single quotes are not double-escaped.
v = strings.ReplaceAll(v, `\`, `\\`)
v = strings.ReplaceAll(v, `'`, `\'`)
return "'" + v + "'"
}
Loading