gomog/internal/database/sqlite/adapter.go

229 lines
5.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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()
}