Skip to content

Commit

Permalink
rewrite(driver): changed model creation through the use of bingo.Docu…
Browse files Browse the repository at this point in the history
…ment
  • Loading branch information
Noku committed Oct 19, 2023
1 parent 2751cda commit 79c237c
Show file tree
Hide file tree
Showing 9 changed files with 264 additions and 79 deletions.
30 changes: 22 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const (
)

type Function struct {
bingo.Document
Name string `json:"name,omitempty"`
Category string `json:"category,omitempty"`
Args []string `json:"args,omitempty"`
Expand All @@ -47,10 +48,6 @@ type Function struct {
Platform Platform `json:"platform,omitempty"`
}

func (f Function) Key() []byte {
return []byte(fmt.Sprintf("%s-%s", f.Name, f.Platform))
}

func main() {
driver, err := bingo.NewDriver(bingo.DriverConfig{
Database: "clippy.db",
Expand Down Expand Up @@ -120,15 +117,12 @@ You can specify an autoincrement ID by returning nil in the `Key` method.

```go
type User struct {
bingo.Document
Username string `json:"username,omitempty" validate:"required,min=3,max=64"`
Email string `json:"email,omitempty" validate:"required,email"`
Password string `json:"password,omitempty" preprocessor:"password-prep" validate:"required,min=6,max=64"`
}

func (u User) Key() []byte {
return []byte(u.Username)
}

func (u *User) CheckPassword(password string) bool {
current := u.Password
if strings.HasPrefix(current, "hash:") {
Expand All @@ -152,6 +146,7 @@ func (u *User) EnsureHashedPassword() error {
```

### 3. Create a collection for your document type
Note: Your document type must have bingo.Document as it's first field or implement the `bingo.DocumentSpec` interface.

```go
users := bingo.CollectionFrom[User](driver, "users")
Expand Down Expand Up @@ -182,6 +177,25 @@ if err != nil {
fmt.Println("Inserted user with ID:", id)
```

**Inserting documents with a custom ID:**

```go
id, err := users.Insert(User{
Document: bingo.Document{
ID: []byte("custom-id"),
},
Username: "test",
Password: "test123",
Email: "[email protected]",
})
if err != nil {
panic(err)
}

fmt.Println("Inserted user with ID:", id)
```


**Querying documents:**

```go
Expand Down
129 changes: 68 additions & 61 deletions bingo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@ package bingo_test
import (
"fmt"
"github.com/nokusukun/bingo"
"go.etcd.io/bbolt"
"os"
"strings"
"testing"
)

type TestDocument struct {
ID string `json:"id" validate:"required"`
bingo.Document
Name string `json:"name" validate:"required"`
}

func (td TestDocument) Key() []byte {
return []byte(td.ID)
}
//func (td TestDocument) Key() []byte {
// return []byte(td.ID)
//}

// Initialize the driver
func TestNewDriver(t *testing.T) {
Expand Down Expand Up @@ -54,22 +55,35 @@ func TestCRUD(t *testing.T) {
coll := bingo.CollectionFrom[TestDocument](driver, "testCollection")
t.Run("should insert", func(t *testing.T) {
// Insert
doc := TestDocument{ID: "1", Name: "Test"}
_, err = coll.Insert(doc)
_, err = coll.Insert(TestDocument{Document: bingo.Document{ID: "1"}, Name: "Test"})
if err != nil {
t.Fatalf("Failed to insert document: %v", err)
}

doc = TestDocument{ID: "2", Name: "Fest"}
_, err = coll.Insert(doc)
_, err = coll.Insert(TestDocument{Name: "Fest"})
if err != nil {
t.Fatalf("Failed to insert document: %v", err)
}

generatedId, err := coll.Insert(TestDocument{Name: "Rest"})
if err != nil {
t.Fatalf("Failed to insert document: %v", err)
}
fmt.Println("Generated Id", string(generatedId))
coll.Driver.View(func(tx *bbolt.Tx) error {
return tx.ForEach(func(name []byte, b *bbolt.Bucket) error {
fmt.Println("Bucket", string(name))
return b.ForEach(func(k, v []byte) error {
fmt.Println("Key", string(k), "Value", string(v))
return nil
})
})
})
})

// should not insert the same document twice
t.Run("should not insert the same document twice", func(t *testing.T) {
doc := TestDocument{ID: "1", Name: "Test"}
doc := TestDocument{Document: bingo.Document{ID: "1"}, Name: "Test"}
_, err = coll.Insert(doc)
if err == nil {
t.Fatalf("Expected insertion failure due to duplicate key")
Expand All @@ -96,15 +110,15 @@ func TestCRUD(t *testing.T) {

// should not insert but not throw an error because IgnoreErrors is passed
t.Run("should not insert but not throw an error because IgnoreErrors is passed", func(t *testing.T) {
doc := TestDocument{ID: "1", Name: "Test"}
doc := TestDocument{Document: bingo.Document{ID: "1"}, Name: "Test"}
id, err := coll.Insert(doc, bingo.IgnoreErrors)
if err != nil && id != nil {
t.Fatalf("Expected insertion to succeed due to IgnoreErrors")
}
})

t.Run("should upsert data", func(t *testing.T) {
doc := TestDocument{ID: "1", Name: "Fooby"}
doc := TestDocument{Document: bingo.Document{ID: "1"}, Name: "Fooby"}
id, err := coll.Insert(doc, bingo.Upsert)
if err != nil {
t.Fatalf("Expected insertion to succeed due to Upsert")
Expand Down Expand Up @@ -165,9 +179,9 @@ func TestFindAll(t *testing.T) {
coll := bingo.CollectionFrom[TestDocument](driver, "testCollection")

docs := []TestDocument{
{ID: "1", Name: "Apple"},
{ID: "2", Name: "Banana"},
{ID: "3", Name: "Cherry"},
{Name: "Apple"},
{Name: "Banana"},
{Name: "Cherry"},
}
for _, doc := range docs {
_, err = coll.Insert(doc)
Expand Down Expand Up @@ -204,9 +218,9 @@ func TestUpdateOne(t *testing.T) {
coll := bingo.CollectionFrom[TestDocument](driver, "testCollection")

docs := []TestDocument{
{ID: "1", Name: "Apple"},
{ID: "2", Name: "Banana"},
{ID: "3", Name: "Cherry"},
{Name: "Apple"},
{Name: "Banana"},
{Name: "Cherry"},
}
ids, err := coll.InsertMany(docs)
if err != nil {
Expand Down Expand Up @@ -260,9 +274,9 @@ func TestDeleteOne(t *testing.T) {
coll := bingo.CollectionFrom[TestDocument](driver, "testCollection")

docs := []TestDocument{
{ID: "1", Name: "Apple"},
{ID: "2", Name: "Banana"},
{ID: "3", Name: "Cherry"},
{Name: "Apple"},
{Name: "Banana"},
{Name: "Cherry"},
}
ids, err := coll.InsertMany(docs)
if err != nil {
Expand Down Expand Up @@ -312,21 +326,21 @@ func TestDeleteIter(t *testing.T) {
coll := bingo.CollectionFrom[TestDocument](driver, "testCollection")

docs := []TestDocument{
{ID: "1", Name: "Apple"},
{ID: "2", Name: "Banana"},
{ID: "3", Name: "Cherry"},
{ID: "4", Name: "Pineapple"},
{ID: "5", Name: "Strawberry"},
{ID: "6", Name: "Watermelon"},
{ID: "7", Name: "Orange"},
{ID: "8", Name: "Grape"},
{ID: "9", Name: "Kiwi"},
{ID: "10", Name: "Mango"},
{ID: "11", Name: "Peach"},
{ID: "12", Name: "Pear"},
{ID: "13", Name: "Plum"},
{ID: "14", Name: "Pomegranate"},
{ID: "15", Name: "Raspberry"},
{Name: "Apple"},
{Name: "Banana"},
{Name: "Cherry"},
{Name: "Pineapple"},
{Name: "Strawberry"},
{Name: "Watermelon"},
{Name: "Orange"},
{Name: "Grape"},
{Name: "Kiwi"},
{Name: "Mango"},
{Name: "Peach"},
{Name: "Pear"},
{Name: "Plum"},
{Name: "Pomegranate"},
{Name: "Raspberry"},
}
ids, err := coll.InsertMany(docs)
if err != nil {
Expand Down Expand Up @@ -374,21 +388,21 @@ func TestUpdateIter(t *testing.T) {
coll := bingo.CollectionFrom[TestDocument](driver, "testCollection")

docs := []TestDocument{
{ID: "1", Name: "Apple"},
{ID: "2", Name: "Banana"},
{ID: "3", Name: "Cherry"},
{ID: "4", Name: "Pineapple"},
{ID: "5", Name: "Strawberry"},
{ID: "6", Name: "Watermelon"},
{ID: "7", Name: "Orange"},
{ID: "8", Name: "Grape"},
{ID: "9", Name: "Kiwi"},
{ID: "10", Name: "Mango"},
{ID: "11", Name: "Peach"},
{ID: "12", Name: "Pear"},
{ID: "13", Name: "Plum"},
{ID: "14", Name: "Pomegranate"},
{ID: "15", Name: "Raspberry"},
{Name: "Apple"},
{Name: "Banana"},
{Name: "Cherry"},
{Name: "Pineapple"},
{Name: "Strawberry"},
{Name: "Watermelon"},
{Name: "Orange"},
{Name: "Grape"},
{Name: "Kiwi"},
{Name: "Mango"},
{Name: "Peach"},
{Name: "Pear"},
{Name: "Plum"},
{Name: "Pomegranate"},
{Name: "Raspberry"},
}
ids, err := coll.InsertMany(docs)
if err != nil {
Expand Down Expand Up @@ -445,9 +459,9 @@ func TestQueryFunctionality(t *testing.T) {

// Insert multiple docs
docs := []TestDocument{
{ID: "1", Name: "Apple"},
{ID: "2", Name: "Banana"},
{ID: "3", Name: "Cherry"},
{Name: "Apple"},
{Name: "Banana"},
{Name: "Cherry"},
}
ids, err := coll.InsertMany(docs)
if err != nil {
Expand Down Expand Up @@ -496,13 +510,6 @@ func TestErrorScenarios(t *testing.T) {

coll := bingo.CollectionFrom[TestDocument](driver, "testErrorCollection")

// Insert doc with missing ID (should fail validation)
doc := TestDocument{Name: "Invalid"}
_, err = coll.Insert(doc)
if err == nil {
t.Fatal("Expected insertion failure due to validation error")
}

// Find non-existent document
_, err = coll.FindByKey("nonexistent")
if err == nil || !bingo.IsErrDocumentNotFound(err) {
Expand Down Expand Up @@ -534,7 +541,7 @@ func TestMiddlewareFunctionality(t *testing.T) {
})

// Insert
doc := TestDocument{ID: "1", Name: "Original"}
doc := TestDocument{Document: bingo.Document{ID: "1"}, Name: "Original"}
coll.Insert(doc)

// Find and check if middleware modified the name
Expand Down
41 changes: 32 additions & 9 deletions collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package bingo
import (
"errors"
"fmt"
"github.com/bwmarrin/snowflake"
"go.etcd.io/bbolt"
"reflect"
)

type KeyMap map[string]any
Expand All @@ -28,6 +30,7 @@ type Collection[DocumentType DocumentSpec] struct {
afterDelete func(doc *DocumentType) error
beforeInsert func(doc *DocumentType) error
afterInsert func(doc *DocumentType) error
keyGenerator func(count int, document *DocumentType) []byte
}

// BeforeUpdate registers a function to be called before a document is updated in the collection.
Expand Down Expand Up @@ -142,20 +145,13 @@ func (c *Collection[T]) insertWithTx(bucket *bbolt.Bucket, doc T, opt *InsertOpt
}
}

idBytes := c.getKey(bucket, &doc)

marshal, err := Marshaller.Marshal(doc)
if err != nil {
return nil, err
}

var idBytes []byte

key := doc.Key()
if len(key) == 0 {
uniqueId, _ := bucket.NextSequence()
idBytes = []byte(fmt.Sprintf("%v", uniqueId))
} else {
idBytes = key
}
err = bucket.Put(idBytes, marshal)

if err != nil {
Expand All @@ -172,6 +168,33 @@ func (c *Collection[T]) insertWithTx(bucket *bbolt.Bucket, doc T, opt *InsertOpt
return idBytes, nil
}

//var base32encoder = base32.NewEncoding("0123456789abcdefghijklmnopqrstuv").WithPadding(base32.NoPadding)

var node *snowflake.Node

func (c *Collection[T]) getKey(bucket *bbolt.Bucket, doc *T) []byte {
if node == nil {
var err error
node, err = snowflake.NewNode(1)
if err != nil {
panic(fmt.Errorf("unable to create snowflake node: %v", err))
}
}
var idBytes []byte

key := (*doc).Key()
if len(key) == 0 {
idBytes = []byte(node.Generate().Base58())
if c.keyGenerator != nil {
idBytes = c.keyGenerator(bucket.Stats().KeyN, doc)
}
reflect.ValueOf(doc).Elem().FieldByName("ID").SetString(string(idBytes))
} else {
idBytes = key
}
return idBytes
}

func (c *Collection[T]) FindOneWithKey(filter func(doc T) bool) (T, []byte, error) {
var empty T
r, keys, _, err := c.queryFind(Query[T]{
Expand Down
9 changes: 9 additions & 0 deletions document.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package bingo

type Document struct {
ID string `json:"_id" bingo_json:"_id"`
}

func (d Document) Key() []byte {
return []byte(d.ID)
}
Loading

0 comments on commit 79c237c

Please sign in to comment.