mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-08 11:31:57 +08:00
🐛 fix(oracle): 修复 Oracle/Dameng 打开表时缺少 schema 前缀导致 ORA-00942
- 问题根因:GetTables 在 dbName 为空时走 user_tables 分支返回纯表名,下游 SQL 缺少 owner 前缀,引用未授权 schema 的表时报 ORA-00942 - SQL 修复:user_tables 分支改用 USER 伪列拼接 owner,确保始终返回 OWNER.TABLE_NAME 格式 - 驱动兼容:列别名用双引号包裹强制大写(AS "OWNER" / AS "TABLE_NAME"),避免不同驱动返回不一致 case 导致 row map 取值失败 - 边界保护:增加 TABLE_NAME 为 NULL 的跳过逻辑,避免污染表名输出 - 达梦对齐:DamengDB.GetTables 同步修复,保持与 Oracle 实现一致 - 测试覆盖:新增 3 个回归用例(all_tables 路径、user_tables 路径、NULL 值跳过),扩展 recording driver 支持 mock 任意查询结果 Refs #445
This commit is contained in:
@@ -211,9 +211,13 @@ func (d *DamengDB) GetDatabases() ([]string, error) {
|
||||
}
|
||||
|
||||
func (d *DamengDB) GetTables(dbName string) ([]string, error) {
|
||||
query := fmt.Sprintf("SELECT owner, table_name FROM all_tables WHERE owner = '%s' ORDER BY table_name", strings.ToUpper(dbName))
|
||||
if dbName == "" {
|
||||
query = "SELECT table_name FROM user_tables"
|
||||
// 始终返回 OWNER.TABLE_NAME,与 Oracle 实现对齐,避免下游 SQL 缺少 schema 前缀(refs issue #445)
|
||||
// 列别名用双引号包裹强制大写,避免不同驱动版本返回不一致 case 导致 row map 取值失败
|
||||
var query string
|
||||
if dbName != "" {
|
||||
query = fmt.Sprintf(`SELECT owner AS "OWNER", table_name AS "TABLE_NAME" FROM all_tables WHERE owner = '%s' ORDER BY table_name`, strings.ToUpper(dbName))
|
||||
} else {
|
||||
query = `SELECT USER AS "OWNER", table_name AS "TABLE_NAME" FROM user_tables ORDER BY table_name`
|
||||
}
|
||||
|
||||
data, _, err := d.Query(query)
|
||||
@@ -223,16 +227,14 @@ func (d *DamengDB) GetTables(dbName string) ([]string, error) {
|
||||
|
||||
var tables []string
|
||||
for _, row := range data {
|
||||
if dbName != "" {
|
||||
if owner, okOwner := row["OWNER"]; okOwner {
|
||||
if name, okName := row["TABLE_NAME"]; okName {
|
||||
tables = append(tables, fmt.Sprintf("%v.%v", owner, name))
|
||||
continue
|
||||
}
|
||||
}
|
||||
owner, okOwner := row["OWNER"]
|
||||
name, okName := row["TABLE_NAME"]
|
||||
if okOwner && okName && name != nil {
|
||||
tables = append(tables, fmt.Sprintf("%v.%v", owner, name))
|
||||
continue
|
||||
}
|
||||
if val, ok := row["TABLE_NAME"]; ok {
|
||||
tables = append(tables, fmt.Sprintf("%v", val))
|
||||
if okName && name != nil {
|
||||
tables = append(tables, fmt.Sprintf("%v", name))
|
||||
}
|
||||
}
|
||||
return tables, nil
|
||||
|
||||
@@ -28,6 +28,12 @@ type oracleRecordingState struct {
|
||||
execQueries []string
|
||||
execArgs [][]driver.NamedValue
|
||||
rowsAffected int64
|
||||
queryResults map[string]oracleRecordingQueryResult
|
||||
}
|
||||
|
||||
type oracleRecordingQueryResult struct {
|
||||
columns []string
|
||||
rows [][]driver.Value
|
||||
}
|
||||
|
||||
func (s *oracleRecordingState) snapshotExecQueries() []string {
|
||||
@@ -80,6 +86,16 @@ func (c *oracleRecordingConn) ExecContext(_ context.Context, query string, args
|
||||
}
|
||||
|
||||
func (c *oracleRecordingConn) QueryContext(_ context.Context, query string, _ []driver.NamedValue) (driver.Rows, error) {
|
||||
c.state.mu.Lock()
|
||||
if result, ok := c.state.queryResults[query]; ok {
|
||||
c.state.mu.Unlock()
|
||||
return &oracleRecordingRows{
|
||||
columns: append([]string(nil), result.columns...),
|
||||
rows: cloneOracleRecordingRows(result.rows),
|
||||
}, nil
|
||||
}
|
||||
c.state.mu.Unlock()
|
||||
|
||||
if strings.Contains(strings.ToLower(query), "tab_columns") {
|
||||
return &oracleRecordingRows{
|
||||
columns: []string{"COLUMN_NAME", "DATA_TYPE", "NULLABLE", "DATA_DEFAULT"},
|
||||
@@ -92,6 +108,14 @@ func (c *oracleRecordingConn) QueryContext(_ context.Context, query string, _ []
|
||||
return &oracleRecordingRows{}, nil
|
||||
}
|
||||
|
||||
func cloneOracleRecordingRows(src [][]driver.Value) [][]driver.Value {
|
||||
dst := make([][]driver.Value, len(src))
|
||||
for i, row := range src {
|
||||
dst[i] = append([]driver.Value(nil), row...)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
var _ driver.ExecerContext = (*oracleRecordingConn)(nil)
|
||||
var _ driver.QueryerContext = (*oracleRecordingConn)(nil)
|
||||
|
||||
@@ -135,7 +159,7 @@ func openOracleRecordingDB(t *testing.T) (*sql.DB, *oracleRecordingState) {
|
||||
oracleRecordingDriverMu.Lock()
|
||||
oracleRecordingDriverSeq++
|
||||
dsn := fmt.Sprintf("oracle-recording-%d", oracleRecordingDriverSeq)
|
||||
state := &oracleRecordingState{rowsAffected: 1}
|
||||
state := &oracleRecordingState{rowsAffected: 1, queryResults: map[string]oracleRecordingQueryResult{}}
|
||||
oracleRecordingDriverStates[dsn] = state
|
||||
oracleRecordingDriverMu.Unlock()
|
||||
|
||||
|
||||
84
internal/db/oracle_get_tables_test.go
Normal file
84
internal/db/oracle_get_tables_test.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOracleGetTablesPrefixesOwnerForAllTablesQuery(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dbConn, state := openOracleRecordingDB(t)
|
||||
state.mu.Lock()
|
||||
state.queryResults[`SELECT owner AS "OWNER", table_name AS "TABLE_NAME" FROM all_tables WHERE owner = 'MYCIMLED' ORDER BY table_name`] = oracleRecordingQueryResult{
|
||||
columns: []string{"OWNER", "TABLE_NAME"},
|
||||
rows: [][]driver.Value{
|
||||
{"MYCIMLED", "T_ADS"},
|
||||
{"MYCIMLED", "T_USERS"},
|
||||
},
|
||||
}
|
||||
state.mu.Unlock()
|
||||
|
||||
oracleDB := &OracleDB{conn: dbConn}
|
||||
tables, err := oracleDB.GetTables("MYCIMLED")
|
||||
if err != nil {
|
||||
t.Fatalf("GetTables 返回错误: %v", err)
|
||||
}
|
||||
|
||||
want := []string{"MYCIMLED.T_ADS", "MYCIMLED.T_USERS"}
|
||||
if !reflect.DeepEqual(tables, want) {
|
||||
t.Fatalf("期望返回带 OWNER 前缀的表名 %v,实际 %v", want, tables)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOracleGetTablesPrefixesCurrentUserForUserTablesQuery(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dbConn, state := openOracleRecordingDB(t)
|
||||
state.mu.Lock()
|
||||
state.queryResults[`SELECT USER AS "OWNER", table_name AS "TABLE_NAME" FROM user_tables ORDER BY table_name`] = oracleRecordingQueryResult{
|
||||
columns: []string{"OWNER", "TABLE_NAME"},
|
||||
rows: [][]driver.Value{
|
||||
{"LOGIN_USER", "T_ADS"},
|
||||
},
|
||||
}
|
||||
state.mu.Unlock()
|
||||
|
||||
oracleDB := &OracleDB{conn: dbConn}
|
||||
tables, err := oracleDB.GetTables("")
|
||||
if err != nil {
|
||||
t.Fatalf("GetTables 返回错误: %v", err)
|
||||
}
|
||||
|
||||
want := []string{"LOGIN_USER.T_ADS"}
|
||||
if !reflect.DeepEqual(tables, want) {
|
||||
t.Fatalf("空 dbName 也应带 OWNER 前缀,期望 %v,实际 %v", want, tables)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOracleGetTablesSkipsRowsWithNullTableName(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dbConn, state := openOracleRecordingDB(t)
|
||||
state.mu.Lock()
|
||||
state.queryResults[`SELECT owner AS "OWNER", table_name AS "TABLE_NAME" FROM all_tables WHERE owner = 'MYCIMLED' ORDER BY table_name`] = oracleRecordingQueryResult{
|
||||
columns: []string{"OWNER", "TABLE_NAME"},
|
||||
rows: [][]driver.Value{
|
||||
{"MYCIMLED", nil},
|
||||
{"MYCIMLED", "T_ADS"},
|
||||
},
|
||||
}
|
||||
state.mu.Unlock()
|
||||
|
||||
oracleDB := &OracleDB{conn: dbConn}
|
||||
tables, err := oracleDB.GetTables("MYCIMLED")
|
||||
if err != nil {
|
||||
t.Fatalf("GetTables 返回错误: %v", err)
|
||||
}
|
||||
|
||||
want := []string{"MYCIMLED.T_ADS"}
|
||||
if !reflect.DeepEqual(tables, want) {
|
||||
t.Fatalf("NULL TABLE_NAME 应被跳过,期望 %v,实际 %v", want, tables)
|
||||
}
|
||||
}
|
||||
@@ -217,9 +217,13 @@ func (o *OracleDB) GetDatabases() ([]string, error) {
|
||||
|
||||
func (o *OracleDB) GetTables(dbName string) ([]string, error) {
|
||||
// dbName is Schema/Owner
|
||||
query := "SELECT table_name FROM user_tables"
|
||||
// 始终返回 OWNER.TABLE_NAME,避免下游 SQL 缺少 schema 前缀导致 ORA-00942(refs issue #445)
|
||||
// 列别名用双引号包裹强制大写,避免不同驱动版本返回不一致 case 导致 row map 取值失败
|
||||
var query string
|
||||
if dbName != "" {
|
||||
query = fmt.Sprintf("SELECT owner, table_name FROM all_tables WHERE owner = '%s' ORDER BY table_name", strings.ToUpper(dbName))
|
||||
query = fmt.Sprintf(`SELECT owner AS "OWNER", table_name AS "TABLE_NAME" FROM all_tables WHERE owner = '%s' ORDER BY table_name`, strings.ToUpper(dbName))
|
||||
} else {
|
||||
query = `SELECT USER AS "OWNER", table_name AS "TABLE_NAME" FROM user_tables ORDER BY table_name`
|
||||
}
|
||||
|
||||
data, _, err := o.Query(query)
|
||||
@@ -229,16 +233,14 @@ func (o *OracleDB) GetTables(dbName string) ([]string, error) {
|
||||
|
||||
var tables []string
|
||||
for _, row := range data {
|
||||
if dbName != "" {
|
||||
if owner, okOwner := row["OWNER"]; okOwner {
|
||||
if name, okName := row["TABLE_NAME"]; okName {
|
||||
tables = append(tables, fmt.Sprintf("%v.%v", owner, name))
|
||||
continue
|
||||
}
|
||||
}
|
||||
owner, okOwner := row["OWNER"]
|
||||
name, okName := row["TABLE_NAME"]
|
||||
if okOwner && okName && name != nil {
|
||||
tables = append(tables, fmt.Sprintf("%v.%v", owner, name))
|
||||
continue
|
||||
}
|
||||
if val, ok := row["TABLE_NAME"]; ok {
|
||||
tables = append(tables, fmt.Sprintf("%v", val))
|
||||
if okName && name != nil {
|
||||
tables = append(tables, fmt.Sprintf("%v", name))
|
||||
}
|
||||
}
|
||||
return tables, nil
|
||||
|
||||
Reference in New Issue
Block a user