feat: harden loose-offline sync for user JWT, schema, and console ops
Enable Binding-scoped agent push/pull, empty-table schema ensure, SyncPage inspect/drop-table, default module import, and agent-bound publish docs from the 宇恒联调意见. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
87
platform/internal/dbsync/drop_table.go
Normal file
87
platform/internal/dbsync/drop_table.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package dbsync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DropTableResult 控制台删表结果。
|
||||
type DropTableResult struct {
|
||||
OK bool `json:"ok"`
|
||||
Side string `json:"side"`
|
||||
Table string `json:"table"`
|
||||
Dropped bool `json:"dropped"`
|
||||
MetaCleared int64 `json:"meta_cleared,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// DropTableOnEndpoint 删除业务表(禁止 _ajz_*);并清理 _ajz_sync_meta 中该表残留。
|
||||
// 注意:仅作用于当前 endpoint(线上 A 或本机 B),不向另一侧同步 DDL。
|
||||
func DropTableOnEndpoint(ctx context.Context, ep Endpoint, side, table string) (*DropTableResult, error) {
|
||||
table = strings.TrimSpace(table)
|
||||
side = strings.TrimSpace(side)
|
||||
if side == "" {
|
||||
side = "remote"
|
||||
}
|
||||
if table == "" {
|
||||
return nil, fmt.Errorf("table required")
|
||||
}
|
||||
if strings.HasPrefix(table, "_ajz_") {
|
||||
return nil, fmt.Errorf("同步系统表不可删: %s", table)
|
||||
}
|
||||
if strings.EqualFold(table, "sqlite_master") || strings.HasPrefix(strings.ToLower(table), "sqlite_") {
|
||||
return nil, fmt.Errorf("系统表不可删: %s", table)
|
||||
}
|
||||
|
||||
db, owned, err := openInspectDB(ep)
|
||||
if err != nil {
|
||||
return &DropTableResult{OK: false, Side: side, Table: table, Message: err.Error()}, err
|
||||
}
|
||||
if owned {
|
||||
defer db.Close()
|
||||
}
|
||||
|
||||
exists, err := tableExists(ctx, db, ep.Driver, table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return &DropTableResult{
|
||||
OK: true,
|
||||
Side: side,
|
||||
Table: table,
|
||||
Dropped: false,
|
||||
Message: "表不存在(可能已删)",
|
||||
}, nil
|
||||
}
|
||||
|
||||
ddl := fmt.Sprintf(`DROP TABLE IF EXISTS %s`, quoteIdent(ep.Driver, table))
|
||||
if _, err := db.ExecContext(ctx, ddl); err != nil {
|
||||
return nil, fmt.Errorf("drop table: %w", err)
|
||||
}
|
||||
|
||||
var metaCleared int64
|
||||
if metaExists, _ := tableExists(ctx, db, ep.Driver, MetaTable); metaExists {
|
||||
q := fmt.Sprintf(`DELETE FROM %s WHERE table_name = ?`, quoteIdent(ep.Driver, MetaTable))
|
||||
if ep.Driver == DriverPostgres {
|
||||
q = fmt.Sprintf(`DELETE FROM %s WHERE table_name = $1`, quoteIdent(ep.Driver, MetaTable))
|
||||
}
|
||||
res, err := db.ExecContext(ctx, q, table)
|
||||
if err == nil && res != nil {
|
||||
metaCleared, _ = res.RowsAffected()
|
||||
}
|
||||
}
|
||||
|
||||
// 若该 DSN 在 remote 池里,丢掉缓存连接,避免旧 schema 缓存感。
|
||||
InvalidateRemote(ep.Driver, ep.DSN)
|
||||
|
||||
return &DropTableResult{
|
||||
OK: true,
|
||||
Side: side,
|
||||
Table: table,
|
||||
Dropped: true,
|
||||
MetaCleared: metaCleared,
|
||||
Message: "已删除;若另一侧仍有同名表,不会自动同步删除,需分别处理或避免再次 push/ensure",
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user