positive is a package that provides a Secondary Index layer above badger
.
Arbitrary indices can be defined using Go language itself. And indices can be queried inside current transaction.
positive is a package that provides a Secondary Index layer above badger
.
Arbitrary indices can be defined using Go language itself. And indices can be queried inside current transaction.
package main
import (
"encoding/json"
"fmt"
"log"
"time"
"github.com/dgraph-io/badger/v4"
"github.com/dc0d/positive/pkg/layer"
)
// Comment represents a sample data structure to store and index
type Comment struct {
ID string `json:"id"`
By string `json:"by"`
Text string `json:"text"`
At time.Time `json:"at"`
Tags []string `json:"tags"`
}
// createDB initializes a Badger database
func createDB(dir string) *badger.DB {
opts := badger.DefaultOptions(dir)
db, err := badger.Open(opts)
if err != nil {
log.Fatalf("Failed to open Badger DB: %v", err)
}
return db
}
// main demonstrates setting up indices, inserting data, and querying
func main() {
// Initialize Badger DB
db := createDB("./data")
defer db.Close()
// Define secondary indices using Positive
indexTags := layer.NewIndex("tags", func(key, val []byte) (entries []layer.IndexEntry, err error) {
var c Comment
if err := json.Unmarshal(val, &c); err != nil {
return nil, err
}
for _, tag := range c.Tags {
entries = append(entries, layer.IndexEntry{Index: []byte(tag)})
}
return entries, nil
})
indexBy := layer.NewIndex("by", func(key, val []byte) (entries []layer.IndexEntry, err error) {
var c Comment
if err := json.Unmarshal(val, &c); err != nil {
return nil, err
}
if c.By != "" {
entries = append(entries, layer.IndexEntry{Index: []byte(c.By)})
}
return entries, nil
})
// Index builder function for transactions
sampleIndexBuilder := func(txn *layer.Txn, entries map[string][]byte) error {
for k, v := range entries {
if err := layer.Emit(txn, indexTags, []byte(k), v); err != nil {
return err
}
if err := layer.Emit(txn, indexBy, []byte(k), v); err != nil {
return err
}
}
return nil
}
// Insert sample data
comment := Comment{
ID: "CMNT::001",
By: "Frodo Baggins",
Text: "Hi there!",
At: time.Now(),
Tags: []string{"tech", "golang"},
}
js, err := json.Marshal(comment)
if err != nil {
log.Fatalf("Failed to marshal comment: %v", err)
}
txn := db.NewTransaction(true)
defer txn.Discard()
if err := txn.Set([]byte(comment.ID), js); err != nil {
log.Fatalf("Failed to set data: %v", err)
}
if err := txn.CommitWith(sampleIndexBuilder, nil); err != nil {
log.Fatalf("Failed to commit transaction: %v", err)
}
fmt.Println("Data inserted successfully")
// Query by tag
queryByTag(db, indexTags, "golang")
// Query by author
queryByAuthor(db, indexBy, "Frodo Baggins")
}
// queryByTag queries comments by a specific tag
func queryByTag(db *badger.DB, index *layer.Index, tag string) {
txn := layer.NewTxn(db.NewTransaction(false))
defer txn.Discard()
params := layer.Q{
IndexName: index.Name(),
Value: []byte(tag),
}
results, count, err := layer.QueryIndex(params, txn)
if err != nil {
log.Printf("Failed to query by tag: %v", err)
return
}
fmt.Printf("Found %d comments with tag '%s':\n", count, tag)
for _, res := range results {
var c Comment
if err := json.Unmarshal(res.Val, &c); err != nil {
log.Printf("Failed to unmarshal result: %v", err)
continue
}
fmt.Printf("- %s by %s: %s\n", c.ID, c.By, c.Text)
}
}
// queryByAuthor queries comments by a specific author
func queryByAuthor(db *badger.DB, index *layer.Index, author string) {
txn := layer.NewTxn(db.NewTransaction(false))
defer txn.Discard()
params := layer.Q{
IndexName: index.Name(),
Value: []byte(author),
}
results, count, err := layer.QueryIndex(params, txn)
if err != nil {
log.Printf("Failed to query by author: %v", err)
return
}
fmt.Printf("Found %d comments by '%s':\n", count, author)
for _, res := range results {
var c Comment
if err := json.Unmarshal(res.Val, &c); err != nil {
log.Printf("Failed to unmarshal result: %v", err)
continue
}
fmt.Printf("- %s: %s (Tags: %v)\n", c.ID, c.Text, c.Tags)
}
}