fix(oracle): preserve quoted object names in DDL

Closes #5186
This commit is contained in:
t8y2 2026-08-03 15:18:34 +08:00
parent a49e795680
commit 147e92ff39
No known key found for this signature in database
2 changed files with 190 additions and 13 deletions

View File

@ -1956,12 +1956,26 @@ ORDER BY CASE
OWNER`, baseSQL, preferredParam, nameColumn, exactParam, typeColumn, nameColumn)
}
func oracleObjectNameCandidates(name string) (string, string, bool) {
exact := strings.TrimSpace(name)
uppercase := strings.ToUpper(exact)
return exact, uppercase, exact != uppercase
}
func (s *server) getColumns(schema, table string) ([]columnInfo, error) {
schema, err := s.normalizeSchema(schema)
if err != nil {
return nil, err
}
table = strings.ToUpper(strings.TrimSpace(table))
exact, uppercase, hasUppercaseFallback := oracleObjectNameCandidates(table)
result, err := s.getColumnsByName(schema, exact)
if err != nil || len(result) > 0 || !hasUppercaseFallback {
return result, err
}
return s.getColumnsByName(schema, uppercase)
}
func (s *server) getColumnsByName(schema, table string) ([]columnInfo, error) {
rows, err := s.queryRows(`
SELECT c.COLUMN_NAME,
c.DATA_TYPE,
@ -2017,7 +2031,15 @@ func (s *server) loadOracleColumnMeta(schema, table string) ([]oracleColumnMeta,
if err != nil {
return nil, err
}
table = strings.ToUpper(strings.TrimSpace(table))
exact, uppercase, hasUppercaseFallback := oracleObjectNameCandidates(table)
result, err := s.loadOracleColumnMetaByName(schema, exact)
if err != nil || len(result) > 0 || !hasUppercaseFallback {
return result, err
}
return s.loadOracleColumnMetaByName(schema, uppercase)
}
func (s *server) loadOracleColumnMetaByName(schema, table string) ([]oracleColumnMeta, error) {
rows, err := s.queryRows(`
SELECT COLUMN_NAME, DATA_TYPE
FROM ALL_TAB_COLUMNS
@ -2255,7 +2277,7 @@ func (s *server) getTableDDL(schema, table, objectType string) (string, error) {
if err != nil {
return "", err
}
objectType, err = s.resolveDDLObjectType(schema, table, objectType)
objectType, table, err = s.resolveDDLObject(schema, table, objectType)
if err != nil {
return "", err
}
@ -2263,7 +2285,7 @@ func (s *server) getTableDDL(schema, table, objectType string) (string, error) {
return s.buildViewDDL(schema, table)
}
var ddl string
err = db.QueryRow("SELECT DBMS_METADATA.GET_DDL(:1, :2, :3) FROM DUAL", objectType, strings.ToUpper(table), schema).Scan(&ddl)
err = db.QueryRow("SELECT DBMS_METADATA.GET_DDL(:1, :2, :3) FROM DUAL", objectType, table, schema).Scan(&ddl)
if err == nil && strings.TrimSpace(ddl) != "" {
return ddl, nil
}
@ -2273,16 +2295,18 @@ func (s *server) getTableDDL(schema, table, objectType string) (string, error) {
return "", err
}
func (s *server) resolveDDLObjectType(schema, name, requested string) (string, error) {
func (s *server) resolveDDLObject(schema, name, requested string) (string, string, error) {
exact, uppercase, hasUppercaseFallback := oracleObjectNameCandidates(name)
objectType := normalizeDDLObjectType(requested)
if objectType != "" {
return objectType, nil
return objectType, exact, nil
}
db, err := s.requireDB()
if err != nil {
return "", err
return "", "", err
}
err = db.QueryRow(`
resolve := func(objectName string) error {
return db.QueryRow(`
SELECT OBJECT_TYPE
FROM (
SELECT OBJECT_TYPE
@ -2292,14 +2316,22 @@ FROM (
AND OBJECT_TYPE IN ('TABLE', 'VIEW', 'MATERIALIZED VIEW')
ORDER BY CASE OBJECT_TYPE WHEN 'TABLE' THEN 0 WHEN 'VIEW' THEN 1 ELSE 2 END
)
WHERE ROWNUM = 1`, schema, strings.ToUpper(name)).Scan(&objectType)
WHERE ROWNUM = 1`, schema, objectName).Scan(&objectType)
}
err = resolve(exact)
if errors.Is(err, sql.ErrNoRows) && hasUppercaseFallback {
err = resolve(uppercase)
if err == nil {
exact = uppercase
}
}
if errors.Is(err, sql.ErrNoRows) {
return "", fmt.Errorf("object not found: %s.%s", schema, name)
return "", "", fmt.Errorf("object not found: %s.%s", schema, name)
}
if err != nil {
return "", err
return "", "", err
}
return normalizeDDLObjectType(objectType), nil
return normalizeDDLObjectType(objectType), exact, nil
}
func normalizeDDLObjectType(value string) string {

View File

@ -290,6 +290,146 @@ func TestNormalizeDDLObjectType(t *testing.T) {
}
}
func TestGetTableDDLPreservesQuotedObjectName(t *testing.T) {
const schema = "ZTZS_ERP2"
const table = "ZGJ_FlowSealTemplate"
const ddl = `CREATE TABLE "ZTZS_ERP2"."ZGJ_FlowSealTemplate" ("FlowId" NUMBER)`
db, scripted := openOracleViewSourceTestDB(t, []oracleViewSourceQueryStep{
{
queryContains: "FROM ALL_OBJECTS",
args: []driver.Value{schema, table},
rows: [][]driver.Value{{"TABLE"}},
},
{
queryContains: "DBMS_METADATA.GET_DDL",
args: []driver.Value{"TABLE", table, schema},
rows: [][]driver.Value{{ddl}},
},
})
s := newServer()
s.db = db
got, err := s.getTableDDL(schema, table, "")
if err != nil {
t.Fatal(err)
}
if got != ddl {
t.Fatalf("getTableDDL() = %q, want %q", got, ddl)
}
if scripted.next != len(scripted.steps) {
t.Fatalf("expected %d queries, got %d", len(scripted.steps), scripted.next)
}
}
func TestGetTableDDLUppercaseObjectDoesNotAddFallbackQuery(t *testing.T) {
const schema = "ZTZS_ERP2"
const table = "ORDERS"
const ddl = `CREATE TABLE "ZTZS_ERP2"."ORDERS" ("ID" NUMBER)`
db, scripted := openOracleViewSourceTestDB(t, []oracleViewSourceQueryStep{
{
queryContains: "FROM ALL_OBJECTS",
args: []driver.Value{schema, table},
rows: [][]driver.Value{{"TABLE"}},
},
{
queryContains: "DBMS_METADATA.GET_DDL",
args: []driver.Value{"TABLE", table, schema},
rows: [][]driver.Value{{ddl}},
},
})
s := newServer()
s.db = db
got, err := s.getTableDDL(schema, table, "")
if err != nil {
t.Fatal(err)
}
if got != ddl {
t.Fatalf("getTableDDL() = %q, want %q", got, ddl)
}
if scripted.next != 2 {
t.Fatalf("uppercase object should use two queries, got %d", scripted.next)
}
}
func TestGetTableDDLFallsBackToUppercaseAfterExactMiss(t *testing.T) {
const schema = "ZTZS_ERP2"
const ddl = `CREATE TABLE "ZTZS_ERP2"."ORDERS" ("ID" NUMBER)`
db, scripted := openOracleViewSourceTestDB(t, []oracleViewSourceQueryStep{
{
queryContains: "FROM ALL_OBJECTS",
args: []driver.Value{schema, "orders"},
rows: nil,
},
{
queryContains: "FROM ALL_OBJECTS",
args: []driver.Value{schema, "ORDERS"},
rows: [][]driver.Value{{"TABLE"}},
},
{
queryContains: "DBMS_METADATA.GET_DDL",
args: []driver.Value{"TABLE", "ORDERS", schema},
rows: [][]driver.Value{{ddl}},
},
})
s := newServer()
s.db = db
got, err := s.getTableDDL(schema, "orders", "")
if err != nil {
t.Fatal(err)
}
if got != ddl {
t.Fatalf("getTableDDL() = %q, want %q", got, ddl)
}
if scripted.next != len(scripted.steps) {
t.Fatalf("expected %d queries, got %d", len(scripted.steps), scripted.next)
}
}
func TestGetTableDDLFallbackPreservesQuotedColumnNames(t *testing.T) {
const schema = "ZTZS_ERP2"
const table = "ZGJ_FlowSealTemplate"
db, scripted := openOracleViewSourceTestDB(t, []oracleViewSourceQueryStep{
{
queryContains: "FROM ALL_OBJECTS",
args: []driver.Value{schema, table},
rows: [][]driver.Value{{"TABLE"}},
},
{
queryContains: "DBMS_METADATA.GET_DDL",
args: []driver.Value{"TABLE", table, schema},
err: errors.New("ORA-31603: object not found"),
},
{
queryContains: "FROM ALL_TAB_COLUMNS",
args: []driver.Value{schema, table},
columns: []string{
"COLUMN_NAME", "DATA_TYPE", "NULLABLE", "DATA_DEFAULT", "IS_PRIMARY_KEY",
"COMMENTS", "DATA_PRECISION", "DATA_SCALE", "CHAR_LENGTH",
},
rows: [][]driver.Value{{"FlowId", "VARCHAR2", "N", nil, int64(1), nil, nil, nil, int64(50)}},
},
})
s := newServer()
s.db = db
got, err := s.getTableDDL(schema, table, "")
if err != nil {
t.Fatal(err)
}
want := `CREATE TABLE "ZTZS_ERP2"."ZGJ_FlowSealTemplate" (
"FlowId" VARCHAR2(50) NOT NULL,
PRIMARY KEY ("FlowId")
)`
if got != want {
t.Fatalf("getTableDDL() = %q, want %q", got, want)
}
if scripted.next != len(scripted.steps) {
t.Fatalf("expected %d queries, got %d", len(scripted.steps), scripted.next)
}
}
func TestIsQuerySQLSkipsLeadingComments(t *testing.T) {
tests := []string{
"-- 测试\nSELECT * FROM (SELECT * FROM \"DBX_TEST\".\"ORDERS_10K\") WHERE ROWNUM <= 100",
@ -1482,6 +1622,7 @@ func contains(values []string, target string) bool {
type oracleViewSourceQueryStep struct {
queryContains string
args []driver.Value
columns []string
rows [][]driver.Value
err error
}
@ -1534,7 +1675,11 @@ func (c *oracleViewSourceConn) QueryContext(
if step.err != nil {
return nil, step.err
}
return &oracleViewSourceRows{columns: []string{"SOURCE"}, values: step.rows}, nil
columns := step.columns
if len(columns) == 0 {
columns = []string{"SOURCE"}
}
return &oracleViewSourceRows{columns: columns, values: step.rows}, nil
}
type oracleViewSourceRows struct {