Initial commit
This commit is contained in:
commit
42dbd4e1fa
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
.idea
|
56
database.go
Normal file
56
database.go
Normal file
@ -0,0 +1,56 @@
|
||||
package mysqlite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"zombiezen.com/go/sqlite"
|
||||
)
|
||||
|
||||
type Db struct {
|
||||
Db *sqlite.Conn
|
||||
}
|
||||
|
||||
func OpenDb(databaseSource string) (*Db, error) {
|
||||
conn, err := sqlite.OpenConn(databaseSource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Db{Db: conn}, nil
|
||||
}
|
||||
|
||||
func (d *Db) Close() error {
|
||||
return d.Db.Close()
|
||||
}
|
||||
|
||||
func (d *Db) QuerySingle(query string, args ...any) error {
|
||||
stmt, remaining, err := d.Db.PrepareTransient(query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stmt.Finalize()
|
||||
if remaining != 0 {
|
||||
return fmt.Errorf("remaining bytes: %s", remaining)
|
||||
}
|
||||
rowReturned, err := stmt.Step()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !rowReturned {
|
||||
return fmt.Errorf("did not return any rows")
|
||||
}
|
||||
if stmt.ColumnCount() != 1 {
|
||||
return fmt.Errorf("query returned %d rows while only one was expected", stmt.ColumnCount())
|
||||
}
|
||||
for i, arg := range args {
|
||||
if asString, ok := arg.(*string); ok {
|
||||
*asString = stmt.ColumnText(i)
|
||||
} else if asInt, ok := arg.(*int); ok {
|
||||
*asInt = stmt.ColumnInt(i)
|
||||
} else if asBool, ok := arg.(*bool); ok {
|
||||
*asBool = stmt.ColumnBool(i)
|
||||
} else {
|
||||
return fmt.Errorf("unsupported column type at index %d", i)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
17
go.mod
Normal file
17
go.mod
Normal file
@ -0,0 +1,17 @@
|
||||
module mysqlite
|
||||
|
||||
go 1.24
|
||||
|
||||
require (
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
modernc.org/libc v1.55.3 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
modernc.org/sqlite v1.33.1 // indirect
|
||||
zombiezen.com/go/sqlite v1.4.0 // indirect
|
||||
)
|
23
go.sum
Normal file
23
go.sum
Normal file
@ -0,0 +1,23 @@
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
|
||||
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||
modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM=
|
||||
modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k=
|
||||
zombiezen.com/go/sqlite v1.4.0 h1:N1s3RIljwtp4541Y8rM880qgGIgq3fTD2yks1xftnKU=
|
||||
zombiezen.com/go/sqlite v1.4.0/go.mod h1:0w9F1DN9IZj9AcLS9YDKMboubCACkwYCGkzoy3eG5ik=
|
87
migrator.go
Normal file
87
migrator.go
Normal file
@ -0,0 +1,87 @@
|
||||
package mysqlite
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
"zombiezen.com/go/sqlite"
|
||||
)
|
||||
|
||||
type ReadDirFileFS interface {
|
||||
fs.ReadDirFS
|
||||
fs.ReadFileFS
|
||||
}
|
||||
|
||||
func (db *Db) MigrateDb(migrations ReadDirFileFS) error {
|
||||
// Read all migrations
|
||||
migrationFiles, err := migrations.ReadDir("")
|
||||
if err != nil {
|
||||
log.Fatalf("error reading migration files: %v", err)
|
||||
}
|
||||
var migrationsByVersion = make(map[int]string)
|
||||
latestVersion := 0
|
||||
for _, f := range migrationFiles {
|
||||
versionStr := f.Name()
|
||||
version, err := strconv.Atoi(strings.SplitN(versionStr, "_", 2)[0])
|
||||
if err != nil {
|
||||
log.Fatalf("invalid version number for migration script: %v", err)
|
||||
}
|
||||
migrationsByVersion[version] = versionStr
|
||||
latestVersion = max(latestVersion, version)
|
||||
}
|
||||
|
||||
// Get current migration version from user_version
|
||||
var currentVersion int
|
||||
err = d.QuerySingle("PRAGMA user_version", ¤tVersion)
|
||||
if err != nil {
|
||||
log.Fatalf("error getting current version: %v", err)
|
||||
}
|
||||
log.Printf("Current database migration version is %d, latest version is %d", currentVersion, latestVersion)
|
||||
|
||||
// If we are no up-to-date, bring the db up-to-date
|
||||
for currentVersion != latestVersion {
|
||||
targetVersion := currentVersion + 1
|
||||
migrationFile := migrationsByVersion[targetVersion]
|
||||
log.Printf("migration to version %s", migrationFile)
|
||||
migrationScript, err := migrations.ReadFile(migrationFile)
|
||||
if err != nil {
|
||||
log.Fatalf("error opening migration script %s: %v", migrationScript, err)
|
||||
}
|
||||
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
log.Fatalf("error beginning transaction: %v", err)
|
||||
}
|
||||
defer tx.MustRollback()
|
||||
|
||||
err = tx.QuerySingle(string(migrationScript))
|
||||
if err != nil {
|
||||
log.Fatalf("error performing migration: %v", err)
|
||||
}
|
||||
|
||||
err = tx.QuerySingle(fmt.Sprintf("PRAGMA user_version = %d", targetVersion))
|
||||
if err != nil {
|
||||
log.Fatalf("error updating version: %v", err)
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
log.Fatalf("error commiting transaction: %v", err)
|
||||
}
|
||||
currentVersion = targetVersion
|
||||
}
|
||||
|
||||
log.Println("All migrations applied")
|
||||
return nil
|
||||
}
|
||||
|
||||
func rollbackIgnoringErrors(tx *sql.Tx) {
|
||||
err := tx.Rollback()
|
||||
if err != nil {
|
||||
log.Printf("error rolling back: %v", err)
|
||||
}
|
||||
}
|
72
query.go
Normal file
72
query.go
Normal file
@ -0,0 +1,72 @@
|
||||
package mysqlite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"zombiezen.com/go/sqlite"
|
||||
)
|
||||
|
||||
type Query struct {
|
||||
stmt *sqlite.Stmt
|
||||
err error
|
||||
}
|
||||
|
||||
func (d *Db) Query(query string) *Query {
|
||||
stmt, remaining, err := d.Db.PrepareTransient(query)
|
||||
if err != nil {
|
||||
return &Query{err: err}
|
||||
}
|
||||
if remaining != 0 {
|
||||
return &Query{err: fmt.Errorf("remaining bytes: %s", remaining)}
|
||||
}
|
||||
return &Query{stmt: stmt}
|
||||
}
|
||||
|
||||
//func (q *Query) Args(args ...any) *Query {
|
||||
// return q
|
||||
//}
|
||||
|
||||
func (q *Query) Exec() error {
|
||||
if q.stmt != nil {
|
||||
defer q.stmt.Finalize()
|
||||
}
|
||||
if q.err != nil {
|
||||
return q.err
|
||||
}
|
||||
rowReturned, err := q.stmt.Step()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rowReturned {
|
||||
return fmt.Errorf("row returned unexpectedly")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (q *Query) ScanSingle(results ...any) error {
|
||||
if q.stmt != nil {
|
||||
defer q.stmt.Finalize()
|
||||
}
|
||||
if q.err != nil {
|
||||
return q.err
|
||||
}
|
||||
rowReturned, err := q.stmt.Step()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !rowReturned {
|
||||
return fmt.Errorf("did not return any rows")
|
||||
}
|
||||
|
||||
for i, arg := range results {
|
||||
if asString, ok := arg.(*string); ok {
|
||||
*asString = q.stmt.ColumnText(i)
|
||||
} else if asInt, ok := arg.(*int); ok {
|
||||
*asInt = q.stmt.ColumnInt(i)
|
||||
} else if asBool, ok := arg.(*bool); ok {
|
||||
*asBool = q.stmt.ColumnBool(i)
|
||||
} else {
|
||||
return fmt.Errorf("unsupported column type at index %d", i)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
34
transaction.go
Normal file
34
transaction.go
Normal file
@ -0,0 +1,34 @@
|
||||
package mysqlite
|
||||
|
||||
import "log"
|
||||
|
||||
type Tx struct {
|
||||
db *Db
|
||||
}
|
||||
|
||||
func (d *Db) Begin() (*Tx, error) {
|
||||
err := d.QuerySingle("BEGIN")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Tx{db: d}, nil
|
||||
}
|
||||
|
||||
func (tx *Tx) Commit() error {
|
||||
return tx.Query("COMMIT").Exec()
|
||||
}
|
||||
|
||||
func (tx *Tx) Rollback() error {
|
||||
return tx.Query("ROLLBACK").Exec()
|
||||
}
|
||||
|
||||
func (tx *Tx) MustRollback() {
|
||||
err := tx.Rollback()
|
||||
if err != nil {
|
||||
log.Panicf("error doing rollback: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (tx *Tx) Query(query string) *Query {
|
||||
return tx.db.Query(query)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user