removing transactions as really they are unneccessary and just adding unneeded complexity
This commit is contained in:
parent
bc1d1e667f
commit
08724b339d
53
README.md
53
README.md
|
@ -11,37 +11,40 @@ Install using `go get github.com/nanobox-io/golang-scribble`.
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
Create a 'transaction' for scribble to transact.
|
|
||||||
|
|
||||||
`t := scribble.Transaction{Action: "read", Collection: "records", ResourceID: "<UniqueID>", Container: &v}`
|
|
||||||
|
|
||||||
+ Action - the action for scribble to perform
|
|
||||||
+ write - write to the scribble db
|
|
||||||
+ read - read from the scribble db
|
|
||||||
+ readall - read from the scribble db (all files in a collection)
|
|
||||||
+ delete - remove a record from the scribble db
|
|
||||||
+ Collection - the folder scribble will create to store grouped records
|
|
||||||
+ ResourceID - the unique ID of the resource being stored (bson, uuid, etc.)
|
|
||||||
+ Container - the Struct that contains the data scribble will marshal into the store, or what it will unmarshal into from the store
|
|
||||||
|
|
||||||
|
|
||||||
#### Full Example
|
|
||||||
```go
|
```go
|
||||||
// a new scribble driver, providing the directory where it will be writing to, and a qualified logger to which it can send any output.
|
// a new scribble driver, providing the directory where it will be writing to,
|
||||||
database, err := scribble.New(dir, logger)
|
// and a qualified logger to which it can send any output.
|
||||||
|
db, err := scribble.New(dir, logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error", err)
|
fmt.Println("Error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is what scribble will either marshal from when writing, or unmarshal into when reading
|
// Write a fish to the database
|
||||||
record := Record{}
|
fish := Fish{}
|
||||||
|
if err := db.Write("fish", "onefish", fish); err != nil {
|
||||||
|
|
||||||
// create a new transaction for scribble to run
|
}
|
||||||
t := scribble.Transaction{Action: scribble.READ, Collection: "records", ResourceID: "<UniqueID>", Container: &record}
|
|
||||||
|
// Read all fish from the database
|
||||||
|
fish := []Fish{}
|
||||||
|
if err := db.Read("fish", fish); err != nil {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read a fish from the database
|
||||||
|
fish := Fish{}
|
||||||
|
if err := db.Read("/fish/onefish", fish); err != nil {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete all fish from the database
|
||||||
|
if err := db.Delete("/fish"); err != nil {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete a fish from the database
|
||||||
|
if err := db.Delete("/fish/onefish"); err != nil {
|
||||||
|
|
||||||
// have scribble attempt to run the transaction
|
|
||||||
if err := database.Transact(t); err != nil {
|
|
||||||
fmt.Println("Error", err)
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -54,6 +57,8 @@ Complete documentation is available on [godoc](http://godoc.org/github.com/nanob
|
||||||
|
|
||||||
## Todo/Doing
|
## Todo/Doing
|
||||||
- Tests!
|
- Tests!
|
||||||
|
- Better support for sub collections
|
||||||
|
- More methods to allow different types of reads/writes
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
177
scribble.go
177
scribble.go
|
@ -5,15 +5,11 @@
|
||||||
// at http://mozilla.org/MPL/2.0/.
|
// at http://mozilla.org/MPL/2.0/.
|
||||||
//
|
//
|
||||||
|
|
||||||
// scribble is a tiny JSON flat file store. It uses transactions that tell it what
|
// scribble is a tiny JSON flat file store
|
||||||
// actions to perform, where it is to store data, and what it is going to write
|
|
||||||
// that data from, or read the data into. It creates a very simple database
|
|
||||||
// structure under a specified directory
|
|
||||||
package scribble
|
package scribble
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
@ -23,7 +19,7 @@ import (
|
||||||
"github.com/nanobox-io/golang-hatchet"
|
"github.com/nanobox-io/golang-hatchet"
|
||||||
)
|
)
|
||||||
|
|
||||||
const Version = "0.1.0"
|
const Version = "0.5.0"
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
|
||||||
|
@ -32,24 +28,8 @@ type (
|
||||||
Driver struct {
|
Driver struct {
|
||||||
mutexes map[string]sync.Mutex
|
mutexes map[string]sync.Mutex
|
||||||
dir string // the directory where scribble will create the database
|
dir string // the directory where scribble will create the database
|
||||||
log hatchet.Logger // the logger scirbble will log to
|
log hatchet.Logger // the logger scribble will log to
|
||||||
}
|
}
|
||||||
|
|
||||||
// a Transactions is what is used by a Driver to complete database operations
|
|
||||||
Transaction struct {
|
|
||||||
Action int // the action for scribble to preform
|
|
||||||
Collection string // the forlder for scribble to read/write to/from
|
|
||||||
ResourceID string // the unique ID of the record
|
|
||||||
Container interface{} // what scribble will marshal from or unmarshal into
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
//
|
|
||||||
const (
|
|
||||||
WRITE = iota
|
|
||||||
READ
|
|
||||||
READALL
|
|
||||||
DELETE
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// New creates a new scribble database at the desired directory location, and
|
// New creates a new scribble database at the desired directory location, and
|
||||||
|
@ -78,42 +58,24 @@ func New(dir string, logger hatchet.Logger) (*Driver, error) {
|
||||||
return scribble, nil
|
return scribble, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transact determins the type of transactions to be run, and calls the appropriate
|
// Write locks the database and attempts to write the record to the database under
|
||||||
// method to complete the operation
|
// the [collection] specified with the [resource] name given
|
||||||
func (d *Driver) Transact(trans Transaction) (err error) {
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// // write [fish] to database
|
||||||
|
// Write("/fish/onefish", fish{name:"onefish"})
|
||||||
|
func (d *Driver) Write(collection, resource string, v interface{}) error {
|
||||||
|
|
||||||
// determin transaction to be run
|
mutex := d.getOrCreateMutex(collection)
|
||||||
switch trans.Action {
|
|
||||||
case WRITE:
|
|
||||||
return d.write(trans)
|
|
||||||
case READ:
|
|
||||||
return d.read(trans)
|
|
||||||
case READALL:
|
|
||||||
return d.readAll(trans)
|
|
||||||
case DELETE:
|
|
||||||
return d.delete(trans)
|
|
||||||
default:
|
|
||||||
return errors.New(fmt.Sprintf("Unsupported action %+v", trans.Action))
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// write locks the database and then proceeds to write the data represented by a
|
|
||||||
// transaction.Container. It will create a direcotry that represents the collection
|
|
||||||
// to wich the record belongs (if it doesn't already exist), and write a file
|
|
||||||
// (named by he transaction.ResourceID) to that directory
|
|
||||||
func (d *Driver) write(trans Transaction) error {
|
|
||||||
|
|
||||||
mutex := d.getOrCreateMutex(trans.Collection)
|
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
defer mutex.Unlock()
|
defer mutex.Unlock()
|
||||||
|
|
||||||
//
|
//
|
||||||
dir := d.dir + "/" + trans.Collection
|
dir := d.dir + "/" + collection
|
||||||
|
|
||||||
//
|
//
|
||||||
b, err := json.MarshalIndent(trans.Container, "", "\t")
|
b, err := json.MarshalIndent(v, "", "\t")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -123,10 +85,10 @@ func (d *Driver) write(trans Transaction) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
finalPath := dir + "/" + trans.ResourceID + ".json"
|
finalPath := dir + "/" + resource + ".json"
|
||||||
tmpPath := finalPath + "~"
|
tmpPath := finalPath + "~"
|
||||||
|
|
||||||
// write marshaled data to a file, named by the resourceID
|
// write marshaled data to the temp file
|
||||||
if err := ioutil.WriteFile(tmpPath, b, 0644); err != nil {
|
if err := ioutil.WriteFile(tmpPath, b, 0644); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -135,71 +97,102 @@ func (d *Driver) write(trans Transaction) error {
|
||||||
return os.Rename(tmpPath, finalPath)
|
return os.Rename(tmpPath, finalPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// read does the opposite operation as write. Reading a record from the database
|
// Read a record from the database
|
||||||
// (named by the transaction.resourceID, found in the transaction.Collection), and
|
//
|
||||||
// unmarshaling the data into the transaction.Container
|
// Example:
|
||||||
func (d *Driver) read(trans Transaction) error {
|
//
|
||||||
|
// // read a single fish
|
||||||
|
// Read("/fish/twofish", &fish)
|
||||||
|
//
|
||||||
|
// // read all fish
|
||||||
|
// Read("/fish", &fish)
|
||||||
|
func (d *Driver) Read(path string, v interface{}) error {
|
||||||
|
|
||||||
dir := d.dir + "/" + trans.Collection
|
dir := d.dir + "/" + path
|
||||||
|
|
||||||
// read record from database
|
//
|
||||||
b, err := ioutil.ReadFile(dir + "/" + trans.ResourceID + ".json")
|
fi, err := os.Stat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// unmarshal data into the transaction.Container
|
switch {
|
||||||
return json.Unmarshal(b, trans.Container)
|
|
||||||
}
|
|
||||||
|
|
||||||
// readAll does the same operation as read, reading all the records in the specified
|
// if the path is a directory, attempt to read all entries into v
|
||||||
// transaction.Collection
|
case fi.Mode().IsDir():
|
||||||
func (d *Driver) readAll(trans Transaction) error {
|
|
||||||
|
|
||||||
dir := d.dir + "/" + trans.Collection
|
// read all the files in the transaction.Collection
|
||||||
|
files, err := ioutil.ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
// an error here just means the collection is either empty or doesn't exist
|
||||||
|
}
|
||||||
|
|
||||||
// read all the files in the transaction.Collection
|
// the files read from the database
|
||||||
files, err := ioutil.ReadDir(dir)
|
var f []string
|
||||||
|
|
||||||
if err != nil {
|
// iterate over each of the files, attempting to read the file. If successful
|
||||||
// an error here just means an empty collection so do nothing
|
// append the files to the collection of read files
|
||||||
}
|
for _, file := range files {
|
||||||
|
b, err := ioutil.ReadFile(dir + "/" + file.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// the files read from the database
|
// append read file
|
||||||
var f []string
|
f = append(f, string(b))
|
||||||
|
}
|
||||||
|
|
||||||
// iterate over each of the files, attempting to read the file. If successful
|
// unmarhsal the read files as a comma delimeted byte array
|
||||||
// append the files to the collection of read files
|
return json.Unmarshal([]byte("["+strings.Join(f, ",")+"]"), v)
|
||||||
for _, file := range files {
|
|
||||||
b, err := ioutil.ReadFile(dir + "/" + file.Name())
|
// if the path is a file, attempt to read the single file
|
||||||
|
case !fi.Mode().IsDir():
|
||||||
|
// read record from database
|
||||||
|
b, err := ioutil.ReadFile(dir + ".json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// append read file
|
// unmarshal data into the transaction.Container
|
||||||
f = append(f, string(b))
|
return json.Unmarshal(b, &v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// unmarhsal the read files as a comma delimeted byte array
|
return nil
|
||||||
return json.Unmarshal([]byte("["+strings.Join(f, ",")+"]"), trans.Container)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete locks that database and then proceeds to remove the record (specified by
|
// Delete locks that database and then attempts to remove the collection/resource
|
||||||
// transaction.ResourceID) from the collection
|
// specified by [path]
|
||||||
func (d *Driver) delete(trans Transaction) error {
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// // delete the fish 'redfish.json'
|
||||||
|
// Delete("/fish/redfish")
|
||||||
|
//
|
||||||
|
// // delete the fish collection
|
||||||
|
// Delete("/fish")
|
||||||
|
func (d *Driver) Delete(path string) error {
|
||||||
|
|
||||||
mutex := d.getOrCreateMutex(trans.Collection)
|
mutex := d.getOrCreateMutex(path)
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
defer mutex.Unlock()
|
defer mutex.Unlock()
|
||||||
|
|
||||||
dir := d.dir + "/" + trans.Collection
|
// stat the file to determine if it is a file or dir
|
||||||
|
fi, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// remove record from database
|
switch {
|
||||||
return os.Remove(dir + "/" + trans.ResourceID + ".json")
|
// remove the collection from database
|
||||||
|
case fi.Mode().IsDir():
|
||||||
|
return os.Remove(d.dir + "/" + path)
|
||||||
|
|
||||||
|
// remove the record from database
|
||||||
|
default:
|
||||||
|
return os.Remove(d.dir + "/" + path + ".json")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// helpers
|
|
||||||
|
|
||||||
// getOrCreateMutex creates a new collection specific mutex any time a collection
|
// getOrCreateMutex creates a new collection specific mutex any time a collection
|
||||||
// is being modfied to avoid unsafe operations
|
// is being modfied to avoid unsafe operations
|
||||||
func (d *Driver) getOrCreateMutex(collection string) sync.Mutex {
|
func (d *Driver) getOrCreateMutex(collection string) sync.Mutex {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user