Compare commits

...

2 Commits

Author SHA1 Message Date
black 7e54a96454 ```
fix(database): 修复SQLite迁移中自增主键的定义问题

- 修复了自增列同时标记为主键时重复添加PRIMARY KEY约束的问题
- 确保自增主键列只添加一次PRIMARY KEY约束
- 优化了复合主键约束的添加逻辑,避免与自增约束冲突
- 修正了自增主键的类型定义为INTEGER以符合SQLite规范
```
2026-04-14 09:54:48 +08:00
black 33faa6f722 feat(database): 添加 SQLite 数据库目录自动创建功能
- 为 SQLite 数据库添加数据库文件目录自动创建逻辑
- 实现 ensureSQLiteDirectory 方法解析数据库路径并创建必要目录
- 支持从 config.Name 或 config.Link 中提取 SQLite 文件路径
- 添加对 SQLite 链接格式 sqlite::@file(path) 的路径解析支持
- 在数据库表迁移前确保 SQLite 数据库文件目录存在
- 新增 sqlitecgo 驱动包实现 SQLite 数据库驱动支持
2026-04-14 09:47:19 +08:00
3 changed files with 76 additions and 24 deletions

View File

@ -52,14 +52,16 @@ func (m *Migration) CreateTable(ctx context.Context, table string, columns map[s
var primaryKeys []string var primaryKeys []string
for name, def := range columns { for name, def := range columns {
colDef := m.buildColumnDefinition(name, def) colDef := m.buildColumnDefinition(name, def)
if def.PrimaryKey { // Only add to primaryKeys if it's not an auto-increment column
// (auto-increment columns already have PRIMARY KEY in their definition)
if def.PrimaryKey && !def.AutoIncrement {
primaryKeys = append(primaryKeys, database.QuoteIdentifier(name)) primaryKeys = append(primaryKeys, database.QuoteIdentifier(name))
} }
colDefs = append(colDefs, " "+colDef) colDefs = append(colDefs, " "+colDef)
} }
// Add primary key constraint if needed // Add composite primary key constraint if needed (for non-auto-increment keys)
if len(primaryKeys) > 0 { if len(primaryKeys) > 1 {
colDefs = append(colDefs, fmt.Sprintf(" PRIMARY KEY (%s)", strings.Join(primaryKeys, ", "))) colDefs = append(colDefs, fmt.Sprintf(" PRIMARY KEY (%s)", strings.Join(primaryKeys, ", ")))
} }
@ -74,30 +76,27 @@ func (m *Migration) buildColumnDefinition(name string, def *database.ColumnDefin
var parts []string var parts []string
parts = append(parts, database.QuoteIdentifier(name)) parts = append(parts, database.QuoteIdentifier(name))
// Handle SQLite-specific types // Handle SQLite-specific types for auto-increment primary key
dbType := def.Type
if def.AutoIncrement && def.PrimaryKey {
if dbType == "INT" || dbType == "INTEGER" {
dbType = "INTEGER"
}
}
parts = append(parts, dbType)
if def.PrimaryKey && def.AutoIncrement { if def.PrimaryKey && def.AutoIncrement {
parts = append(parts, "PRIMARY KEY AUTOINCREMENT") // SQLite requires INTEGER type for AUTOINCREMENT
} else { parts = append(parts, "INTEGER PRIMARY KEY AUTOINCREMENT")
if !def.Null { return strings.Join(parts, " ")
parts = append(parts, "NOT NULL") }
}
if def.Unique && !def.PrimaryKey { // Regular column definition
parts = append(parts, "UNIQUE") parts = append(parts, def.Type)
}
if def.Default != nil { if !def.Null {
defaultValue := formatDefaultValue(def.Default) parts = append(parts, "NOT NULL")
parts = append(parts, fmt.Sprintf("DEFAULT %s", defaultValue)) }
}
if def.Unique && !def.PrimaryKey {
parts = append(parts, "UNIQUE")
}
if def.Default != nil {
defaultValue := formatDefaultValue(def.Default)
parts = append(parts, fmt.Sprintf("DEFAULT %s", defaultValue))
} }
return strings.Join(parts, " ") return strings.Join(parts, " ")

View File

@ -50,3 +50,9 @@ func (d *Driver) New(core *database.Core, node *database.ConfigNode) (database.D
func (d *Driver) GetChars() (charLeft string, charRight string) { func (d *Driver) GetChars() (charLeft string, charRight string) {
return quoteChar, quoteChar return quoteChar, quoteChar
} }
// GetMigration returns a Migration instance implementing the Migration interface.
// Note: sqlitecgo driver does not have migration support yet, returns nil.
func (d *Driver) GetMigration() database.Migration {
return nil
}

View File

@ -9,6 +9,8 @@ package database
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"path/filepath"
"reflect" "reflect"
"strings" "strings"
"time" "time"
@ -46,6 +48,13 @@ func (am *AutoMigrateCore) migrateEntity(ctx context.Context, entity any) error
return fmt.Errorf("no columns found for table %s", tableName) return fmt.Errorf("no columns found for table %s", tableName)
} }
// For SQLite, ensure the database file directory exists
if am.db.GetConfig().Type == "sqlite" {
if err := am.ensureSQLiteDirectory(); err != nil {
return fmt.Errorf("failed to prepare SQLite database: %w", err)
}
}
// Check if table exists // Check if table exists
hasTable, err := am.db.HasTable(ctx, tableName) hasTable, err := am.db.HasTable(ctx, tableName)
if err != nil { if err != nil {
@ -450,3 +459,41 @@ func IsZeroValue(v any) bool {
} }
return false return false
} }
// ensureSQLiteDirectory ensures the SQLite database file directory exists.
func (am *AutoMigrateCore) ensureSQLiteDirectory() error {
config := am.db.GetConfig()
if config.Type != "sqlite" {
return nil
}
// Get the database file path from config.Name or config.Link
dbPath := config.Name
if dbPath == "" && config.Link != "" {
// Parse link format: sqlite::@file(./data/company.db)
if strings.Contains(config.Link, "@file(") {
start := strings.Index(config.Link, "@file(") + 6
end := strings.Index(config.Link[start:], ")")
if end > 0 {
dbPath = config.Link[start : start+end]
}
}
}
if dbPath == "" {
return fmt.Errorf("SQLite database path is empty")
}
// Get directory path
dir := filepath.Dir(dbPath)
// Check if directory exists
if _, err := os.Stat(dir); os.IsNotExist(err) {
// Create directory with all parents
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("failed to create directory %s: %w", dir, err)
}
}
return nil
}