initial commit
This commit is contained in:
@@ -0,0 +1,50 @@
|
|||||||
|
package sqaffold
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNoRows = NewError(http.StatusNotFound, "no rows", nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Error struct {
|
||||||
|
StatusCode int
|
||||||
|
Text string
|
||||||
|
|
||||||
|
Previous error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
return e.Text
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Is(target error) bool {
|
||||||
|
if other, ok := target.(*Error); ok {
|
||||||
|
if other.StatusCode > 0 {
|
||||||
|
if e.StatusCode != other.StatusCode {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if other.Text != "" {
|
||||||
|
if e.Text != other.Text {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func Err(err error) *Error {
|
||||||
|
return NewError(http.StatusInternalServerError, err.Error(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewError(statusCode int, text string, previous error) *Error {
|
||||||
|
err := &Error{
|
||||||
|
StatusCode: statusCode,
|
||||||
|
Text: text,
|
||||||
|
Previous: previous,
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
module code.aneur.in/go/sqaffold
|
||||||
|
|
||||||
|
go 1.25.7
|
||||||
|
|
||||||
|
require (
|
||||||
|
code.aneur.in/go/record v0.0.0-20260704022026-db3f74ccdeb4
|
||||||
|
code.aneur.in/go/version v0.0.0-20260706210436-9f229dde97c9
|
||||||
|
modernc.org/sqlite v1.57.0
|
||||||
|
)
|
||||||
|
|
||||||
|
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.24 // indirect
|
||||||
|
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
golang.org/x/sys v0.47.0 // indirect
|
||||||
|
modernc.org/libc v1.74.4 // indirect
|
||||||
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
|
modernc.org/memory v1.11.0 // indirect
|
||||||
|
)
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
code.aneur.in/go/record v0.0.0-20260704022026-db3f74ccdeb4 h1:cE8tCjhHePkEMBEQbkuTy/NQ5DlAoDwRPQERqcZK5DM=
|
||||||
|
code.aneur.in/go/record v0.0.0-20260704022026-db3f74ccdeb4/go.mod h1:nIqf1d1YWOPSVrbBFJY2ZBEMMFSfQCgXL7nFVXOzVqo=
|
||||||
|
code.aneur.in/go/version v0.0.0-20260706210436-9f229dde97c9 h1:OquK0VYWHUts7u/23A6IiZ0PkznhIihj9U0DBSXfeM0=
|
||||||
|
code.aneur.in/go/version v0.0.0-20260706210436-9f229dde97c9/go.mod h1:G26bUe3Jroua1UDRzJO12ssn6OPcTsizffKJ+5/FK7U=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
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/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo=
|
||||||
|
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk=
|
||||||
|
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/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||||
|
github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
|
||||||
|
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
|
||||||
|
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||||
|
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
||||||
|
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
|
||||||
|
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||||
|
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
|
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||||
|
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
|
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
|
||||||
|
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
modernc.org/cc/v4 v4.29.1 h1:MKgdCV3WykTSPqpVrnxdEDS0HEd2FHpKZDzxzU5LyeI=
|
||||||
|
modernc.org/cc/v4 v4.29.1/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||||
|
modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU=
|
||||||
|
modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk=
|
||||||
|
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||||
|
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||||
|
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||||
|
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||||
|
modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI=
|
||||||
|
modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||||
|
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||||
|
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||||
|
modernc.org/libc v1.74.4 h1:fX1Omw4o2/1C2iRkkIsrQTasJQldLhRmuPreXLoWs9k=
|
||||||
|
modernc.org/libc v1.74.4/go.mod h1:eeQAS9W3sZeKYMFubydxJpII9ybHWshk+7or7bLG9co=
|
||||||
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
|
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
|
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
|
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
||||||
|
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||||
|
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
|
modernc.org/sqlite v1.57.0 h1:qNQP6xnx5M0ISNtlnxoOX0+cD5bJ0/gr9aMmndFczzg=
|
||||||
|
modernc.org/sqlite v1.57.0/go.mod h1:yCJ2cmAaIkHQ25oXWrF8H4O1lIfPYPR26yCEDj2P3pQ=
|
||||||
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
package sqaffold
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.aneur.in/go/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Scaffold) Migrate() error {
|
||||||
|
if err := s.migrateApp("sqaffold", s.migration()); err != nil {
|
||||||
|
return Err(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for appName, migration := range s.apps {
|
||||||
|
if err := s.migrateApp(appName, migration); err != nil {
|
||||||
|
return Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scaffold) migrateApp(appName string, migration version.Migration) error {
|
||||||
|
currentVersion, err := s.GetVersion(appName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
required, err := migration.Match(&version.Constraint{Gt: currentVersion})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
versions, _ := required.Versions()
|
||||||
|
if len(versions) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var updateVersion *version.Version
|
||||||
|
err = migration.Up(func(applied, next *version.Version) {
|
||||||
|
updateVersion = applied
|
||||||
|
})
|
||||||
|
|
||||||
|
if updateVersion != nil {
|
||||||
|
s.setVersion(appName, updateVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package sqaffold
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.aneur.in/go/version"
|
||||||
|
_ "modernc.org/sqlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMigrate(t *testing.T) {
|
||||||
|
conn, err := sql.Open("sqlite", "migrate_test.sqlite3")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
conn.Close()
|
||||||
|
os.Remove("migrate_test.sqlite3")
|
||||||
|
}()
|
||||||
|
|
||||||
|
s := NewScaffold(t.Context(), conn).WithApp("myapp", version.Migration{
|
||||||
|
"1.0.0": version.Patch{
|
||||||
|
Up: func() error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
Down: func() error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := s.Migrate(); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package sqaffold
|
||||||
|
|
||||||
|
import "code.aneur.in/go/version"
|
||||||
|
|
||||||
|
func (s *Scaffold) migration() version.Migration {
|
||||||
|
return version.Migration{
|
||||||
|
"0.1.0": version.Patch{
|
||||||
|
Up: func() error {
|
||||||
|
_, err := s.Exec(`
|
||||||
|
create table sqaffold_apps (
|
||||||
|
name text not null,
|
||||||
|
version text,
|
||||||
|
constraint config__unq__name unique (name) on conflict replace
|
||||||
|
)`)
|
||||||
|
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
|
||||||
|
Down: func() error {
|
||||||
|
_, err := s.Exec("drop table sqaffold_apps")
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
package sqaffold
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"code.aneur.in/go/record"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ScanOneRecord(rows *sql.Rows) (record.Record, error) {
|
||||||
|
records, err := ScanRecords(rows)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(records) == 0 {
|
||||||
|
return nil, ErrNoRows
|
||||||
|
}
|
||||||
|
|
||||||
|
return records[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScanRecords transforms SQL row data into record.Record objects, making them easier to handle with type safety.
|
||||||
|
//
|
||||||
|
// https://go.dev/ref/spec#Types
|
||||||
|
func ScanRecords(rows *sql.Rows) ([]record.Record, error) {
|
||||||
|
columns, err := rows.ColumnTypes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data := []record.Record{}
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
values := []any{}
|
||||||
|
for _, column := range columns {
|
||||||
|
switch column.ScanType().Name() {
|
||||||
|
case "bool":
|
||||||
|
var value bool
|
||||||
|
values = append(values, &value)
|
||||||
|
case "byte":
|
||||||
|
var value byte
|
||||||
|
values = append(values, &value)
|
||||||
|
case "complex64":
|
||||||
|
var value complex64
|
||||||
|
values = append(values, &value)
|
||||||
|
case "complex128":
|
||||||
|
var value complex128
|
||||||
|
values = append(values, &value)
|
||||||
|
case "float32":
|
||||||
|
var value float32
|
||||||
|
values = append(values, &value)
|
||||||
|
case "float64":
|
||||||
|
var value float64
|
||||||
|
values = append(values, &value)
|
||||||
|
case "int":
|
||||||
|
var value int
|
||||||
|
values = append(values, &value)
|
||||||
|
case "int8":
|
||||||
|
var value int8
|
||||||
|
values = append(values, &value)
|
||||||
|
case "int16":
|
||||||
|
var value int16
|
||||||
|
values = append(values, &value)
|
||||||
|
case "int32":
|
||||||
|
var value int32
|
||||||
|
values = append(values, &value)
|
||||||
|
case "int64":
|
||||||
|
var value int64
|
||||||
|
values = append(values, &value)
|
||||||
|
case "rune":
|
||||||
|
var value rune
|
||||||
|
values = append(values, &value)
|
||||||
|
case "string":
|
||||||
|
var value string
|
||||||
|
values = append(values, &value)
|
||||||
|
case "uint":
|
||||||
|
var value uint
|
||||||
|
values = append(values, &value)
|
||||||
|
case "uint8":
|
||||||
|
var value uint8
|
||||||
|
values = append(values, &value)
|
||||||
|
case "uint16":
|
||||||
|
var value uint16
|
||||||
|
values = append(values, &value)
|
||||||
|
case "uint32":
|
||||||
|
var value uint32
|
||||||
|
values = append(values, &value)
|
||||||
|
case "uint64":
|
||||||
|
var value uint64
|
||||||
|
values = append(values, &value)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown scan type %q", column.ScanType().Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := rows.Scan(values...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
row := record.Record{}
|
||||||
|
for i, column := range columns {
|
||||||
|
switch column.ScanType().Name() {
|
||||||
|
case "bool":
|
||||||
|
value := values[i].(*bool)
|
||||||
|
row[column.Name()] = *value
|
||||||
|
case "byte":
|
||||||
|
value := values[i].(*byte)
|
||||||
|
row[column.Name()] = *value
|
||||||
|
case "complex64":
|
||||||
|
value := values[i].(*complex64)
|
||||||
|
row[column.Name()] = *value
|
||||||
|
case "complex128":
|
||||||
|
value := values[i].(*complex128)
|
||||||
|
row[column.Name()] = *value
|
||||||
|
case "float32":
|
||||||
|
value := values[i].(*float32)
|
||||||
|
row[column.Name()] = *value
|
||||||
|
case "float64":
|
||||||
|
value := values[i].(*float64)
|
||||||
|
row[column.Name()] = *value
|
||||||
|
case "int":
|
||||||
|
value := values[i].(*int)
|
||||||
|
row[column.Name()] = *value
|
||||||
|
case "int8":
|
||||||
|
value := values[i].(*int8)
|
||||||
|
row[column.Name()] = *value
|
||||||
|
case "int16":
|
||||||
|
value := values[i].(*int16)
|
||||||
|
row[column.Name()] = *value
|
||||||
|
case "int32":
|
||||||
|
value := values[i].(*int32)
|
||||||
|
row[column.Name()] = *value
|
||||||
|
case "int64":
|
||||||
|
value := values[i].(*int64)
|
||||||
|
row[column.Name()] = *value
|
||||||
|
case "rune":
|
||||||
|
value := values[i].(*rune)
|
||||||
|
row[column.Name()] = *value
|
||||||
|
case "string":
|
||||||
|
value := values[i].(*string)
|
||||||
|
row[column.Name()] = *value
|
||||||
|
case "uint":
|
||||||
|
value := values[i].(*uint)
|
||||||
|
row[column.Name()] = *value
|
||||||
|
case "uint8":
|
||||||
|
value := values[i].(*uint8)
|
||||||
|
row[column.Name()] = *value
|
||||||
|
case "uint16":
|
||||||
|
value := values[i].(*uint16)
|
||||||
|
row[column.Name()] = *value
|
||||||
|
case "uint32":
|
||||||
|
value := values[i].(*uint32)
|
||||||
|
row[column.Name()] = *value
|
||||||
|
case "uint64":
|
||||||
|
value := values[i].(*uint64)
|
||||||
|
row[column.Name()] = *value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data = append(data, row)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
package sqaffold
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"code.aneur.in/go/record"
|
||||||
|
"code.aneur.in/go/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Scaffold struct {
|
||||||
|
ctx context.Context
|
||||||
|
conn *sql.DB
|
||||||
|
|
||||||
|
apps map[string]version.Migration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scaffold) Exec(query string, args ...any) (sql.Result, error) {
|
||||||
|
return s.conn.ExecContext(s.ctx, query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scaffold) Query(query string, args ...any) ([]record.Record, error) {
|
||||||
|
rows, err := s.conn.QueryContext(s.ctx, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, Err(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ScanRecords(rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scaffold) QueryOne(query string, args ...any) (record.Record, error) {
|
||||||
|
rows, err := s.conn.QueryContext(s.ctx, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, Err(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ScanOneRecord(rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scaffold) WithApp(name string, migration version.Migration) *Scaffold {
|
||||||
|
s.apps[name] = migration
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewScaffold(ctx context.Context, conn *sql.DB) *Scaffold {
|
||||||
|
s := &Scaffold{
|
||||||
|
ctx: ctx,
|
||||||
|
conn: conn,
|
||||||
|
|
||||||
|
apps: map[string]version.Migration{},
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
package sqaffold
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"code.aneur.in/go/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Scaffold) GetVersion(appName string) (*version.Version, error) {
|
||||||
|
row, err := s.QueryOne("select version from sqaffold_apps where name = ?", appName)
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
case err.Error() == "SQL logic error: no such table: sqaffold_apps (1)":
|
||||||
|
fallthrough
|
||||||
|
case errors.Is(err, ErrNoRows):
|
||||||
|
return version.Parse("0")
|
||||||
|
default:
|
||||||
|
return nil, Err(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return version.Parse(row.String("version"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scaffold) setVersion(appName string, version *version.Version) error {
|
||||||
|
_, err := s.Exec("insert into sqaffold_apps (name, version) values (?, ?)", appName, version.Text)
|
||||||
|
return err
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user