Files
MyGoNavi/internal/db/change_preview.go
Syngnat 6958299ec2 🐛 fix(oceanbase): 修复 Oracle 模式单元格事务提交
- 单元格变更提交与预览使用所选 schema 限定目标表
- 对限定表名按分段方式引用,避免将 schema 与表名整体转义
- 为全列定位条件中的日期和时间值补充显式 Oracle 转换
- 增加 OceanBase Oracle 事务提交与 SQL 预览回归测试
2026-07-13 17:29:41 +08:00

149 lines
3.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package db
import (
"fmt"
"sort"
"strings"
"time"
"GoNavi-Wails/internal/connection"
)
// GenerateChangePreview 将 ChangeSet 转为可读 SQL 语句(不执行)。
// quoteIdent 用于引用列名/表名MySQL: backtick, PostgreSQL: double quote
func GenerateChangePreview(tableName string, changes connection.ChangeSet, quoteIdent func(string) string) (deletes, updates, inserts []string) {
return GenerateChangePreviewWithTableQuoter(tableName, changes, quoteIdent, quoteIdent)
}
// GenerateChangePreviewWithTableQuoter allows qualified table names to be quoted
// segment by segment while keeping column quoting unchanged.
func GenerateChangePreviewWithTableQuoter(
tableName string,
changes connection.ChangeSet,
quoteIdent func(string) string,
quoteTable func(string) string,
) (deletes, updates, inserts []string) {
if quoteTable == nil {
quoteTable = quoteIdent
}
// Deletes
for _, pk := range changes.Deletes {
var conds []string
for _, k := range sortedKeys(pk) {
v := pk[k]
conds = append(conds, fmt.Sprintf("%s = %s", quoteIdent(k), formatLiteral(v)))
}
if len(conds) > 0 {
deletes = append(deletes, fmt.Sprintf("DELETE FROM %s WHERE %s;", quoteTable(tableName), strings.Join(conds, " AND ")))
}
}
// Updates
for _, row := range changes.Updates {
var sets []string
for _, k := range sortedKeys(row.Values) {
v := row.Values[k]
sets = append(sets, fmt.Sprintf("%s = %s", quoteIdent(k), formatLiteral(v)))
}
if len(sets) == 0 {
continue
}
var conds []string
for _, k := range sortedKeys(row.Keys) {
v := row.Keys[k]
conds = append(conds, fmt.Sprintf("%s = %s", quoteIdent(k), formatLiteral(v)))
}
if len(conds) == 0 {
continue
}
updates = append(updates, fmt.Sprintf("UPDATE %s SET %s WHERE %s;", quoteTable(tableName), strings.Join(sets, ", "), strings.Join(conds, " AND ")))
}
// Inserts
for _, row := range changes.Inserts {
var cols []string
var vals []string
for _, k := range sortedKeys(row) {
v := row[k]
if v == nil {
continue
}
cols = append(cols, quoteIdent(k))
vals = append(vals, formatLiteral(v))
}
if len(cols) == 0 {
continue
}
inserts = append(inserts, fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s);", quoteTable(tableName), strings.Join(cols, ", "), strings.Join(vals, ", ")))
}
return deletes, updates, inserts
}
// sortedKeys 返回 map 的键排序切片,保证输出确定性。
func sortedKeys(m map[string]interface{}) []string {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}
// formatLiteral 将 Go 值转为 SQL 字面量字符串。
func formatLiteral(v interface{}) string {
if v == nil {
return "NULL"
}
switch t := v.(type) {
case string:
escaped := strings.ReplaceAll(t, "\\", "\\\\")
escaped = strings.ReplaceAll(escaped, "'", "\\'")
return fmt.Sprintf("'%s'", escaped)
case float64:
return formatNumber(t)
case float32:
return formatNumber(float64(t))
case int:
return fmt.Sprintf("%d", t)
case int64:
return fmt.Sprintf("%d", t)
case int32:
return fmt.Sprintf("%d", t)
case int16:
return fmt.Sprintf("%d", t)
case int8:
return fmt.Sprintf("%d", t)
case uint64:
return fmt.Sprintf("%d", t)
case uint32:
return fmt.Sprintf("%d", t)
case uint16:
return fmt.Sprintf("%d", t)
case uint8:
return fmt.Sprintf("%d", t)
case uint:
return fmt.Sprintf("%d", t)
case time.Time:
return fmt.Sprintf("'%s'", t.Format("2006-01-02 15:04:05"))
case bool:
if t {
return "TRUE"
}
return "FALSE"
case []byte:
return formatLiteral(string(t))
default:
escaped := strings.ReplaceAll(fmt.Sprintf("%v", t), "'", "\\'")
return fmt.Sprintf("'%s'", escaped)
}
}
func formatNumber(f float64) string {
if f == float64(int64(f)) {
return fmt.Sprintf("%d", int64(f))
}
return fmt.Sprintf("%v", f)
}