mirror of
https://github.com/syntrex-lab/gomcp.git
synced 2026-04-25 12:26:22 +02:00
114 lines
2.8 KiB
Go
114 lines
2.8 KiB
Go
// Copyright 2026 Syntrex Lab. All rights reserved.
|
|
// Use of this source code is governed by an Apache-2.0 license
|
|
// that can be found in the LICENSE file.
|
|
|
|
// Package sqlite provides SQLite-based persistence using modernc.org/sqlite (pure Go, no CGO).
|
|
package sqlite
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
|
|
_ "modernc.org/sqlite"
|
|
)
|
|
|
|
// DB wraps a *sql.DB with SQLite-specific configuration.
|
|
type DB struct {
|
|
db *sql.DB
|
|
path string
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// Open opens or creates an SQLite database at the given path.
|
|
// It applies WAL mode and recommended pragmas for performance.
|
|
func Open(path string) (*DB, error) {
|
|
dir := filepath.Dir(path)
|
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
return nil, fmt.Errorf("create db directory: %w", err)
|
|
}
|
|
|
|
db, err := sql.Open("sqlite", path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open sqlite: %w", err)
|
|
}
|
|
|
|
// Apply performance pragmas.
|
|
pragmas := []string{
|
|
"PRAGMA journal_mode=WAL",
|
|
"PRAGMA synchronous=NORMAL",
|
|
"PRAGMA cache_size=-64000", // 64MB
|
|
"PRAGMA foreign_keys=ON",
|
|
"PRAGMA busy_timeout=5000",
|
|
}
|
|
for _, p := range pragmas {
|
|
if _, err := db.Exec(p); err != nil {
|
|
db.Close()
|
|
return nil, fmt.Errorf("exec pragma %q: %w", p, err)
|
|
}
|
|
}
|
|
|
|
// Connection pool settings for SQLite (single writer).
|
|
db.SetMaxOpenConns(1)
|
|
db.SetMaxIdleConns(1)
|
|
|
|
return &DB{db: db, path: path}, nil
|
|
}
|
|
|
|
// OpenMemory opens an in-memory SQLite database (for testing).
|
|
func OpenMemory() (*DB, error) {
|
|
db, err := sql.Open("sqlite", ":memory:")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open in-memory sqlite: %w", err)
|
|
}
|
|
|
|
if _, err := db.Exec("PRAGMA foreign_keys=ON"); err != nil {
|
|
db.Close()
|
|
return nil, fmt.Errorf("enable foreign keys: %w", err)
|
|
}
|
|
|
|
// In-memory SQLite: each connection gets a SEPARATE database.
|
|
// Limit to 1 connection to ensure all queries see the same tables.
|
|
db.SetMaxOpenConns(1)
|
|
db.SetMaxIdleConns(1)
|
|
|
|
return &DB{db: db, path: ":memory:"}, nil
|
|
}
|
|
|
|
// SqlDB returns the underlying *sql.DB.
|
|
func (d *DB) SqlDB() *sql.DB {
|
|
return d.db
|
|
}
|
|
|
|
// Path returns the database file path.
|
|
func (d *DB) Path() string {
|
|
return d.path
|
|
}
|
|
|
|
// Close closes the database connection.
|
|
func (d *DB) Close() error {
|
|
return d.db.Close()
|
|
}
|
|
|
|
// Exec executes a query that doesn't return rows.
|
|
func (d *DB) Exec(query string, args ...any) (sql.Result, error) {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
return d.db.Exec(query, args...)
|
|
}
|
|
|
|
// Query executes a query that returns rows.
|
|
func (d *DB) Query(query string, args ...any) (*sql.Rows, error) {
|
|
d.mu.RLock()
|
|
defer d.mu.RUnlock()
|
|
return d.db.Query(query, args...)
|
|
}
|
|
|
|
// QueryRow executes a query that returns at most one row.
|
|
func (d *DB) QueryRow(query string, args ...any) *sql.Row {
|
|
d.mu.RLock()
|
|
defer d.mu.RUnlock()
|
|
return d.db.QueryRow(query, args...)
|
|
}
|