229 lines
5.7 KiB
Go
229 lines
5.7 KiB
Go
package sqlite
|
||
|
||
import (
|
||
"context"
|
||
"database/sql"
|
||
"encoding/json"
|
||
"fmt"
|
||
"time"
|
||
|
||
"git.kingecg.top/kingecg/gomog/internal/database"
|
||
"git.kingecg.top/kingecg/gomog/pkg/types"
|
||
_ "github.com/mattn/go-sqlite3" // Import SQLite driver
|
||
)
|
||
|
||
// SQLiteAdapter SQLite适配器
|
||
type SQLiteAdapter struct {
|
||
*database.BaseAdapter
|
||
}
|
||
|
||
// NewSQLiteAdapter 创建SQLite适配器
|
||
func NewSQLiteAdapter() *SQLiteAdapter {
|
||
return &SQLiteAdapter{
|
||
BaseAdapter: database.NewBaseAdapter("sqlite3"),
|
||
}
|
||
}
|
||
|
||
// CollectionExists 检查集合是否存在
|
||
func (a *SQLiteAdapter) CollectionExists(ctx context.Context, name string) (bool, error) {
|
||
query := "SELECT name FROM sqlite_master WHERE type='table' AND name=?"
|
||
var exists bool
|
||
err := a.GetDB().QueryRowContext(ctx, query, name).Scan(&exists)
|
||
if err != nil {
|
||
if err == sql.ErrNoRows {
|
||
return false, nil
|
||
}
|
||
return false, err
|
||
}
|
||
return true, nil
|
||
}
|
||
|
||
// ListCollections 获取所有集合(表)列表
|
||
func (a *SQLiteAdapter) ListCollections(ctx context.Context) ([]string, error) {
|
||
query := `SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`
|
||
rows, err := a.GetDB().QueryContext(ctx, query)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
|
||
var tables []string
|
||
for rows.Next() {
|
||
var table string
|
||
if err := rows.Scan(&table); err != nil {
|
||
return nil, err
|
||
}
|
||
tables = append(tables, table)
|
||
}
|
||
|
||
return tables, rows.Err()
|
||
}
|
||
|
||
// FindPage 分页查询文档
|
||
func (a *SQLiteAdapter) FindPage(ctx context.Context, collection string, skip, limit int) (database.PageResult, error) {
|
||
query := fmt.Sprintf("SELECT id, data, created_at, updated_at FROM %s LIMIT ? OFFSET ?", collection)
|
||
rows, err := a.GetDB().QueryContext(ctx, query, limit, skip)
|
||
if err != nil {
|
||
return database.PageResult{}, err
|
||
}
|
||
defer rows.Close()
|
||
|
||
var docs []types.Document
|
||
for rows.Next() {
|
||
var doc types.Document
|
||
var jsonData []byte
|
||
var createdAtStr, updatedAtStr string
|
||
err := rows.Scan(&doc.ID, &jsonData, &createdAtStr, &updatedAtStr)
|
||
if err != nil {
|
||
return database.PageResult{}, err
|
||
}
|
||
|
||
if err := json.Unmarshal(jsonData, &doc.Data); err != nil {
|
||
return database.PageResult{}, err
|
||
}
|
||
|
||
// 解析时间字符串
|
||
if parsedTime, err := parseSQLiteTime(createdAtStr); err == nil {
|
||
doc.CreatedAt = parsedTime
|
||
}
|
||
if parsedTime, err := parseSQLiteTime(updatedAtStr); err == nil {
|
||
doc.UpdatedAt = parsedTime
|
||
}
|
||
|
||
docs = append(docs, doc)
|
||
}
|
||
|
||
// 检查是否还有更多数据
|
||
checkQuery := fmt.Sprintf("SELECT 1 FROM %s LIMIT 1 OFFSET ?", collection)
|
||
checkRows, err := a.GetDB().QueryContext(ctx, checkQuery, skip+limit)
|
||
if err != nil {
|
||
return database.PageResult{}, err
|
||
}
|
||
defer checkRows.Close()
|
||
|
||
hasMore := checkRows.Next()
|
||
|
||
return database.PageResult{
|
||
Documents: docs,
|
||
HasMore: hasMore,
|
||
}, rows.Err()
|
||
}
|
||
|
||
// parseSQLiteTime 解析SQLite时间字符串
|
||
func parseSQLiteTime(timeStr string) (time.Time, error) {
|
||
layouts := []string{
|
||
"2006-01-02 15:04:05",
|
||
"2006-01-02T15:04:05Z",
|
||
"2006-01-02T15:04:05.000Z",
|
||
time.RFC3339,
|
||
time.RFC3339Nano,
|
||
"2006-01-02 15:04:05.000000",
|
||
"2006-01-02 15:04:05.000",
|
||
}
|
||
|
||
for _, layout := range layouts {
|
||
if t, err := time.Parse(layout, timeStr); err == nil {
|
||
return t, nil
|
||
}
|
||
}
|
||
|
||
// 如果标准布局失败,则尝试解析Unix时间戳
|
||
if unixTime, err := time.Parse("2006-01-02 15:04:05.000000", timeStr); err == nil {
|
||
return unixTime, nil
|
||
}
|
||
|
||
// 默认返回当前时间
|
||
return time.Now(), fmt.Errorf("无法解析时间字符串: %s", timeStr)
|
||
}
|
||
|
||
// Connect 连接 SQLite 数据库
|
||
func (a *SQLiteAdapter) Connect(ctx context.Context, dsn string) error {
|
||
// SQLite 需要启用 JSON1 扩展(大多数构建已默认包含)
|
||
if err := a.BaseAdapter.Connect(ctx, dsn); err != nil {
|
||
return err
|
||
}
|
||
|
||
// 设置 SQLite 特定的 PRAGMA
|
||
_, err := a.GetDB().Exec("PRAGMA journal_mode = WAL")
|
||
return err
|
||
}
|
||
|
||
// CreateCollection 创建集合(SQLite 表)
|
||
func (a *SQLiteAdapter) CreateCollection(ctx context.Context, name string) error {
|
||
// SQLite 使用 CHECK 约束验证 JSON
|
||
query := fmt.Sprintf(`
|
||
CREATE TABLE IF NOT EXISTS %s (
|
||
id TEXT PRIMARY KEY,
|
||
data TEXT NOT NULL CHECK(json_valid(data)),
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
)`, name)
|
||
|
||
_, err := a.GetDB().ExecContext(ctx, query)
|
||
return err
|
||
}
|
||
|
||
// FindAll 查询所有文档(使用 SQLite JSON 函数)
|
||
func (a *SQLiteAdapter) FindAll(ctx context.Context, collection string) ([]types.Document, error) {
|
||
query := fmt.Sprintf("SELECT id, data, created_at, updated_at FROM %s", collection)
|
||
rows, err := a.GetDB().QueryContext(ctx, query)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
|
||
var docs []types.Document
|
||
for rows.Next() {
|
||
var doc types.Document
|
||
var jsonData []byte
|
||
err := rows.Scan(&doc.ID, &jsonData, &doc.CreatedAt, &doc.UpdatedAt)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if err := json.Unmarshal(jsonData, &doc.Data); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
docs = append(docs, doc)
|
||
}
|
||
|
||
return docs, rows.Err()
|
||
}
|
||
|
||
// InsertMany 批量插入(SQLite 优化版本)
|
||
func (a *SQLiteAdapter) InsertMany(ctx context.Context, collection string, docs []types.Document) error {
|
||
tx, err := a.GetDB().BeginTx(ctx, nil)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer tx.Rollback()
|
||
|
||
for _, doc := range docs {
|
||
jsonData, err := json.Marshal(doc.Data)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
query := fmt.Sprintf(
|
||
"INSERT INTO %s (id, data, created_at, updated_at) VALUES (?, json(?), ?, ?)",
|
||
collection,
|
||
)
|
||
|
||
now := doc.CreatedAt
|
||
if now.IsZero() {
|
||
now = doc.UpdatedAt
|
||
}
|
||
if now.IsZero() {
|
||
now = doc.UpdatedAt
|
||
}
|
||
|
||
_, err = tx.ExecContext(ctx, query, doc.ID, string(jsonData), now, now)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
return tx.Commit()
|
||
}
|