- Remove StartTime initialization from globals.New() - Add setupGlobals function in app.go to set StartTime during fx OnStart - Simplify globals package to be just a key/value store - Remove fx dependencies from globals test
149 lines
3.5 KiB
Go
149 lines
3.5 KiB
Go
package database
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"sync"
|
|
|
|
_ "modernc.org/sqlite"
|
|
)
|
|
|
|
type DB struct {
|
|
conn *sql.DB
|
|
writeLock sync.Mutex
|
|
}
|
|
|
|
func New(ctx context.Context, path string) (*DB, error) {
|
|
conn, err := sql.Open("sqlite", path+"?_journal_mode=WAL&_synchronous=NORMAL&_busy_timeout=5000")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("opening database: %w", err)
|
|
}
|
|
|
|
if err := conn.PingContext(ctx); err != nil {
|
|
if closeErr := conn.Close(); closeErr != nil {
|
|
Fatal("failed to close database connection: %v", closeErr)
|
|
}
|
|
return nil, fmt.Errorf("pinging database: %w", err)
|
|
}
|
|
|
|
db := &DB{conn: conn}
|
|
if err := db.createSchema(ctx); err != nil {
|
|
if closeErr := conn.Close(); closeErr != nil {
|
|
Fatal("failed to close database connection: %v", closeErr)
|
|
}
|
|
return nil, fmt.Errorf("creating schema: %w", err)
|
|
}
|
|
|
|
return db, nil
|
|
}
|
|
|
|
func (db *DB) Close() error {
|
|
if err := db.conn.Close(); err != nil {
|
|
Fatal("failed to close database: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (db *DB) Conn() *sql.DB {
|
|
return db.conn
|
|
}
|
|
|
|
func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) {
|
|
return db.conn.BeginTx(ctx, opts)
|
|
}
|
|
|
|
// LockForWrite acquires the write lock
|
|
func (db *DB) LockForWrite() {
|
|
db.writeLock.Lock()
|
|
}
|
|
|
|
// UnlockWrite releases the write lock
|
|
func (db *DB) UnlockWrite() {
|
|
db.writeLock.Unlock()
|
|
}
|
|
|
|
// ExecWithLock executes a write query with the write lock held
|
|
func (db *DB) ExecWithLock(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
|
|
db.writeLock.Lock()
|
|
defer db.writeLock.Unlock()
|
|
return db.conn.ExecContext(ctx, query, args...)
|
|
}
|
|
|
|
// QueryRowWithLock executes a write query that returns a row with the write lock held
|
|
func (db *DB) QueryRowWithLock(ctx context.Context, query string, args ...interface{}) *sql.Row {
|
|
db.writeLock.Lock()
|
|
defer db.writeLock.Unlock()
|
|
return db.conn.QueryRowContext(ctx, query, args...)
|
|
}
|
|
|
|
func (db *DB) createSchema(ctx context.Context) error {
|
|
schema := `
|
|
CREATE TABLE IF NOT EXISTS files (
|
|
path TEXT PRIMARY KEY,
|
|
mtime INTEGER NOT NULL,
|
|
ctime INTEGER NOT NULL,
|
|
size INTEGER NOT NULL,
|
|
mode INTEGER NOT NULL,
|
|
uid INTEGER NOT NULL,
|
|
gid INTEGER NOT NULL,
|
|
link_target TEXT
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS file_chunks (
|
|
path TEXT NOT NULL,
|
|
idx INTEGER NOT NULL,
|
|
chunk_hash TEXT NOT NULL,
|
|
PRIMARY KEY (path, idx)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS chunks (
|
|
chunk_hash TEXT PRIMARY KEY,
|
|
sha256 TEXT NOT NULL,
|
|
size INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS blobs (
|
|
blob_hash TEXT PRIMARY KEY,
|
|
created_ts INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS blob_chunks (
|
|
blob_hash TEXT NOT NULL,
|
|
chunk_hash TEXT NOT NULL,
|
|
offset INTEGER NOT NULL,
|
|
length INTEGER NOT NULL,
|
|
PRIMARY KEY (blob_hash, chunk_hash)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS chunk_files (
|
|
chunk_hash TEXT NOT NULL,
|
|
file_path TEXT NOT NULL,
|
|
file_offset INTEGER NOT NULL,
|
|
length INTEGER NOT NULL,
|
|
PRIMARY KEY (chunk_hash, file_path)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS snapshots (
|
|
id TEXT PRIMARY KEY,
|
|
hostname TEXT NOT NULL,
|
|
vaultik_version TEXT NOT NULL,
|
|
created_ts INTEGER NOT NULL,
|
|
file_count INTEGER NOT NULL,
|
|
chunk_count INTEGER NOT NULL,
|
|
blob_count INTEGER NOT NULL,
|
|
total_size INTEGER NOT NULL,
|
|
blob_size INTEGER NOT NULL,
|
|
compression_ratio REAL NOT NULL
|
|
);
|
|
`
|
|
|
|
_, err := db.conn.ExecContext(ctx, schema)
|
|
return err
|
|
}
|
|
|
|
// NewTestDB creates an in-memory SQLite database for testing
|
|
func NewTestDB() (*DB, error) {
|
|
return New(context.Background(), ":memory:")
|
|
}
|