feat: add schema fingerprint and Z10d alter; refresh sync coop docs
Ship ensure ADD COLUMN and fingerprint API for Yuheng reconcile, and rewrite §0.2–0.3 cooperation checklist in the联调意见. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -37,8 +37,8 @@ func EnsureTableFromRow(ctx context.Context, db *sql.DB, driver Driver, table, p
|
||||
return EnsureTableFromColumns(ctx, db, driver, table, pkCol, cols)
|
||||
}
|
||||
|
||||
// EnsureTableFromColumns 表不存在时按列名建空表(全部 TEXT,指定 PK)。已存在则幂等跳过。
|
||||
// 用于空表结构同步:本机有空表 → 线上也建同名空表(无需 outbox 行)。
|
||||
// EnsureTableFromColumns 按列名建空表(全部 TEXT,指定 PK)。
|
||||
// 已存在时补齐缺失列(ADD COLUMN),解决「先按 _row_id 建表、后 push 注入 id」导致的缺列 503。
|
||||
func EnsureTableFromColumns(ctx context.Context, db *sql.DB, driver Driver, table, pkCol string, columns []string) error {
|
||||
table = strings.TrimSpace(table)
|
||||
pkCol = strings.TrimSpace(pkCol)
|
||||
@@ -55,27 +55,57 @@ func EnsureTableFromColumns(ctx context.Context, db *sql.DB, driver Driver, tabl
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
return nil
|
||||
}
|
||||
want := make([]string, 0, len(columns)+1)
|
||||
seen := map[string]struct{}{}
|
||||
defs := make([]string, 0, len(columns)+1)
|
||||
defs = append(defs, fmt.Sprintf("%s TEXT PRIMARY KEY", quoteIdent(driver, pkCol)))
|
||||
seen[pkCol] = struct{}{}
|
||||
for _, c := range columns {
|
||||
add := func(c string) {
|
||||
c = strings.TrimSpace(c)
|
||||
if c == "" {
|
||||
continue
|
||||
return
|
||||
}
|
||||
if _, ok := seen[c]; ok {
|
||||
continue
|
||||
return
|
||||
}
|
||||
seen[c] = struct{}{}
|
||||
defs = append(defs, fmt.Sprintf("%s TEXT", quoteIdent(driver, c)))
|
||||
want = append(want, c)
|
||||
}
|
||||
if len(defs) == 0 {
|
||||
add(pkCol)
|
||||
for _, c := range columns {
|
||||
add(c)
|
||||
}
|
||||
if len(want) == 0 {
|
||||
return fmt.Errorf("columns required")
|
||||
}
|
||||
if exists {
|
||||
have, err := listColumns(ctx, db, driver, table)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
haveSet := map[string]struct{}{}
|
||||
for _, c := range have {
|
||||
haveSet[c] = struct{}{}
|
||||
}
|
||||
for _, c := range want {
|
||||
if _, ok := haveSet[c]; ok {
|
||||
continue
|
||||
}
|
||||
ddl := fmt.Sprintf(`ALTER TABLE %s ADD COLUMN %s TEXT`, quoteIdent(driver, table), quoteIdent(driver, c))
|
||||
if _, err := db.ExecContext(ctx, ddl); err != nil {
|
||||
// SQLite 无 IF NOT EXISTS;并发下可能已存在
|
||||
if !strings.Contains(strings.ToLower(err.Error()), "duplicate column") {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
defs := make([]string, 0, len(want))
|
||||
defs = append(defs, fmt.Sprintf("%s TEXT PRIMARY KEY", quoteIdent(driver, pkCol)))
|
||||
for _, c := range want {
|
||||
if c == pkCol {
|
||||
continue
|
||||
}
|
||||
defs = append(defs, fmt.Sprintf("%s TEXT", quoteIdent(driver, c)))
|
||||
}
|
||||
ddl := fmt.Sprintf(`CREATE TABLE IF NOT EXISTS %s (%s)`, quoteIdent(driver, table), strings.Join(defs, ", "))
|
||||
_, err = db.ExecContext(ctx, ddl)
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user