Skip to content

Commit

Permalink
next steps: extended schema.go and created duckdb.go
Browse files Browse the repository at this point in the history
  • Loading branch information
Ludwig Lehnert committed Sep 23, 2024
1 parent 6a7ac24 commit 34f8393
Show file tree
Hide file tree
Showing 9 changed files with 629 additions and 83 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# LDB (lehnert-db) Framework

SQLite3 DBMS wrapper as-a-library inspired by [pocketbase](https://pocketbase.io/).
[DuckDB](https://duckdb.org/) DBMS wrapper as-a-library inspired by [pocketbase](https://pocketbase.io/).

## Key Features

Expand Down
30 changes: 30 additions & 0 deletions database.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package ldb

type DatabaseAdapter interface {
Close() error
Begin() (DatabaseTransaction, error)
}

type DatabaseTransaction interface {
// perform commit; implementation may be omitted for NoSQL datbases
Commit() error
// perform rollback; implementation may be omitted for NoSQL databases
Rollback() error

SaveCollection(collection Collection) error
DropCollection(collection Collection) error

SaveView(view View) error
DropView(view View) error

// checks if the migration with the given name has already been performed
MigrationExists(migrationName string) (bool, error)
// saves the given migration name to the migration history
FinishMigration(migrationName string) error

// GetCollection(name string, fields map[string]FieldType) ([]any, error)
// GetRecord(collection string, fields map[string]FieldType, id string) (any, error)
// CreateRecord(collection string, fields map[string]FieldType, data map[string]any) (string, error)
// UpdateRecord(collection string, fields map[string]FieldType, id string, data map[string]any) error
// DeleteRecord(collection string, fields map[string]FieldType, id string) error
}
198 changes: 198 additions & 0 deletions duckdb.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package ldb

import (
"database/sql"
"fmt"
"strings"

_ "github.com/marcboeker/go-duckdb"
"github.com/samber/lo"
)

var _ DatabaseAdapter = DuckDBAdapter{}
var _ DatabaseTransaction = DuckDBTransaction{}

type DuckDBAdapter struct {
db *sql.DB
}

func OpenDuckDBAdapter(databaseFilePath string) (*DuckDBAdapter, error) {
db, err := sql.Open("duckdb", databaseFilePath)
if err != nil {
return nil, err
}

return &DuckDBAdapter{db}, nil
}

func (s DuckDBAdapter) Close() error {
return s.db.Close()
}

func (s DuckDBAdapter) Begin() (DatabaseTransaction, error) {
tx, err := s.db.Begin()
if err != nil {
return nil, err
}

return DatabaseTransaction(DuckDBTransaction{tx}), nil
}

type DuckDBTransaction struct {
tx *sql.Tx
}

// Commit implements DatabaseTransaction.
func (s DuckDBTransaction) Commit() error {
return s.tx.Commit()
}

// Rollback implements DatabaseTransaction.
func (s DuckDBTransaction) Rollback() error {
return s.tx.Rollback()
}

// SaveCollection implements DatabaseTransaction.
func (s DuckDBTransaction) SaveCollection(collection Collection) error {
// create collection if not exists
if collection.original == nil {
columns := []string{}
for _, field := range collection.Schema.Fields {
columns = append(columns, columnSQL(field.Name, field.Schema.Type))
}

sql := fmt.Sprintf("CREATE TABLE %s (%s)", collection.Name, strings.Join(columns, ", "))

_, err := s.tx.Exec(sql)
return err
}

// rename collection if neccessary
if collection.original.Name != collection.Name {
sql := fmt.Sprintf("ALTER TABLE %s RENAME TO %s", collection.original.Name, collection.Name)
_, err := s.tx.Exec(sql)
if err != nil {

return err
}
}

createFields := lo.Filter(collection.Schema.Fields, func(field *Field, i int) bool {
return field.original == nil
})

renameFields := lo.Filter(collection.Schema.Fields, func(field *Field, i int) bool {
return field.original.original.Name != field.Name
})

removeFields := []*Field{}
if collection.original != nil {
removeFields = lo.Filter(collection.original.Schema.Fields, func(origField *Field, i int) bool {
_, found := lo.Find(collection.Schema.Fields, func(field *Field) bool {
return field.original != nil && field.original.Name == origField.Name
})

return !found
})
}

for _, field := range removeFields {
sql := fmt.Sprintf("ALTER TABLE %s DROP COLUMN %s", collection.Name, field.Name)
if _, err := s.tx.Exec(sql); err != nil {
return err
}
}

for _, field := range renameFields {
sql := fmt.Sprintf("ALTER TABLE %s RENAME COLUMN %s TO %s", collection.Name, field.original.Name, field.Name)
if _, err := s.tx.Exec(sql); err != nil {
return err
}
}

for _, field := range createFields {
sql := fmt.Sprintf("ALTER TABLE %s ADD COLUMN %s", collection.Name, columnSQL(field.Name, field.Schema.Type))
if _, err := s.tx.Exec(sql); err != nil {
return err
}
}

return nil
}

// DropCollection implements DatabaseTransaction.
func (s DuckDBTransaction) DropCollection(collection Collection) error {
panic("unimplemented")
}

// SaveView implements DatabaseTransaction.
func (s DuckDBTransaction) SaveView(view View) error {
panic("unimplemented")
}

// DropView implements DatabaseTransaction.
func (s DuckDBTransaction) DropView(view View) error {
panic("unimplemented")
}

// MigrationExists implements DatabaseTransaction.
func (s DuckDBTransaction) MigrationExists(migrationName string) (bool, error) {
panic("unimplemented")
}

// FinishMigration implements DatabaseTransaction.
func (s DuckDBTransaction) FinishMigration(migrationName string) error {
panic("unimplemented")
}

func withNullConstraint(sql string, nullable bool) string {
if nullable {
return sql + " NULL"
}

return sql + " NOT NULL"
}

func columnSQL(column string, fieldType FieldType) string {
switch ft := fieldType.(type) {
case FieldTypeBool:
return withNullConstraint(column+" BOOL", ft.Nullable)

case FieldTypeDateTime:
return withNullConstraint(column+" TIMESTAMP", ft.Nullable)

case FieldTypeEnum:
return withNullConstraint(column+" TEXT", ft.Nullable)

case FieldTypeFloat:
return withNullConstraint(column+" REAL", ft.Nullable)

case FieldTypeId:
sql := withNullConstraint(column+" TEXT", ft.Nullable || ft.PrimaryKey)

if ft.PrimaryKey {
sql += " PRIMARY KEY"
}

return sql

case FieldTypeInt:
return withNullConstraint(column+" BIGINT", ft.Nullable)

case FieldTypeSingleRelation:
sql := withNullConstraint(column+" TEXT", ft.Nullable)
sql += " REFERENCES " + ft.Collection + "(id)"

if ft.CascadeDelete {
sql += " ON DELETE CASCADE"
}

return sql

case FieldTypeText:
return withNullConstraint(column+" TEXT", ft.Nullable)

default:
panic("SQLiteAdapter: unexpected fieldType")
}
}
23 changes: 23 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
module lehnert.dev/ldb

go 1.22.7

require (
github.com/marcboeker/go-duckdb v1.8.0
github.com/samber/lo v1.47.0
)

require (
github.com/apache/arrow/go/v17 v17.0.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/google/flatbuffers v24.3.25+incompatible // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/zeebo/xxh3 v1.0.2 // indirect
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/tools v0.22.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
)
49 changes: 49 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
github.com/apache/arrow/go/v17 v17.0.0 h1:RRR2bdqKcdbss9Gxy2NS/hK8i4LDMh23L6BbkN5+F54=
github.com/apache/arrow/go/v17 v17.0.0/go.mod h1:jR7QHkODl15PfYyjM2nU+yTLScZ/qfj7OSUZmJ8putc=
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/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI=
github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
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/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/marcboeker/go-duckdb v1.8.0 h1:iOWv1wTL0JIMqpyns6hCf5XJJI4fY6lmJNk+itx5RRo=
github.com/marcboeker/go-duckdb v1.8.0/go.mod h1:2oV8BZv88S16TKGKM+Lwd0g7DX84x0jMxjTInThC8Is=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
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/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
gonum.org/v1/gonum v0.15.0 h1:2lYxjRbTYyxkJxlhC+LvJIx3SsANPdRybu1tGj9/OrQ=
gonum.org/v1/gonum v0.15.0/go.mod h1:xzZVBJBtS+Mz4q0Yl2LJTk+OxOg4jiXZ7qBoM0uISGo=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
4 changes: 1 addition & 3 deletions ldb.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package ldb

import "fmt"

type App struct {
Migrations map[string]*Migration
DatabaseService *DatabaseService
Expand All @@ -14,7 +12,7 @@ type Migration struct {
}

type DatabaseService interface {
CreateCollection(name string, schema CollectionSchema) error
CreateCollection(schema CollectionSchema) error
DropCollection(name string) error
}

Expand Down
63 changes: 63 additions & 0 deletions ldb_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package ldb_test

import (
"testing"

"lehnert.dev/ldb"
)

func TestSQLite(t *testing.T) {
adapter, err := ldb.OpenDuckDBAdapter("/tmp/test.db")
if err != nil {
t.Fatal(err)
}

tx, err := adapter.Begin()
if err != nil {
t.Fatal(err)
}

if err := tx.SaveCollection(ldb.Collection{
Name: "test0",
Schema: &ldb.CollectionSchema{
Fields: []*ldb.Field{
{
Name: "id",
Schema: &ldb.FieldSchema{
Type: ldb.FieldTypeId{
PrimaryKey: true,
},
},
},
},
},
}); err != nil {
t.Fatal(err)
}

if err := tx.SaveCollection(ldb.Collection{
Name: "test1",
Schema: &ldb.CollectionSchema{
Fields: []*ldb.Field{
{Name: "bool", Schema: &ldb.FieldSchema{Type: ldb.FieldTypeBool{}}},
{Name: "datetime", Schema: &ldb.FieldSchema{Type: ldb.FieldTypeDateTime{}}},
{Name: "enum", Schema: &ldb.FieldSchema{Type: ldb.FieldTypeEnum{EnumValues: []string{"a", "b", "c"}}}},
{Name: "float", Schema: &ldb.FieldSchema{Type: ldb.FieldTypeFloat{}}},
{Name: "id", Schema: &ldb.FieldSchema{Type: ldb.FieldTypeId{PrimaryKey: true}}},
{Name: "int", Schema: &ldb.FieldSchema{Type: ldb.FieldTypeInt{}}},
{Name: "singleRelation", Schema: &ldb.FieldSchema{Type: ldb.FieldTypeSingleRelation{Collection: "test0"}}},
{Name: "text", Schema: &ldb.FieldSchema{Type: ldb.FieldTypeText{}}},
},
},
}); err != nil {
t.Fatal(err)
}

if err := tx.Commit(); err != nil {
t.Fatal(err)
}

if err := adapter.Close(); err != nil {
t.Fatal(err)
}
}
1 change: 1 addition & 0 deletions migration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package ldb
Loading

0 comments on commit 34f8393

Please sign in to comment.