mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-08 11:31:57 +08:00
🐛 fix(sql-editor): 修复存储过程与返回结果写语句的结果识别
- 补齐 SQL 分类逻辑,识别 SQL Server 裸存储过程调用、RETURNING/OUTPUT、SELECT INTO 及消息块场景 - 调整多语句执行与批量写入分支,避免返回行或服务端消息被 Exec 路径吞掉 - 为 PostgreSQL、OpenGauss、Kingbase、HighGo 补充 notice 回传能力并增加回归测试
This commit is contained in:
@@ -5,6 +5,7 @@ package db
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
@@ -17,7 +18,7 @@ import (
|
||||
"GoNavi-Wails/internal/ssh"
|
||||
"GoNavi-Wails/internal/utils"
|
||||
|
||||
_ "github.com/highgo/pq-sm3" // HighGo uses dedicated SM3-capable driver
|
||||
highgopq "github.com/highgo/pq-sm3" // HighGo uses dedicated SM3-capable driver
|
||||
)
|
||||
|
||||
// HighGoDB implements Database interface for HighGo (瀚高) database
|
||||
@@ -28,6 +29,13 @@ type HighGoDB struct {
|
||||
forwarder *ssh.LocalForwarder
|
||||
}
|
||||
|
||||
type highgoSessionExecer struct {
|
||||
*sqlConnStatementExecer
|
||||
}
|
||||
|
||||
var _ QueryMessageExecer = (*HighGoDB)(nil)
|
||||
var _ StatementQueryMessageExecer = (*highgoSessionExecer)(nil)
|
||||
|
||||
func (h *HighGoDB) getDSN(config connection.ConnectionConfig) string {
|
||||
// postgres://user:password@host:port/dbname?sslmode=disable
|
||||
dbname := config.Database
|
||||
@@ -152,6 +160,20 @@ func (h *HighGoDB) QueryContext(ctx context.Context, query string) ([]map[string
|
||||
return scanRows(rows)
|
||||
}
|
||||
|
||||
func (h *HighGoDB) QueryContextWithMessages(ctx context.Context, query string) ([]map[string]interface{}, []string, []string, error) {
|
||||
if h.conn == nil {
|
||||
return nil, nil, nil, fmt.Errorf("连接未打开")
|
||||
}
|
||||
|
||||
conn, err := h.conn.Conn(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
return queryHighGoConnWithMessages(ctx, conn, query)
|
||||
}
|
||||
|
||||
func (h *HighGoDB) Query(query string) ([]map[string]interface{}, []string, error) {
|
||||
if h.conn == nil {
|
||||
return nil, nil, fmt.Errorf("连接未打开")
|
||||
@@ -165,6 +187,10 @@ func (h *HighGoDB) Query(query string) ([]map[string]interface{}, []string, erro
|
||||
return scanRows(rows)
|
||||
}
|
||||
|
||||
func (h *HighGoDB) QueryWithMessages(query string) ([]map[string]interface{}, []string, []string, error) {
|
||||
return h.QueryContextWithMessages(context.Background(), query)
|
||||
}
|
||||
|
||||
func (h *HighGoDB) ExecContext(ctx context.Context, query string) (int64, error) {
|
||||
if h.conn == nil {
|
||||
return 0, fmt.Errorf("连接未打开")
|
||||
@@ -195,7 +221,7 @@ func (h *HighGoDB) OpenSessionExecer(ctx context.Context) (StatementExecer, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewSQLConnStatementExecer(conn), nil
|
||||
return &highgoSessionExecer{sqlConnStatementExecer: &sqlConnStatementExecer{conn: conn}}, nil
|
||||
}
|
||||
|
||||
func (h *HighGoDB) Exec(query string) (int64, error) {
|
||||
@@ -209,6 +235,31 @@ func (h *HighGoDB) Exec(query string) (int64, error) {
|
||||
return res.RowsAffected()
|
||||
}
|
||||
|
||||
func (e *highgoSessionExecer) QueryWithMessages(query string) ([]map[string]interface{}, []string, []string, error) {
|
||||
return e.QueryContextWithMessages(context.Background(), query)
|
||||
}
|
||||
|
||||
func (e *highgoSessionExecer) QueryContextWithMessages(ctx context.Context, query string) ([]map[string]interface{}, []string, []string, error) {
|
||||
if e == nil || e.conn == nil {
|
||||
return nil, nil, nil, fmt.Errorf("连接未打开")
|
||||
}
|
||||
return queryHighGoConnWithMessages(ctx, e.conn, query)
|
||||
}
|
||||
|
||||
func queryHighGoConnWithMessages(ctx context.Context, conn *sql.Conn, query string) ([]map[string]interface{}, []string, []string, error) {
|
||||
return querySQLConnWithTextNotices(ctx, conn, query, func(driverConn driver.Conn, addNotice func(string)) {
|
||||
if addNotice == nil {
|
||||
highgopq.SetNoticeHandler(driverConn, nil)
|
||||
return
|
||||
}
|
||||
highgopq.SetNoticeHandler(driverConn, func(notice *highgopq.Error) {
|
||||
if notice != nil {
|
||||
addNotice(notice.Message)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (h *HighGoDB) GetDatabases() ([]string, error) {
|
||||
data, _, err := h.Query("SELECT datname FROM pg_database WHERE datistemplate = false")
|
||||
if err != nil {
|
||||
|
||||
@@ -5,6 +5,7 @@ package db
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
@@ -18,7 +19,7 @@ import (
|
||||
"GoNavi-Wails/internal/ssh"
|
||||
"GoNavi-Wails/internal/utils"
|
||||
|
||||
_ "gitea.com/kingbase/gokb" // Registers "kingbase" driver
|
||||
gokb "gitea.com/kingbase/gokb" // Registers "kingbase" driver
|
||||
)
|
||||
|
||||
type KingbaseDB struct {
|
||||
@@ -27,6 +28,13 @@ type KingbaseDB struct {
|
||||
forwarder *ssh.LocalForwarder // Store SSH tunnel forwarder
|
||||
}
|
||||
|
||||
type kingbaseSessionExecer struct {
|
||||
*sqlConnStatementExecer
|
||||
}
|
||||
|
||||
var _ QueryMessageExecer = (*KingbaseDB)(nil)
|
||||
var _ StatementQueryMessageExecer = (*kingbaseSessionExecer)(nil)
|
||||
|
||||
func quoteConnValue(v string) string {
|
||||
if v == "" {
|
||||
return "''"
|
||||
@@ -280,6 +288,20 @@ func (k *KingbaseDB) QueryContext(ctx context.Context, query string) ([]map[stri
|
||||
return scanRows(rows)
|
||||
}
|
||||
|
||||
func (k *KingbaseDB) QueryContextWithMessages(ctx context.Context, query string) ([]map[string]interface{}, []string, []string, error) {
|
||||
if k.conn == nil {
|
||||
return nil, nil, nil, fmt.Errorf("连接未打开")
|
||||
}
|
||||
|
||||
conn, err := k.conn.Conn(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
return queryKingbaseConnWithMessages(ctx, conn, query)
|
||||
}
|
||||
|
||||
func (k *KingbaseDB) Query(query string) ([]map[string]interface{}, []string, error) {
|
||||
if k.conn == nil {
|
||||
return nil, nil, fmt.Errorf("连接未打开")
|
||||
@@ -293,6 +315,10 @@ func (k *KingbaseDB) Query(query string) ([]map[string]interface{}, []string, er
|
||||
return scanRows(rows)
|
||||
}
|
||||
|
||||
func (k *KingbaseDB) QueryWithMessages(query string) ([]map[string]interface{}, []string, []string, error) {
|
||||
return k.QueryContextWithMessages(context.Background(), query)
|
||||
}
|
||||
|
||||
func (k *KingbaseDB) ExecContext(ctx context.Context, query string) (int64, error) {
|
||||
if k.conn == nil {
|
||||
return 0, fmt.Errorf("连接未打开")
|
||||
@@ -323,7 +349,7 @@ func (k *KingbaseDB) OpenSessionExecer(ctx context.Context) (StatementExecer, er
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewSQLConnStatementExecer(conn), nil
|
||||
return &kingbaseSessionExecer{sqlConnStatementExecer: &sqlConnStatementExecer{conn: conn}}, nil
|
||||
}
|
||||
|
||||
func (k *KingbaseDB) Exec(query string) (int64, error) {
|
||||
@@ -337,6 +363,31 @@ func (k *KingbaseDB) Exec(query string) (int64, error) {
|
||||
return res.RowsAffected()
|
||||
}
|
||||
|
||||
func (e *kingbaseSessionExecer) QueryWithMessages(query string) ([]map[string]interface{}, []string, []string, error) {
|
||||
return e.QueryContextWithMessages(context.Background(), query)
|
||||
}
|
||||
|
||||
func (e *kingbaseSessionExecer) QueryContextWithMessages(ctx context.Context, query string) ([]map[string]interface{}, []string, []string, error) {
|
||||
if e == nil || e.conn == nil {
|
||||
return nil, nil, nil, fmt.Errorf("连接未打开")
|
||||
}
|
||||
return queryKingbaseConnWithMessages(ctx, e.conn, query)
|
||||
}
|
||||
|
||||
func queryKingbaseConnWithMessages(ctx context.Context, conn *sql.Conn, query string) ([]map[string]interface{}, []string, []string, error) {
|
||||
return querySQLConnWithTextNotices(ctx, conn, query, func(driverConn driver.Conn, addNotice func(string)) {
|
||||
if addNotice == nil {
|
||||
gokb.SetNoticeHandler(driverConn, nil)
|
||||
return
|
||||
}
|
||||
gokb.SetNoticeHandler(driverConn, func(notice *gokb.Error) {
|
||||
if notice != nil {
|
||||
addNotice(notice.Message)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (k *KingbaseDB) GetDatabases() ([]string, error) {
|
||||
data, _, err := k.Query("SELECT datname FROM pg_database WHERE datistemplate = false")
|
||||
if err == nil {
|
||||
|
||||
60
internal/db/notice_query.go
Normal file
60
internal/db/notice_query.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type sqlTextNoticeHandlerSetter func(driver.Conn, func(string))
|
||||
|
||||
func querySQLConnWithTextNotices(ctx context.Context, conn *sql.Conn, query string, setHandler sqlTextNoticeHandlerSetter) ([]map[string]interface{}, []string, []string, error) {
|
||||
if conn == nil {
|
||||
return nil, nil, nil, fmt.Errorf("连接未打开")
|
||||
}
|
||||
if setHandler == nil {
|
||||
return nil, nil, nil, fmt.Errorf("未配置消息捕获处理器")
|
||||
}
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
notices := make([]string, 0, 2)
|
||||
addNotice := func(text string) {
|
||||
text = strings.TrimSpace(text)
|
||||
if text != "" {
|
||||
notices = append(notices, text)
|
||||
}
|
||||
}
|
||||
|
||||
if err := conn.Raw(func(rawConn interface{}) error {
|
||||
driverConn, ok := rawConn.(driver.Conn)
|
||||
if !ok {
|
||||
return fmt.Errorf("底层连接类型不支持消息捕获")
|
||||
}
|
||||
setHandler(driverConn, addNotice)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = conn.Raw(func(rawConn interface{}) error {
|
||||
driverConn, ok := rawConn.(driver.Conn)
|
||||
if ok {
|
||||
setHandler(driverConn, nil)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}()
|
||||
|
||||
rows, err := conn.QueryContext(ctx, query)
|
||||
if err != nil {
|
||||
return nil, nil, append([]string(nil), notices...), err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
data, columns, err := scanRows(rows)
|
||||
return data, columns, append([]string(nil), notices...), err
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package db
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
@@ -15,7 +16,7 @@ import (
|
||||
"GoNavi-Wails/internal/ssh"
|
||||
"GoNavi-Wails/internal/utils"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
type PostgresDB struct {
|
||||
@@ -24,6 +25,13 @@ type PostgresDB struct {
|
||||
forwarder *ssh.LocalForwarder // Store SSH tunnel forwarder
|
||||
}
|
||||
|
||||
type postgresSessionExecer struct {
|
||||
*sqlConnStatementExecer
|
||||
}
|
||||
|
||||
var _ QueryMessageExecer = (*PostgresDB)(nil)
|
||||
var _ StatementQueryMessageExecer = (*postgresSessionExecer)(nil)
|
||||
|
||||
func resolvePostgresConnectDatabases(config connection.ConnectionConfig) []string {
|
||||
explicit := strings.TrimSpace(config.Database)
|
||||
if explicit != "" {
|
||||
@@ -225,6 +233,20 @@ func (p *PostgresDB) QueryContext(ctx context.Context, query string) ([]map[stri
|
||||
return scanRows(rows)
|
||||
}
|
||||
|
||||
func (p *PostgresDB) QueryContextWithMessages(ctx context.Context, query string) ([]map[string]interface{}, []string, []string, error) {
|
||||
if p.conn == nil {
|
||||
return nil, nil, nil, fmt.Errorf("连接未打开")
|
||||
}
|
||||
|
||||
conn, err := p.conn.Conn(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
return queryPostgresConnWithMessages(ctx, conn, query)
|
||||
}
|
||||
|
||||
func (p *PostgresDB) Query(query string) ([]map[string]interface{}, []string, error) {
|
||||
if p.conn == nil {
|
||||
return nil, nil, fmt.Errorf("连接未打开")
|
||||
@@ -238,6 +260,10 @@ func (p *PostgresDB) Query(query string) ([]map[string]interface{}, []string, er
|
||||
return scanRows(rows)
|
||||
}
|
||||
|
||||
func (p *PostgresDB) QueryWithMessages(query string) ([]map[string]interface{}, []string, []string, error) {
|
||||
return p.QueryContextWithMessages(context.Background(), query)
|
||||
}
|
||||
|
||||
func (p *PostgresDB) ExecBatchContext(ctx context.Context, query string) (int64, error) {
|
||||
if p.conn == nil {
|
||||
return 0, fmt.Errorf("连接未打开")
|
||||
@@ -257,7 +283,7 @@ func (p *PostgresDB) OpenSessionExecer(ctx context.Context) (StatementExecer, er
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewSQLConnStatementExecer(conn), nil
|
||||
return &postgresSessionExecer{sqlConnStatementExecer: &sqlConnStatementExecer{conn: conn}}, nil
|
||||
}
|
||||
|
||||
func (p *PostgresDB) ExecContext(ctx context.Context, query string) (int64, error) {
|
||||
@@ -282,6 +308,31 @@ func (p *PostgresDB) Exec(query string) (int64, error) {
|
||||
return res.RowsAffected()
|
||||
}
|
||||
|
||||
func (e *postgresSessionExecer) QueryWithMessages(query string) ([]map[string]interface{}, []string, []string, error) {
|
||||
return e.QueryContextWithMessages(context.Background(), query)
|
||||
}
|
||||
|
||||
func (e *postgresSessionExecer) QueryContextWithMessages(ctx context.Context, query string) ([]map[string]interface{}, []string, []string, error) {
|
||||
if e == nil || e.conn == nil {
|
||||
return nil, nil, nil, fmt.Errorf("连接未打开")
|
||||
}
|
||||
return queryPostgresConnWithMessages(ctx, e.conn, query)
|
||||
}
|
||||
|
||||
func queryPostgresConnWithMessages(ctx context.Context, conn *sql.Conn, query string) ([]map[string]interface{}, []string, []string, error) {
|
||||
return querySQLConnWithTextNotices(ctx, conn, query, func(driverConn driver.Conn, addNotice func(string)) {
|
||||
if addNotice == nil {
|
||||
pq.SetNoticeHandler(driverConn, nil)
|
||||
return
|
||||
}
|
||||
pq.SetNoticeHandler(driverConn, func(notice *pq.Error) {
|
||||
if notice != nil {
|
||||
addNotice(notice.Message)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (p *PostgresDB) GetDatabases() ([]string, error) {
|
||||
data, _, err := p.Query("SELECT datname FROM pg_database WHERE datistemplate = false")
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user