mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-10-29 18:49:20 +08:00
migrations/sqlite 落后于 migrations/versioned,导致 Lite 模式全新数据库 首次建号/建空间失败(tenants.api_principal_config、system_settings、 task_pending_ops 等缺失)。新增 000005-000011 七个 SQLite 迁移, 按 versioned 迁移主题一一补齐: - 000005 <- 000034/000054: messages.attachments、tenant_invitations token/accepted_count - 000006 <- 000041: task_pending_ops / task_dead_letters(与 #2433 同一 DDL) - 000007 <- 000053: system_settings、users.is_system_admin - 000008 <- 000055/000056: knowledge_processing_spans、knowledges.pending_subtasks_count - 000009 <- 000060: embed_channels.allow_memory - 000010 <- 000063: knowledge_tag_relations - 000011 <- 000064: tenants.api_principal_config、mcp_oauth_tokens principal 列 测试:新增 internal/database 迁移测试,覆盖全新库迁移到 v11 与 v4->v11 升级保数据两条路径;go test ./internal/database 全绿。 关联 #2158;与 #2433 范围重叠(任务表部分 DDL 保持一致)。
185 lines
5.7 KiB
Go
185 lines
5.7 KiB
Go
package database
|
|
|
|
import (
|
|
"database/sql"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// versionedSQLiteTables is the set of tables that SQLite migrations must
|
|
// create to stay in sync with the versioned (PostgreSQL) migrations:
|
|
// 000041 task queue, 000053 system settings, 000055 processing spans,
|
|
// 000063 knowledge multi-tags.
|
|
var versionedSQLiteTables = []string{
|
|
"task_pending_ops",
|
|
"task_dead_letters",
|
|
"system_settings",
|
|
"knowledge_processing_spans",
|
|
"knowledge_tag_relations",
|
|
}
|
|
|
|
// versionedSQLiteColumns maps each existing table to the columns that the
|
|
// versioned migrations add and the SQLite baseline was missing.
|
|
var versionedSQLiteColumns = map[string][]string{
|
|
"tenants": {"api_principal_config"}, // 000064
|
|
"users": {"is_system_admin"}, // 000053
|
|
"knowledges": {"pending_subtasks_count"}, // 000056
|
|
"messages": {"attachments"}, // 000034
|
|
"tenant_invitations": {"token", "accepted_count"}, // 000054
|
|
"embed_channels": {"allow_memory"}, // 000060
|
|
"mcp_oauth_tokens": {"principal_type", "principal_id"}, // 000064
|
|
}
|
|
|
|
const expectedSQLiteMigrationVersion = 11
|
|
|
|
func TestSQLiteMigrationsCreateVersionedSchema(t *testing.T) {
|
|
repoRoot := sqliteRepoRoot(t)
|
|
chdirAndRestore(t, repoRoot)
|
|
|
|
dbPath := filepath.Join(t.TempDir(), "fresh.db")
|
|
require.NoError(t, RunMigrationsWithOptions("sqlite3://unused", MigrationOptions{SQLiteDBPath: dbPath}))
|
|
|
|
db := openSQLiteDB(t, dbPath)
|
|
version, dirty := sqliteMigrationState(t, db)
|
|
require.Equal(t, expectedSQLiteMigrationVersion, version)
|
|
require.False(t, dirty)
|
|
|
|
for _, table := range versionedSQLiteTables {
|
|
require.Truef(t, sqliteTableExists(t, db, table), "SQLite migrations must create table %s", table)
|
|
}
|
|
for table, columns := range versionedSQLiteColumns {
|
|
for _, column := range columns {
|
|
require.Truef(
|
|
t,
|
|
sqliteColumnExists(t, db, table, column),
|
|
"SQLite migrations must add column %s.%s",
|
|
table,
|
|
column,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSQLiteMigrationsUpgradeV4PreservesData(t *testing.T) {
|
|
repoRoot := sqliteRepoRoot(t)
|
|
|
|
// Build a legacy v4 migration root (000000_init .. 000004_memory) so we
|
|
// can prove the new migrations upgrade an existing Lite database without
|
|
// replaying the baseline.
|
|
legacyRoot := copySQLiteMigrationsV4(t, repoRoot)
|
|
chdirAndRestore(t, legacyRoot)
|
|
|
|
dbPath := filepath.Join(t.TempDir(), "upgrade.db")
|
|
require.NoError(t, RunMigrationsWithOptions("sqlite3://unused", MigrationOptions{SQLiteDBPath: dbPath}))
|
|
|
|
db := openSQLiteDB(t, dbPath)
|
|
versionBefore, dirtyBefore := sqliteMigrationState(t, db)
|
|
require.Equal(t, 4, versionBefore)
|
|
require.False(t, dirtyBefore)
|
|
_, err := db.Exec("INSERT INTO tenants (name, business) VALUES (?, ?)", "upgrade-sentinel", "migration-test")
|
|
require.NoError(t, err)
|
|
|
|
// Run the full migration set from the repo root.
|
|
chdirAndRestore(t, repoRoot)
|
|
require.NoError(t, RunMigrationsWithOptions("sqlite3://unused", MigrationOptions{SQLiteDBPath: dbPath}))
|
|
|
|
db = openSQLiteDB(t, dbPath)
|
|
versionAfter, dirtyAfter := sqliteMigrationState(t, db)
|
|
require.Equal(t, expectedSQLiteMigrationVersion, versionAfter)
|
|
require.False(t, dirtyAfter)
|
|
|
|
for _, table := range versionedSQLiteTables {
|
|
require.Truef(t, sqliteTableExists(t, db, table), "upgraded SQLite DB must have table %s", table)
|
|
}
|
|
for table, columns := range versionedSQLiteColumns {
|
|
for _, column := range columns {
|
|
require.Truef(
|
|
t,
|
|
sqliteColumnExists(t, db, table, column),
|
|
"upgraded SQLite DB must have column %s.%s",
|
|
table,
|
|
column,
|
|
)
|
|
}
|
|
}
|
|
|
|
var sentinelName string
|
|
require.NoError(t, db.QueryRow("SELECT name FROM tenants WHERE business = ?", "migration-test").Scan(&sentinelName))
|
|
require.Equal(t, "upgrade-sentinel", sentinelName)
|
|
}
|
|
|
|
func sqliteRepoRoot(t *testing.T) string {
|
|
t.Helper()
|
|
repoRoot, err := filepath.Abs(filepath.Join("..", ".."))
|
|
require.NoError(t, err)
|
|
return repoRoot
|
|
}
|
|
|
|
func chdirAndRestore(t *testing.T, dir string) {
|
|
t.Helper()
|
|
previousDir, err := os.Getwd()
|
|
require.NoError(t, err)
|
|
require.NoError(t, os.Chdir(dir))
|
|
t.Cleanup(func() { _ = os.Chdir(previousDir) })
|
|
}
|
|
|
|
func openSQLiteDB(t *testing.T, dbPath string) *sql.DB {
|
|
t.Helper()
|
|
db, err := sql.Open("sqlite3", dbPath)
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() { _ = db.Close() })
|
|
return db
|
|
}
|
|
|
|
func sqliteMigrationState(t *testing.T, db *sql.DB) (version int, dirty bool) {
|
|
t.Helper()
|
|
require.NoError(t, db.QueryRow("SELECT version, dirty FROM schema_migrations").Scan(&version, &dirty))
|
|
return version, dirty
|
|
}
|
|
|
|
func sqliteTableExists(t *testing.T, db *sql.DB, table string) bool {
|
|
t.Helper()
|
|
var n int
|
|
require.NoError(t, db.QueryRow(
|
|
"SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = ?",
|
|
table,
|
|
).Scan(&n))
|
|
return n == 1
|
|
}
|
|
|
|
func sqliteColumnExists(t *testing.T, db *sql.DB, table, column string) bool {
|
|
t.Helper()
|
|
var n int
|
|
require.NoError(t, db.QueryRow(
|
|
"SELECT COUNT(*) FROM pragma_table_info(?) WHERE name = ?",
|
|
table,
|
|
column,
|
|
).Scan(&n))
|
|
return n == 1
|
|
}
|
|
|
|
func copySQLiteMigrationsV4(t *testing.T, repoRoot string) string {
|
|
t.Helper()
|
|
dest := t.TempDir()
|
|
srcDir := filepath.Join(repoRoot, "migrations", "sqlite")
|
|
destDir := filepath.Join(dest, "migrations", "sqlite")
|
|
require.NoError(t, os.MkdirAll(destDir, 0o755))
|
|
|
|
legacy := []string{
|
|
"000000_init.up.sql",
|
|
"000001_remove_wiki_log.up.sql",
|
|
"000002_knowledge_folder_path.up.sql",
|
|
"000003_knowledge_base_auto_tag_config.up.sql",
|
|
"000004_memory.up.sql",
|
|
}
|
|
for _, name := range legacy {
|
|
data, err := os.ReadFile(filepath.Join(srcDir, name))
|
|
require.NoError(t, err)
|
|
require.NoError(t, os.WriteFile(filepath.Join(destDir, name), data, 0o600))
|
|
}
|
|
return dest
|
|
}
|