internal/repo/database.go (93 lines of code) (raw):
package repo
import (
"context"
"time"
"github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3"
)
const DATABASE_TYPE = "sqlite3"
const pragma = `
PRAGMA journal_mode = WAL; -- Use checkpoints instead of atomic commits
PRAGMA cache_size = -62500; -- 64MB, maximum database disk pages size to be held per database files
PRAGMA busy_timeout = 5000; -- Sleep if SQLITE_BUSY is returned
PRAGMA synchronous = NORMAL; -- Only sync at critical moments, recommended when using WAL
`
const schema = `
CREATE TABLE metadata (
bucket TEXT NOT NULL,
name TEXT NOT NULL,
size INTEGER NOT NULL,
updated TIMESTAMP NOT NULL,
created TIMESTAMP NOT NULL,
parent TEXT,
storage_class TEXT NOT NULL CHECK (storage_class IN ('STANDARD', 'NEARLINE', 'COLDLINE', 'ARCHIVE')),
FOREIGN KEY (parent) REFERENCES parent(name),
PRIMARY KEY (bucket, name)
);
CREATE TABLE directory (
bucket TEXT NOT NULL,
name TEXT NOT NULL,
count INTEGER DEFAULT 0,
size_standard INTEGER DEFAULT 0,
size_nearline INTEGER DEFAULT 0,
size_coldline INTEGER DEFAULT 0,
size_archive INTEGER DEFAULT 0,
parent TEXT,
FOREIGN KEY (parent) REFERENCES directory(name),
PRIMARY KEY (bucket, name)
);
`
type Database struct {
*sqlx.DB
url string
maxOpenConnections int
}
func NewDatabase(url string, maxOpenConnections int) *Database {
db := &Database{
url: url,
maxOpenConnections: maxOpenConnections,
}
return db
}
// Connect to a database at Database.url
// If database file does not exist, a new db file will be created at Database.url
func (db *Database) Connect(ctx context.Context) error {
dbCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
var err error
db.DB, err = sqlx.ConnectContext(dbCtx, DATABASE_TYPE, db.url)
if err != nil {
return err
}
db.SetMaxOpenConns(db.maxOpenConnections)
return nil
}
// Setup executes all PRAGMA configs to setup database behavior
func (db *Database) Setup() error {
if _, err := db.Exec(pragma); err != nil {
return err
}
return nil
}
// CreateTables creates and executes database schema defined
func (db *Database) CreateTables() error {
if _, err := db.Exec(schema); err != nil {
return err
}
return nil
}
// PingTable checks if database schema has been created by pinging metadata table
func (db *Database) PingTable() (bool, error) {
var tableExists bool
if err := db.QueryRow(`SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type="table" AND name="metadata");`).Scan(&tableExists); err != nil {
return false, err
}
return tableExists, nil
}
// CreateIndexes creates relevant indexes to improve query performance
func (db *Database) CreateIndexes() error {
query := `
CREATE INDEX idx_metadata_parent ON metadata(parent);
CREATE INDEX idx_directory_parent ON directory(parent);
CREATE INDEX idx_directory_name ON directory(name);
VACUUM; -- Repackage database to clean empty space
`
if _, err := db.Exec(query); err != nil {
return err
}
return nil
}