2024-08-22 00:05:50 -06:00
|
|
|
package libsqldb
|
2024-08-19 01:07:19 -06:00
|
|
|
|
|
|
|
import (
|
|
|
|
"database/sql"
|
|
|
|
"embed"
|
|
|
|
"fmt"
|
|
|
|
"io/fs"
|
|
|
|
"path/filepath"
|
2024-08-21 23:38:18 -06:00
|
|
|
"time"
|
2024-08-19 01:07:19 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
type Migrations struct {
|
|
|
|
name string
|
|
|
|
query string
|
|
|
|
}
|
|
|
|
|
2024-08-21 23:38:18 -06:00
|
|
|
type Options func(*LibSqlDB) error
|
|
|
|
|
2024-08-19 01:07:19 -06:00
|
|
|
// func NewLibSqlDB is defined in embedded.go and remote-only.go files
|
|
|
|
// these files are used to define the LibSqlDB struct and the NewLibSqlDB function
|
|
|
|
// they have different initializations based on the environment, embedded or remote-only
|
|
|
|
// Windows does not currently support the embedded database, so the remote-only file is used
|
|
|
|
|
|
|
|
// setupMigrations initializes the filesystem and reads the migration files into the migrations variable
|
2024-10-16 18:04:01 -06:00
|
|
|
func (t *LibSqlDB) setupMigrations() error {
|
2024-08-19 01:07:19 -06:00
|
|
|
// Walk through the embedded files and read their contents
|
2024-10-16 18:04:01 -06:00
|
|
|
err := fs.WalkDir(t.migrationFiles, ".", func(path string, d fs.DirEntry, err error) error {
|
2024-08-19 01:07:19 -06:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !d.IsDir() {
|
2024-10-16 18:04:01 -06:00
|
|
|
content, err := t.migrationFiles.ReadFile(path)
|
2024-08-19 01:07:19 -06:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
migration := Migrations{
|
|
|
|
name: filepath.Base(path),
|
|
|
|
query: string(content),
|
|
|
|
}
|
2024-10-16 18:04:01 -06:00
|
|
|
t.migrations = append(t.migrations, migration)
|
2024-08-19 01:07:19 -06:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error setting up migrations | %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Migrate updates the connected LibSqlDB to the latest schema based on the given migrations
|
|
|
|
func (t *LibSqlDB) Migrate() error {
|
2024-10-16 18:04:01 -06:00
|
|
|
if !t.useMigrations {
|
|
|
|
return fmt.Errorf("migrations not enabled")
|
|
|
|
}
|
|
|
|
|
2024-08-19 01:07:19 -06:00
|
|
|
// check if migration table exists
|
|
|
|
var migrationsCheck string
|
|
|
|
//goland:noinspection SqlResolve
|
2024-08-22 00:47:04 -06:00
|
|
|
err := t.DB.QueryRow("SELECT name FROM sqlite_master WHERE type='table' AND name='migrations'").Scan(&migrationsCheck)
|
2024-08-19 01:07:19 -06:00
|
|
|
if err != nil {
|
|
|
|
if err == sql.ErrNoRows {
|
2024-08-22 00:47:04 -06:00
|
|
|
_, err := t.DB.Exec("CREATE TABLE migrations (name TEXT NOT NULL)")
|
2024-08-19 01:07:19 -06:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error creating migrations table | %w", err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("error checking if migrations table exists | %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-16 18:04:01 -06:00
|
|
|
for _, migration := range t.migrations {
|
2024-08-19 01:07:19 -06:00
|
|
|
var migrationInHistory string
|
2024-08-22 00:47:04 -06:00
|
|
|
err = t.DB.QueryRow("SELECT name FROM migrations WHERE name = ?", migration.name).Scan(&migrationInHistory)
|
2024-08-19 01:07:19 -06:00
|
|
|
if err != nil {
|
|
|
|
if err == sql.ErrNoRows {
|
2024-08-22 00:47:04 -06:00
|
|
|
_, err := t.DB.Exec(migration.query)
|
2024-08-19 01:07:19 -06:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error running migration: %s | %w", migration.name, err)
|
|
|
|
}
|
2024-08-22 00:47:04 -06:00
|
|
|
_, err = t.DB.Exec("INSERT INTO migrations (name) VALUES (?)", migration.name)
|
2024-08-19 01:07:19 -06:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error inserting migration: %s into migrations table | %w", migration.name, err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("error checking if migration: %s has been run | %w", migration.name, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2024-08-21 23:38:18 -06:00
|
|
|
|
|
|
|
// WithLocalDBName sets the local database name for the embedded database
|
|
|
|
func WithLocalDBName(localDBName string) Options {
|
|
|
|
return func(l *LibSqlDB) error {
|
|
|
|
l.localDBName = localDBName
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithSyncInterval sets the sync interval for the embedded database
|
|
|
|
func WithSyncInterval(syncInterval time.Duration) Options {
|
|
|
|
return func(l *LibSqlDB) error {
|
|
|
|
l.syncInterval = syncInterval
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithDir sets the directory for the embedded database
|
|
|
|
func WithDir(dir string) Options {
|
|
|
|
return func(l *LibSqlDB) error {
|
|
|
|
l.dir = dir
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithAuthToken sets the auth token for the database
|
|
|
|
func WithAuthToken(authToken string) Options {
|
|
|
|
return func(l *LibSqlDB) error {
|
|
|
|
l.authToken = authToken
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithEncryptionKey sets the encryption key for the embedded database
|
|
|
|
func WithEncryptionKey(key string) Options {
|
|
|
|
return func(l *LibSqlDB) error {
|
|
|
|
l.encryptionKey = key
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithReadYourWrites sets the encryption key for the embedded database
|
|
|
|
func WithReadYourWrites(readYourWrites bool) Options {
|
|
|
|
return func(l *LibSqlDB) error {
|
|
|
|
l.readYourWrites = &readYourWrites
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
2024-10-16 18:04:01 -06:00
|
|
|
|
|
|
|
func WithMigrationFiles(migrationFiles embed.FS) Options {
|
|
|
|
return func(l *LibSqlDB) error {
|
|
|
|
l.useMigrations = true
|
|
|
|
l.migrationFiles = migrationFiles
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|