hookdb

package module
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 23, 2024 License: Apache-2.0 Imports: 8 Imported by: 0

README

HookDB

This key-value store is a simple, lightweight, on-memory database. One of its unique features is the ability to pre-register key prefixes. Whenever a key with a registered prefix is stored in the database, a specific function is triggered to notify or handle that event.

Features

  • Key-Value Store
  • Put, Delete, Get and Query commands
  • HookHandler, Callback function triggered by put key
  • Deletion after HookHandler call
  • Transaction
  • Scription to key prefix events

Subscribe to Key Prefix Events

HookDB allows you to subscribe to specific key prefixes and receive notifications whenever a key with the registered prefix is put into the database. This feature is useful for triggering custom logic based on specific key patterns.

Install

go get github.com/yyyoichi/hookdb

Example

import (
    "context"
    "encoding/json"
    "fmt"
    "log"
    "net/http"

    "github.com/yyyoichi/hookdb"
)

func Example() {
    db := hookdb.New()

    http.HandleFunc("POST /game/{gameId}/actions", func(w http.ResponseWriter, r *http.Request) {
        var requestBody struct {
            Action string `json:"action"`
        }
        _ = json.NewDecoder(r.Body).Decode(&requestBody)

        gameID := r.URL.Query().Get("gameId")
        key := fmt.Sprintf("GAME%s#ACT", gameID)
        err := db.Put([]byte(key), []byte(requestBody.Action))
        if err != nil {
            log.Fatal(err)
        }
        w.WriteHeader(http.StatusOK)
    })

    // in other gorutin, subscribe to the key "GAME100#ACT" until the context is done
    go func() {
        ctx := context.Background()
        event, err := db.Subscribe(ctx, []byte("GAME100#ACT"))
        if err != nil {
            log.Fatal(err)
        }
        for v := range event {
            log.Println(string(v))
        }
    }()

    http.ListenAndServe(":8080", nil)

    // curl -X POST -H "Content-Type: application/json" -d '{"action":"PUNCH"}' http://localhost:8080/game/100/actions
    //  -> print: 'PUNCH'
}

examples

Documentation

Overview

Example
package main

import (
	"fmt"
	"log"

	"github.com/yyyoichi/hookdb"
)

func main() {
	db := hookdb.New()
	err := db.AppendHook([]byte("c"), func(k, v []byte) (removeHook bool) {
		fmt.Printf("%s: %s\n", k, v)
		return false
	})
	if err != nil {
		log.Fatal(err)
	}

	err = db.Put([]byte("apple"), []byte("10kg"))
	if err != nil {
		log.Fatal(err)
	}

	// exp hit
	err = db.Put([]byte("chocolate"), []byte("100g"))
	if err != nil {
		log.Fatal(err)
	}

	err = db.Put([]byte("car"), []byte("3t"))
	if err != nil {
		log.Fatal(err)
	}

}
Output:

chocolate: 100g
car: 3t

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrKeyNotFound       = errors.New("key not found")
	ErrEmptyEntry        = errors.New("entry(i,k) cannot be empty")
	ErrDeleted           = errors.New("deleted")
	ErrClosedTransaction = errors.New("transaction is closed")
)

Functions

This section is empty.

Types

type DB added in v0.0.2

type DB struct {
	// contains filtered or unexported fields
}

func (*DB) AppendHook added in v0.0.2

func (db *DB) AppendHook(prefix []byte, fn HookHandler) error
Example
package main

import (
	"fmt"
	"log"

	"github.com/yyyoichi/hookdb"
)

func main() {
	db := hookdb.New()
	err := db.AppendHook([]byte("GAME100#ACT"), func(k, v []byte) (removeHook bool) {
		fmt.Printf("%s..ACTION '%s'!\n", k, v)
		return false
	})

	err = db.Put([]byte("GAME100#ACT1"), []byte("KICK"))
	if err != nil {
		log.Fatal(err)
	}
	err = db.Put([]byte("GAME100#ACT2"), []byte("PUNCH"))
	if err != nil {
		log.Fatal(err)
	}
	err = db.Put([]byte("GAME100#ACT3"), []byte("GUARD"))
	if err != nil {
		log.Fatal(err)
	}
	// GAME999 !
	err = db.Put([]byte("GAME999#ACT1"), []byte("PUNCH"))
	if err != nil {
		log.Fatal(err)
	}

}
Output:

GAME100#ACT1..ACTION 'KICK'!
GAME100#ACT2..ACTION 'PUNCH'!
GAME100#ACT3..ACTION 'GUARD'!
Example (Removal)
package main

import (
	"fmt"
	"log"

	"github.com/yyyoichi/hookdb"
)

func main() {
	db := hookdb.New()
	err := db.AppendHook([]byte("SHOP200#ORDER"), func(k, v []byte) (removeHook bool) {
		fmt.Printf("%s..ORDER '%s'!\n", k, v)
		return true // !
	})

	// exp hit
	err = db.Put([]byte("SHOP200#ORDER1"), []byte("SHOES"))
	if err != nil {
		log.Fatal(err)
	}
	// hook is deleted

	// exp no hit
	err = db.Put([]byte("SHOP200#ORDER2"), []byte("HAT"))
	if err != nil {
		log.Fatal(err)
	}
}
Output:

SHOP200#ORDER1..ORDER 'SHOES'!

func (*DB) Delete added in v0.0.2

func (db *DB) Delete(k []byte) error
Example
package main

import (
	"errors"
	"fmt"
	"log"

	"github.com/yyyoichi/hookdb"
)

func main() {
	db := hookdb.New()
	err := db.Put([]byte("town"), []byte("Yokohama"))
	if err != nil {
		log.Fatal(err)
	}
	err = db.Put([]byte("town"), []byte("Sapporo"))
	if err != nil {
		log.Fatal(err)
	}
	err = db.Delete([]byte("town"))
	if err != nil {
		log.Fatal(err)
	}

	v, err := db.Get([]byte("town"))
	if !errors.Is(err, hookdb.ErrKeyNotFound) {
		log.Fatal(err)
	}
	fmt.Println(string(v))

}
Output:

func (*DB) Get added in v0.0.2

func (db *DB) Get(k []byte) ([]byte, error)
Example
package main

import (
	"fmt"
	"log"

	"github.com/yyyoichi/hookdb"
)

func main() {
	db := hookdb.New()
	err := db.Put([]byte("town"), []byte("Yokohama"))
	if err != nil {
		log.Fatal(err)
	}
	v, err := db.Get([]byte("town"))
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(v))

	v, err = db.Get([]byte("town"))
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(v))

}
Output:

Yokohama
Yokohama

func (*DB) Put added in v0.0.2

func (db *DB) Put(k []byte, v []byte) error
Example
package main

import (
	"fmt"
	"log"

	"github.com/yyyoichi/hookdb"
)

func main() {
	db := hookdb.New()
	err := db.Put([]byte("apple"), []byte("10kg"))
	if err != nil {
		log.Fatal(err)
	}
	err = db.Put([]byte("apple"), []byte("9kg"))
	if err != nil {
		log.Fatal(err)
	}
	err = db.Put([]byte("apple"), []byte("8kg"))
	if err != nil {
		log.Fatal(err)
	}
	v, err := db.Get([]byte("apple"))
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(v))

}
Output:

8kg

func (*DB) Query added in v0.0.2

func (db *DB) Query(ctx context.Context, k []byte, opts ...QueryOption) iter.Seq2[[]byte, error]
Example
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/yyyoichi/hookdb"
)

func main() {
	db := hookdb.New()

	err := db.Put([]byte("user01"), []byte("user01"))
	if err != nil {
		log.Fatal(err)
	}
	err = db.Put([]byte("user02"), []byte("user02"))
	if err != nil {
		log.Fatal(err)
	}
	err = db.Put([]byte("user03"), []byte("user03"))
	if err != nil {
		log.Fatal(err)
	}

	ctx := context.Background()
	for v, err := range db.Query(ctx, []byte("user")) {
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println(string(v))
	}

}
Output:

user01
user02
user03
Example (WithReverseQueryOption)
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/yyyoichi/hookdb"
)

func main() {
	db := hookdb.New()

	err := db.Put([]byte("user01"), []byte("user01"))
	if err != nil {
		log.Fatal(err)
	}
	err = db.Put([]byte("user02"), []byte("user02"))
	if err != nil {
		log.Fatal(err)
	}
	err = db.Put([]byte("user03"), []byte("user03"))
	if err != nil {
		log.Fatal(err)
	}

	ctx := context.Background()
	for v, err := range db.Query(ctx, []byte("user"), hookdb.WithReverseQuery()) {
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println(string(v))
	}

}
Output:

user03
user02
user01

func (*DB) RemoveHook added in v0.0.2

func (db *DB) RemoveHook(prefix []byte) error

func (*DB) Subscribe added in v0.0.2

func (db *DB) Subscribe(ctx context.Context, prefix []byte, opts ...SubscribeOption) (<-chan []byte, error)

Subscribe subscribes to events with the given prefix and sends the data to the returned channel. If default option is used, the returned channel will not close until the provided context is done.

Example
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/yyyoichi/hookdb"
)

func main() {
	db := hookdb.New()

	// 1. subscribe with persistent
	// 2. put 3 orders
	// 3. receive 3 orders and print
	// 4. context done
	//  	stop receive
	// 5. put 1 order
	// 6. no receive

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	event, err := db.Subscribe(ctx, []byte("order"))
	if err != nil {
		log.Fatal(err)
	}

	go func() {
		var receiveCount int
		for {
			v, ok := <-event
			if !ok {
				break
			}
			fmt.Println(string(v))
			receiveCount++
			if receiveCount == 3 {
				cancel()
				break
			}
		}
		// read all events if remaining in the channel
		// in this case, no remaining events
		// for range event {
		// }
	}()
	err = db.Put([]byte("order1"), []byte("shoes"))
	if err != nil {
		log.Fatal(err)
	}
	err = db.Put([]byte("order2"), []byte("hat"))
	if err != nil {
		log.Fatal(err)
	}
	err = db.Put([]byte("order3"), []byte("gloves"))
	if err != nil {
		log.Fatal(err)
	}
	<-ctx.Done()

	err = db.Put([]byte("order4"), []byte("socks"))
	if err != nil {
		log.Fatal(err)
	}

}
Output:

shoes
hat
gloves
Example (Once)
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/yyyoichi/hookdb"
)

func main() {
	db := hookdb.New()

	// 1. subscribe with once
	// 2. put 3 orders
	// 3. receive 1 order and print
	// 4. put 1 order
	// 5. no receive

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	event, err := db.Subscribe(ctx, []byte("order"), hookdb.WithOnceSubscription())
	if err != nil {
		log.Fatal(err)
	}

	go func() {
		for v := range event {
			fmt.Println(string(v))
		}
		cancel()
	}()
	err = db.Put([]byte("order1"), []byte("shoes"))
	if err != nil {
		log.Fatal(err)
	}
	err = db.Put([]byte("order2"), []byte("hat"))
	if err != nil {
		log.Fatal(err)
	}
	err = db.Put([]byte("order3"), []byte("gloves"))
	if err != nil {
		log.Fatal(err)
	}

	err = db.Put([]byte("order4"), []byte("socks"))
	if err != nil {
		log.Fatal(err)
	}
	<-ctx.Done()

}
Output:

shoes

type HookDB

type HookDB struct {
	*DB
}

func New

func New() *HookDB

func (*HookDB) Transaction

func (db *HookDB) Transaction() *Transaction
Example
package main

import (
	"errors"
	"fmt"
	"log"

	"github.com/yyyoichi/hookdb"
)

func main() {
	db := hookdb.New()
	err := db.Put([]byte("color1"), []byte("red"))
	if err != nil {
		log.Fatal(err)
	}

	txn := db.Transaction()
	v, err := txn.Get([]byte("color1"))
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("get color1 in txn: '%s'\n", v)

	// put in txn
	err = txn.Put([]byte("color2"), []byte("blue"))
	if err != nil {
		log.Fatal(err)
	}

	// get in txn
	v, err = txn.Get([]byte("color2"))
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("get color2 in txn: '%s'\n", v)

	// get from db
	v, err = db.Get([]byte("color2"))
	if !errors.Is(err, hookdb.ErrKeyNotFound) {
		log.Fatal(err)
	}
	fmt.Printf("get color2 from db: '%s'\n", v)

	err = txn.Commit()
	if err != nil {
		log.Fatal(err)
	}

	v, err = db.Get([]byte("color2"))
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("get color2 from db after commit: '%s'\n", v)

}
Output:

get color1 in txn: 'red'
get color2 in txn: 'blue'
get color2 from db: ''
get color2 from db after commit: 'blue'

func (*HookDB) TransactionWithLock

func (db *HookDB) TransactionWithLock() *Transaction
Example
package main

import (
	"fmt"
	"log"

	"github.com/yyyoichi/hookdb"
)

func main() {
	db := hookdb.New()

	key := []byte("key")
	txn := db.TransactionWithLock()

	done := make(chan struct{})
	go func() {
		// overwrite
		defer close(done)
		txn := db.TransactionWithLock()
		err := txn.Put(key, []byte("new-val"))
		if err != nil {
			log.Fatal(err)
		}
		err = txn.Commit()
		if err != nil {
			log.Fatal(err)
		}
	}()
	err := txn.Put(key, []byte("val"))
	if err != nil {
		log.Fatal(err)
	}
	err = txn.Commit()
	if err != nil {
		log.Fatal(err)
	}

	<-done
	v, err := db.Get(key)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(v))

}
Output:

new-val

type HookHandler

type HookHandler func(k, v []byte) (removeHook bool)

in handler, cannot appned hook

type QueryOption

type QueryOption func(*QueryOptions) error

func WithReverseQuery

func WithReverseQuery() QueryOption

type QueryOptions

type QueryOptions struct {
	Reverse bool
}

type SubscribeOption added in v0.0.2

type SubscribeOption func(*SubscribeOptions) error

func WithBufSize added in v0.0.2

func WithBufSize(size int) SubscribeOption

WithBufSize sets the buffer size of the channel that the Subscribe function returns.

func WithOnceSubscription added in v0.0.2

func WithOnceSubscription() SubscribeOption

WithOnceSubscription returns a SubscribeOption that sets the Once field of SubscribeOptions to true, indicating that the subscription should only be executed once.

type SubscribeOptions added in v0.0.2

type SubscribeOptions struct {
	Once    bool
	BufSize *int // default 1
}

type Transaction

type Transaction struct {
	*DB
}

func (*Transaction) Commit

func (txn *Transaction) Commit() error

func (*Transaction) Rollback

func (txn *Transaction) Rollback() error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL