initial commit
This commit is contained in:
89
db/db.go
Normal file
89
db/db.go
Normal file
@ -0,0 +1,89 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Migrations struct {
|
||||
name string
|
||||
query string
|
||||
}
|
||||
|
||||
//go:embed migrations/*.sql
|
||||
var migrationFiles embed.FS
|
||||
|
||||
var migrations []Migrations
|
||||
|
||||
// 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
|
||||
func setupMigrations() error {
|
||||
// Walk through the embedded files and read their contents
|
||||
err := fs.WalkDir(migrationFiles, ".", func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !d.IsDir() {
|
||||
content, err := migrationFiles.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
migration := Migrations{
|
||||
name: filepath.Base(path),
|
||||
query: string(content),
|
||||
}
|
||||
migrations = append(migrations, migration)
|
||||
}
|
||||
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 {
|
||||
// check if migration table exists
|
||||
var migrationsCheck string
|
||||
//goland:noinspection SqlResolve
|
||||
err := t.db.QueryRow("SELECT name FROM sqlite_master WHERE type='table' AND name='migrations'").Scan(&migrationsCheck)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
_, err := t.db.Exec("CREATE TABLE migrations (name TEXT NOT NULL)")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating migrations table | %w", err)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("error checking if migrations table exists | %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, migration := range migrations {
|
||||
var migrationInHistory string
|
||||
err = t.db.QueryRow("SELECT name FROM migrations WHERE name = ?", migration.name).Scan(&migrationInHistory)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
_, err := t.db.Exec(migration.query)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error running migration: %s | %w", migration.name, err)
|
||||
}
|
||||
_, err = t.db.Exec("INSERT INTO migrations (name) VALUES (?)", migration.name)
|
||||
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
|
||||
}
|
73
db/embedded.go
Normal file
73
db/embedded.go
Normal file
@ -0,0 +1,73 @@
|
||||
//go:build !windows
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/tursodatabase/go-libsql"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type LibSqlDB struct {
|
||||
db *sql.DB
|
||||
connector *libsql.Connector // only used for embedded replica
|
||||
dir string // only used for embedded replica
|
||||
}
|
||||
|
||||
var syncInterval = 200 * time.Millisecond
|
||||
|
||||
func NewLibSqlDB(primaryUrl, authToken, localDBName string) (*LibSqlDB, error) {
|
||||
dir, err := os.MkdirTemp("", "libsql-*")
|
||||
if err != nil {
|
||||
fmt.Println("Error creating temporary directory:", err)
|
||||
return nil, fmt.Errorf("error setting up temporary directory for local database | %w", err)
|
||||
}
|
||||
//defer os.RemoveAll(dir)
|
||||
|
||||
dbPath := filepath.Join(dir, localDBName)
|
||||
|
||||
connector, err := libsql.NewEmbeddedReplicaConnector(dbPath, primaryUrl,
|
||||
libsql.WithAuthToken(authToken),
|
||||
libsql.WithSyncInterval(syncInterval),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating connector | %w", err)
|
||||
}
|
||||
|
||||
db := sql.OpenDB(connector)
|
||||
|
||||
err = setupMigrations()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error setting up migrations | %w", err)
|
||||
}
|
||||
|
||||
return &LibSqlDB{
|
||||
db: db,
|
||||
connector: connector,
|
||||
dir: dir,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *LibSqlDB) Close() error {
|
||||
var resultError *multierror.Error
|
||||
|
||||
if err := t.db.Close(); err != nil {
|
||||
resultError = multierror.Append(resultError, fmt.Errorf("failed to close database: %w", err))
|
||||
}
|
||||
|
||||
if t.connector != nil {
|
||||
if err := t.connector.Close(); err != nil {
|
||||
resultError = multierror.Append(resultError, fmt.Errorf("failed to close connector: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
if t.dir != "" {
|
||||
if err := os.RemoveAll(t.dir); err != nil {
|
||||
resultError = multierror.Append(resultError, fmt.Errorf("failed to remove directory %s: %w", t.dir, err))
|
||||
}
|
||||
}
|
||||
|
||||
return resultError.ErrorOrNil()
|
||||
}
|
7
db/migrations/001_create-tables.sql
Normal file
7
db/migrations/001_create-tables.sql
Normal file
@ -0,0 +1,7 @@
|
||||
CREATE TABLE eventlog (
|
||||
eventid TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
body BLOB,
|
||||
metadata BLOB,
|
||||
createdat TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
41
db/remote-only.go
Normal file
41
db/remote-only.go
Normal file
@ -0,0 +1,41 @@
|
||||
//go:build windows
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
_ "github.com/tursodatabase/libsql-client-go/libsql"
|
||||
)
|
||||
|
||||
type LibSqlDB struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewLibSqlDB(primaryUrl, authToken, localDBName string) (*LibSqlDB, error) {
|
||||
url := primaryUrl + "?authToken=" + authToken
|
||||
db, err := sql.Open("libsql", url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error setting up LibSQL db | %w", err)
|
||||
}
|
||||
|
||||
err = setupMigrations()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error setting up migrations | %w", err)
|
||||
}
|
||||
|
||||
return &LibSqlDB{
|
||||
db: db,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *LibSqlDB) Close() error {
|
||||
var resultError *multierror.Error
|
||||
|
||||
if err := t.db.Close(); err != nil {
|
||||
resultError = multierror.Append(resultError, fmt.Errorf("failed to close database: %w", err))
|
||||
}
|
||||
|
||||
return resultError.ErrorOrNil()
|
||||
}
|
Reference in New Issue
Block a user