From 7875d65f2aa33e0d47d7073ec94b99ea12ab75f8 Mon Sep 17 00:00:00 2001 From: Josh Baker Date: Tue, 19 Jul 2016 15:15:00 -0700 Subject: [PATCH] initial commit --- .travis.yml | 1 + LICENSE | 20 + README.md | 314 +++++++++++ buntdb.go | 1357 ++++++++++++++++++++++++++++++++++++++++++++++++ buntdb_test.go | 791 ++++++++++++++++++++++++++++ logo.png | Bin 0 -> 95733 bytes 6 files changed, 2483 insertions(+) create mode 100644 .travis.yml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 buntdb.go create mode 100644 buntdb_test.go create mode 100644 logo.png diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..4f2ee4d --- /dev/null +++ b/.travis.yml @@ -0,0 +1 @@ +language: go diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..58f5819 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2016 Josh Baker + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f6f115d --- /dev/null +++ b/README.md @@ -0,0 +1,314 @@ +

+BuntDB +
+Build Status +Code Coverage +GoDoc +Version +

+ +==== + +BuntDB is a low-level, in-memory, key/value store in pure Go. +It persists to disk, is ACID compliant, and uses locking for multiple +readers and a single writer. It supports custom indexes and geospatial +data. It's ideal for projects that need a dependable database and favor +speed over data size. + +The desire to create BuntDB stems from the need for a new embeddable +database for [Tile38](https://github.com/tidwall/tile38). One that can work +both as a performant [Raft Store](https://github.com/tidwall/raft-boltdb), +and a Geospatial database. + +Much of the API is inspired by the fantastic [BoltDB](https://github.com/boltdb/bolt), +an amazing key/value store that can handle terrabytes of data on disk. + +Features +======== + +- In-memory database for [fast reads and writes](https://github.com/tidwall/raft-boltdb#benchmarks) +- Embeddable with a [simple API](https://godoc.org/github.com/tidwall/buntdb) +- [Spatial indexing](#spatial-indexes) for up to 4 dimensions; Useful for Geospatial data +- - Create [custom indexes](#custom-indexes) for any data type +- [Built-in types](#built-in-types) that are easy to get up & running; String, Uint, Int, Float +- Flexible [iteration](#iterating) of data; ascending, descending, and ranges +- Durable append-only file format. Adopts the [Redis AOF](http://redis.io/topics/persistence) process +- Option to evict old items with an [expiration](#data-expiration) TTL +- Tight codebase, under 1K loc using the `cloc` command. +- ACID semantics with locking [transactions](#transactions) that support rollbacks + +Getting Started +=============== + +## Installing + +To start using BuntDB, install Go and run `go get`: + +```sh +$ go get github.com/tidwall/buntdb +``` + +This will retrieve the library. + + +## Opening a database + +The primary object in BuntDB is a `DB`. To open or create your +database, use the `buntdb.Open()` function: + +```go +package main + +import ( + "log" + + "github.com/tidwall/buntdb" +) + +func main() { + // Open the data.db file. It will be created if it doesn't exist. + db, err := buntdb.Open("data.db") + if err != nil { + log.Fatal(err) + } + defer db.Close() + + ... +} +``` + +It's important to note that BuntDB does not currently support file locking, so avoid accessing the database from multiple processes. + +## Transactions +All reads and writes must be performed from inside a transaction. BuntDB can have one write transaction opened at a time, but can have many concurrent read transactions. Each transaction maintains a stable view of the database. In other words, once a transaction has begun, the data for that transaction cannot be changed by other transactions. + +Transactions run in a function that exposes a `Tx` object, which represents the transaction state. While inside a transaction, all database operations should be performed using this object. You should never access the origin `DB` object while inside a transaction. Doing so may have side-effects, such as blocking your application. + +When a transaction fails, it will roll back, and revert all chanages that ocurred to the database during that transaction. There's a single return value that you can use to close the transaction. For read/write transactions, returning an error this way will force the transaction to roll back. When a read/write transaction succeeds all changes are persisted to disk. + +### Read-only Transactions +A read-only transaction should be used when you don't need to make changes to the data. The advantage of a read-only transaction is that there can be many running concurrently. + +```go +err := db.View(func(tx *buntdb.Tx) error { + ... + return nil +}) +``` + +### Read/write Transactions +A read/write transaction is used when you need to make changes to your data. There can only be one read/write transaction running at a time. So make sure you close it as soon as you are done with it. + +```go +err := db.Update(func(tx *buntdb.Tx) error { + ... + return nil +}) +``` + +## Setting and getting key/values + +To set a value you must open a read/write tranasction: + +```go +err := db.Update(func(tx *buntdb.Tx) error { + err := tx.Set("mykey", "myvalue", nil) + return err +}) +``` + + +To get the value: + +```go +err := db.View(func(tx *buntdb.Tx) error { + val, err := tx.Get("mykey") + if err != nil{ + return err + } + fmt.Printf("value is %s\n", val) + return nil +}) +``` + +Getting non-existent values will case an `ErrNotFound` error. + +### Iterating +All keys/value pairs are ordered in the database by the key. To iterate over the keys: + +```go +err := db.View(func(tx *buntdb.Tx) error { +err := tx.Ascend("", func(key, value string) bool{ + fmt.Printf("key: %s, value: %s\n", key, value) + }) + return err +}) +``` + +There is also `AscendGreaterOrEqual`, `AscendLessThan`, `AscendRange`, `Descend`, `DescendLessOrEqual`, `DescendGreaterThan`, and `DescendRange`. Please see the [documentation](https://godoc.org/github.com/tidwall/buntdb) for more information on these functions. + + +## Custom Indexes +Initially all data is stored in a single [B-tree](https://en.wikipedia.org/wiki/B-tree) with each item having one key and one value. All of these items are ordered by the key. This is great for quickly getting a value from a key or [iterating](#iterating) over the keys. + +You can also create custom indexes that allow for ordering and [iterating](#iterating) over values. A custom index also uses a B-tree, but it's more flexible because it allows for custom ordering. + +For example, let's say you want to create an index for ordering names: + +```go +db.CreateIndex("names", "*", buntdb.IndexString) +``` + +This will create an index named `names` which stores and sorts all values. The second parameter is a pattern that is used to filter on keys. A `*` wildcard argument means that we want to accept all keys. `IndexString` is a built-in function that performs case-insensitive ordering on the values + +Now you can add various names: + +```go +db.Update(func(tx *buntdb.Tx) error { + tx.Set("user:0:name", "tom", nil) + tx.Set("user:1:name", "Randi", nil) + tx.Set("user:2:name", "jane", nil) + tx.Set("user:4:name", "Janet", nil) + tx.Set("user:5:name", "Paula", nil) + tx.Set("user:6:name", "peter", nil) + tx.Set("user:7:name", "Terri", nil) + return nil +}) +``` + +Finally you can iterate over the index: + +```go +db.View(func(tx *buntdb.Tx) error { + tx.Ascend("names", func(key, val string) bool { + fmt.Printf(buf, "%s %s\n", key, val) + return true + }) + return nil +}) +``` +The output should be: +``` +user:2:name jane +user:4:name Janet +user:5:name Paula +user:6:name peter +user:1:name Randi +user:7:name Terri +user:0:name tom +``` + +The pattern parameter can be used to filter on keys like this: + +```go +db.CreateIndex("names", "user:*", buntdb.IndexString) +``` + +Now only items with keys that have the prefix `user:` will be added to the `names` index. + + +### Built-in types +Along with `IndexString`, there is also `IndexInt`, `IndexUint`, and `IndexFloat`. +These are built-in types for indexing. You can choose to use these or create your own. + +So to create an index that is numerically ordered on an age key, we could use: + +```go +db.CreateIndex("ages", "user:*:age", buntdb.IndexInt) +``` + +And then add values: + +```go +db.Update(func(tx *buntdb.Tx) error { + tx.Set("user:0:age", "35", nil) + tx.Set("user:1:age", "49", nil) + tx.Set("user:2:age", "13", nil) + tx.Set("user:4:age", "63", nil) + tx.Set("user:5:age", "8", nil) + tx.Set("user:6:age", "3", nil) + tx.Set("user:7:age", "16", nil) + return nil +}) +``` + +```go +db.View(func(tx *buntdb.Tx) error { + tx.Ascend("ages", func(key, val string) bool { + fmt.Printf(buf, "%s %s\n", key, val) + return true + }) + return nil +}) +``` + +The output should be: +``` +user:6:name 3 +user:5:name 8 +user:2:name 13 +user:7:name 16 +user:0:name 35 +user:1:name 49 +user:4:name 63 +``` + +### Spatial Indexes +BuntDB has support for spatial indexes by storing rectangles in an [R-tree](https://en.wikipedia.org/wiki/R-tree). An R-tree is organized in a similar manner as a [B-tree](https://en.wikipedia.org/wiki/B-tree), and both are balanaced trees. But, an R-tree is special because it can operate on data that is in multiple dimensions. This is super handy for Geospatial applications. + +To create a spatial index use the `CreateSpatialIndex` function: + +```go +db.CreateSpatialIndex("fleet", "fleet:*:pos", buntdb.IndexRect) +``` + +Then `IndexRect` is a built-in function that converts rect strings to a format that the R-tree can use. It's easy to use this function out of the box, but you might find it better to create a custom one that renders from a different format, such as [Well-known text](https://en.wikipedia.org/wiki/Well-known_text) or [GeoJSON](http://geojson.org/). + +To add some lon,lat points to the `fleet` index: + +```go +db.Update(func(tx *buntdb.Tx) error { + tx.Set("fleet:0:pos", "[-115.567 33.532]", nil) + tx.Set("fleet:1:pos", "[-116.671 35.735]", nil) + tx.Set("fleet:2:pos", "[-113.902 31.234]", nil) + return nil +}) +``` + +And then you can run the `Intersects` function on the index: + +```go +db.View(func(tx *buntdb.Tx) error { + tx.Intersects("fleet", "[-117 30],[-112 36]", func(key, val string) bool { + ... + return true + }) + return nil +}) +``` + +This will get all three positions. + +The bracket syntax `[-117 30],[-112 36]` is unique to BuntDB, and it's how the built-in rectangles are processed, but you are not limited to this syntax. Whatever Rect function you choose to use during `CreateSpatialIndex` will be used to process the paramater, in this case it's `IndexRect`. + + +### Data Expiration +Items can be automatically evicted by using the `SetOptions` object in the `Set` function to set a `TTL`. + +```go +db.Update(func(tx *buntdb.Tx) error { + tx.Set("mykey", "myval", &buntdb.SetOptions{Expires:true, TTL:time.Second}) + return nil +}) +``` + +Now `mykey` will automatically be deleted after one second. You can remove the TTL by setting the value again with the same key/value, but with the options parameter set to nil. + +## Contact +Josh Baker [@tidwall](http://twitter.com/tidwall) + +## License + +BuntDB source code is available under the MIT [License](/LICENSE). diff --git a/buntdb.go b/buntdb.go new file mode 100644 index 0000000..4d25b5c --- /dev/null +++ b/buntdb.go @@ -0,0 +1,1357 @@ +// Package buntdb implements a low-level in-memory key/value store in pure Go. +// It persists to disk, is ACID compliant, and uses locking for multiple +// readers and a single writer. Bunt is ideal for projects that need +// a dependable database, and favor speed over data size. +package buntdb + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "os" + "sort" + "strconv" + "strings" + "sync" + "time" + + "github.com/tidwall/btree" + "github.com/tidwall/rtree" +) + +var ( + // ErrTxNotWritable is returned when performing a write operation on a + // read-only transaction. + ErrTxNotWritable = errors.New("tx not writable") + // ErrTxClosed is returned when committing or rolling back a transaction + // that has already been committed or rolled back. + ErrTxClosed = errors.New("tx closed") + // ErrNotFound is returned when an item or index is not in the database. + ErrNotFound = errors.New("not found") + // ErrInvalid is returned when the database file is an invalid format. + ErrInvalid = errors.New("invalid database") + // ErrDatabaseClosed is returned when the database is closed. + ErrDatabaseClosed = errors.New("database closed") + // ErrIndexExists is returned when an index already exists in the database. + ErrIndexExists = errors.New("index exists") + // ErrInvalidOperation is returned when an operation cannot be completed. + ErrInvalidOperation = errors.New("invalid operation") +) + +// Iterator allows callers of Ascend* or Descend* to iterate in-order +// over portions of an index. When this function returns false, iteration +// will stop and the associated Ascend* or Descend* function will immediately +// return. +type Iterator func(key, val string) bool + +// DB represents a collection of key-value pairs that persist on disk. +// Transactions are used for all forms of data access to the DB. +type DB struct { + mu sync.RWMutex // the gatekeeper for all fields + file *os.File // the underlying file + bufw *bufio.Writer // only write to this + keys *btree.BTree // a tree of all item ordered by key + exps *btree.BTree // a tree of items ordered by expiration + idxs map[string]*index // the index trees. + exmgr bool // indicates that expires manager is running. + flushes int // a count of the number of disk flushes + closed bool // set when the database has been closed + aoflen int // the number of lines in the aof file +} + +// exctx is a simple b-tree context for ordering by expiration. +type exctx struct { + db *DB +} + +// Open opens a database at the provided path. +// If the file does not exist then it will be created automatically. +func Open(path string) (*DB, error) { + db := &DB{} + db.keys = btree.New(16, nil) + db.exps = btree.New(16, &exctx{db}) + db.idxs = make(map[string]*index) + var err error + // Hardcoding 0666 as the default mode. + db.file, err = os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0666) + if err != nil { + return nil, err + } + if err := db.load(); err != nil { + db.file.Close() + return nil, err + } + db.bufw = bufio.NewWriter(db.file) + // start the background manager. + go db.backgroundManager() + return db, nil +} + +// Close releases all database resources. +// All transactions must be closed before closing the database. +func (db *DB) Close() error { + db.mu.Lock() + defer db.mu.Unlock() + if db.closed { + return ErrDatabaseClosed + } + db.closed = true + return db.file.Close() +} + +// index represents a b-tree or r-tree index and also acts as the +// b-tree/r-tree context for itself. +type index struct { + btr *btree.BTree // contains the items + rtr *rtree.RTree // contains the items + name string // name of the index + pattern string // a required key pattern + less func(a, b string) bool // less comparison function + rect func(item string) (min, max []float64) // rect from string function + db *DB // the origin database +} + +// CreateIndex builds a new index and populates it with items. +// The items are ordered in an b-tree and can be retrieved using the +// Ascend* and Descend* methods. +// An error will occur if an index with the same name already exists. +// +// When a pattern is provided, the index will be populated with +// keys that match the specified pattern. +// The less function compares if string 'a' is less than string 'b'. +// It allows for indexes to create custom ordering. It's possible +// that the strings may be textual or binary. It's up to the provided +// less function to handle the content format and comparison. +// There are some default less function that can be used such as +// IndexString, IndexBinary, etc. +func (db *DB) CreateIndex(name, pattern string, + less func(a, b string) bool) error { + return db.createIndex(name, pattern, less, nil) +} + +// CreateSpatialIndex builds a new index and populates it with items. +// The items are organized in an r-tree and can be retrieved using the +// Intersects method. +// An error will occur if an index with the same name already exists. +// +// The rect function converts a string to a rectangle. The rectangle is +// represented by two arrays, min and max. Both arrays may have a length +// between 1 and 4, and both arrays must match in length. A length of 1 is a +// one dimensional rectangle, and a length of 4 is a four dimension rectangle. +// The values of min must be less than the values of max at the same dimension. +// Thus min[0] must be less-than-or-equal-to max[0]. +// The IndexRect is a default function that can be used for the rect +// parameter. +func (db *DB) CreateSpatialIndex(name, pattern string, + rect func(item string) (min, max []float64)) error { + return db.createIndex(name, pattern, nil, rect) +} + +// createIndex is called by CreateIndex() and CreateSpatialIndex() +func (db *DB) createIndex( + name string, + pattern string, + less func(a, b string) bool, + rect func(item string) (min, max []float64), +) error { + if name == "" { + return ErrIndexExists + } + db.mu.Lock() + defer db.mu.Unlock() + if db.closed { + return ErrDatabaseClosed + } + if _, ok := db.idxs[name]; ok { + return ErrIndexExists + } + idx := &index{ + name: name, + pattern: pattern, + less: less, + rect: rect, + db: db, + } + if less != nil { + idx.btr = btree.New(16, idx) + } + if rect != nil { + idx.rtr = rtree.New(idx) + } + db.keys.Ascend(func(item btree.Item) bool { + dbi := item.(*dbItem) + if !wildcardMatch(dbi.key, idx.pattern) { + return true + } + if less != nil { + idx.btr.ReplaceOrInsert(dbi) + } + if rect != nil { + idx.rtr.Insert(dbi) + } + return true + }) + db.idxs[name] = idx + return nil +} + +// wilcardMatch returns true if str matches pattern. This is a very +// simple wildcard match where '*' matches on any number characters +// and '?' matches on any one character. +func wildcardMatch(str, pattern string) bool { + if pattern == "*" { + return true + } + return deepMatch(str, pattern) +} +func deepMatch(str, pattern string) bool { + for len(pattern) > 0 { + switch pattern[0] { + default: + if len(str) == 0 || str[0] != pattern[0] { + return false + } + case '?': + if len(str) == 0 { + return false + } + case '*': + return wildcardMatch(str, pattern[1:]) || + (len(str) > 0 && wildcardMatch(str[1:], pattern)) + } + str = str[1:] + pattern = pattern[1:] + } + return len(str) == 0 && len(pattern) == 0 +} + +// DropIndex removes an index. +func (db *DB) DropIndex(name string) error { + if name == "" { + return ErrInvalidOperation + } + db.mu.Lock() + defer db.mu.Unlock() + if db.closed { + return ErrDatabaseClosed + } + if _, ok := db.idxs[name]; !ok { + return ErrNotFound + } + delete(db.idxs, name) + return nil +} + +// Indexes returns a list of index names. +func (db *DB) Indexes() ([]string, error) { + db.mu.RLock() + defer db.mu.RUnlock() + if db.closed { + return nil, ErrDatabaseClosed + } + names := make([]string, 0, len(db.idxs)) + for name := range db.idxs { + names = append(names, name) + } + sort.Strings(names) + return names, nil +} + +// insertIntoDatabase performs inserts an item in to the database and updates +// all indexes. If a previous item with the same key already exists, that item +// will be replaced with the new one, and return the previous item. +func (db *DB) insertIntoDatabase(item *dbItem) *dbItem { + var pdbi *dbItem + prev := db.keys.ReplaceOrInsert(item) + if prev != nil { + // A previous item was removed from the keys tree. Let's + // fully delete this item from all indexes. + pdbi = prev.(*dbItem) + if pdbi.opts != nil && pdbi.opts.ex { + // Remove it from the exipres tree. + db.exps.Delete(pdbi) + } + for _, idx := range db.idxs { + if idx.btr != nil { + // Remove it from the btree index. + idx.btr.Delete(pdbi) + } + if idx.rtr != nil { + // Remove it from the rtree index. + idx.rtr.Remove(pdbi) + } + } + } + if item.opts != nil && item.opts.ex { + // The new item has eviction options. Add it to the + // expires tree + db.exps.ReplaceOrInsert(item) + } + for _, idx := range db.idxs { + if !wildcardMatch(item.key, idx.pattern) { + continue + } + if idx.btr != nil { + // Add new item to btree index. + idx.btr.ReplaceOrInsert(item) + } + if idx.rtr != nil { + // Add new item to rtree index. + idx.rtr.Insert(item) + } + } + // we must return the previous item to the caller. + return pdbi +} + +// deleteFromDatabase removes and item from the database and indexes. The input +// item must only have the key field specified thus "&dbItem{key: key}" is all +// that is needed to fully remove the item with the matching key. If an item +// with the matching key was found in the database, it will be removed and +// returned to the caller. A nil return value means that the item was not +// found in the database +func (db *DB) deleteFromDatabase(item *dbItem) *dbItem { + var pdbi *dbItem + prev := db.keys.Delete(item) + if prev != nil { + pdbi = prev.(*dbItem) + if pdbi.opts != nil && pdbi.opts.ex { + // Remove it from the exipres tree. + db.exps.Delete(pdbi) + } + for _, idx := range db.idxs { + if idx.btr != nil { + // Remove it from the btree index. + idx.btr.Delete(pdbi) + } + if idx.rtr != nil { + // Remove it from the rtree index. + idx.rtr.Remove(pdbi) + } + } + } + return pdbi +} + +// backgroundManager runs continuously in the background and performs various +// operations such as removing expired items and syncing to disk. +func (db *DB) backgroundManager() { + flushes := 0 + t := time.NewTicker(time.Second) + defer t.Stop() + for range t.C { + stop := false + multiple := 0 + // Open a standard view. This will take a full lock of the + // database thus allowing for access to anything we need. + db.Update(func(tx *Tx) error { + if db.closed { + // the manager has stopped. exit now. + stop = true + return nil + } + if db.keys.Len() > 0 { + multiple = db.aoflen / db.keys.Len() + } + // produce a list of expired items that need removing + var remove []*dbItem + db.exps.AscendLessThan(&dbItem{ + opts: &dbItemOpts{ex: true, exat: time.Now()}, + }, func(item btree.Item) bool { + remove = append(remove, item.(*dbItem)) + return true + }) + for _, item := range remove { + if _, err := tx.Delete(item.key); err != nil { + // it's ok to get a "not found" because the + // 'Delete' method reports "not found" for + // expired items. + if err != ErrNotFound { + return err + } + } + } + // execute a disk sync. + if flushes != db.flushes { + db.file.Sync() + flushes = db.flushes + } + return nil + }) + if multiple > 4 { + db.Shrink() + } + if stop { + break + } + } +} +func (db *DB) Shrink() error { + db.mu.Lock() + if db.closed { + db.mu.Unlock() + return ErrDatabaseClosed + } + fname := db.file.Name() + tmpname := fname + ".tmp" + // the endpos is used to return to the end of the file when we are + // finished writing all of the current items. + aoflen := db.aoflen + endpos, err := db.file.Seek(0, 2) + if err != nil { + return err + } + db.mu.Unlock() + f, err := os.Create(tmpname) + if err != nil { + return err + } + defer func() { + f.Close() + os.RemoveAll(tmpname) + }() + + // we are going to read items in as chunks as to not hold up the database + // for too long. + naoflen := 0 + wr := bufio.NewWriter(f) + pivot := "" + done := false + for !done { + err := func() error { + db.mu.RLock() + defer db.mu.RUnlock() + if db.closed { + return ErrDatabaseClosed + } + n := 0 + done = true + db.keys.AscendGreaterOrEqual(&dbItem{key: pivot}, + func(item btree.Item) bool { + dbi := item.(*dbItem) + if n > 100 { + pivot = dbi.key + done = false + return false + } + dbi.writeSetTo(wr) + naoflen++ + n++ + return true + }, + ) + if err := wr.Flush(); err != nil { + return err + } + return nil + }() + if err != nil { + return err + } + } + // We reached this far so all of the items have been written to a new tmp + // There's some more work to do by appending the new line from the aof + // to the tmp file and finally swap the files out. + err = func() error { + // We're wrapping this in a function to get the benefit of a defered + // lock/unlock. + db.mu.Lock() + defer db.mu.Unlock() + // We are going to open a new version of the aof file so that we do + // not change the seek position of the previous. This may cause a + // problem in the future if we choose to use syscall file locking. + aof, err := os.Open(fname) + if err != nil { + return err + } + defer aof.Close() + if _, err := aof.Seek(endpos, 0); err != nil { + return err + } + // Just copy all of the new commands that have occured since we + // started the shrink process. + if _, err := io.Copy(f, aof); err != nil { + return err + } + // Close all files + if err := aof.Close(); err != nil { + return err + } + if err := f.Close(); err != nil { + return err + } + if err := db.file.Close(); err != nil { + return err + } + // Anything failures below here is really bad. So just panic. + if err := os.Rename(tmpname, fname); err != nil { + panic(err) + } + db.file, err = os.OpenFile(fname, os.O_CREATE|os.O_RDWR, 0666) + if err != nil { + panic(err) + } + if _, err := db.file.Seek(0, 2); err != nil { + return err + } + // reset the bufio writer + db.bufw = bufio.NewWriter(db.file) + // finally update the aoflen + db.aoflen = naoflen + (db.aoflen - aoflen) + return nil + }() + return err +} +func loadReadLine(r *bufio.Reader) (string, error) { + line, err := r.ReadBytes('\n') + if err != nil { + return "", err + } + if len(line) < 2 || line[len(line)-2] != '\r' { + return "", ErrInvalid + } + return string(line[:len(line)-2]), nil +} +func loadReadLineNum(r *bufio.Reader) (int, error) { + line, err := loadReadLine(r) + if err != nil { + return 0, err + } + n, err := strconv.ParseUint(line, 10, 64) + if err != nil { + return 0, err + } + return int(n), nil +} + +var errValidEOF = errors.New("valid eof") + +func loadReadCommand(r *bufio.Reader) ([]string, error) { + c, err := r.ReadByte() + if err != nil { + if err == io.EOF { + return nil, errValidEOF + } + return nil, err + } + if c != '*' { + return nil, ErrInvalid + } + n, err := loadReadLineNum(r) + if err != nil { + return nil, err + } + parts := make([]string, n) + for i := 0; i < len(parts); i++ { + c, err := r.ReadByte() + if err != nil { + return nil, err + } + if c != '$' { + return nil, ErrInvalid + } + n, err := loadReadLineNum(r) + if err != nil { + return nil, err + } + data := make([]byte, n) + if _, err = io.ReadFull(r, data); err != nil { + return nil, err + } + eol := make([]byte, 2) + if _, err = io.ReadFull(r, eol); err != nil { + return nil, err + } + if eol[0] != '\r' || eol[1] != '\n' { + return nil, ErrInvalid + } + parts[i] = string(data) + } + return parts, nil +} + +// load reads entries from the append only database file and fills the database. +// The file format uses the Redis append only file format, which is and a series +// of RESP commands. For more information on RESP please read +// http://redis.io/topics/protocol. The only supported RESP commands are DEL and +// SET. +func (db *DB) load() error { + r := bufio.NewReader(db.file) + for { + var item = &dbItem{} + parts, err := loadReadCommand(r) + if err != nil { + if err == errValidEOF { + break + } + if err == io.EOF { + return io.ErrUnexpectedEOF + } + return err + } + if len(parts) == 0 { + continue + } + db.aoflen++ + switch strings.ToLower(parts[0]) { + default: + return ErrInvalid + case "set": + if len(parts) < 3 || len(parts) == 4 || len(parts) > 5 { + return ErrInvalid + } + item.key, item.val = parts[1], parts[2] + if len(parts) == 5 { + if strings.ToLower(parts[3]) != "ex" { + return ErrInvalid + } + ex, err := strconv.ParseInt(parts[4], 10, 64) + if err != nil { + return err + } + dur := time.Duration(ex) * time.Second + item.opts = &dbItemOpts{ + ex: true, + exat: time.Now().Add(dur), + } + } + db.insertIntoDatabase(item) + case "del": + if len(parts) != 2 { + return ErrInvalid + } + item.key = parts[1] + db.deleteFromDatabase(item) + } + } + return nil +} + +// managed calls a block of code that is fully contained in a transaction. +// This method is intended to be wrapped by Update and View +func (db *DB) managed(writable bool, fn func(tx *Tx) error) (err error) { + var tx *Tx + tx, err = db.begin(writable) + if err != nil { + return + } + defer func() { + if err != nil { + // The caller returned an error. We must rollback. + tx.rollback() + return + } + if writable { + // Everything went well. Lets Commit() + err = tx.commit() + } else { + // read-only transaction can only roll back. + err = tx.rollback() + } + }() + tx.funcd = true + defer func() { + tx.funcd = false + }() + err = fn(tx) + return +} + +// View executes a function within a managed read-only transaction. +// When a non-nil error is returned from the function that error will be return +// to the caller of View(). +// +// Executing a manual commit or rollback from inside the function will result +// in a panic. +func (db *DB) View(fn func(tx *Tx) error) error { + return db.managed(false, fn) +} + +// Update executes a function within a managed read/write transaction. +// The transaction has been committed when no error is returned. +// In the event that an error is returned, the transaction will be rolled back. +// When a non-nil error is returned from the function, the transaction will be +// rolled back and the that error will be return to the caller of Update(). +// +// Executing a manual commit or rollback from inside the function will result +// in a panic. +func (db *DB) Update(fn func(tx *Tx) error) error { + return db.managed(true, fn) +} + +// Tx represents a transaction on the database. This transaction can either be +// read-only or read/write. Read-only transactions can be used for retrieving +// values for keys and iterating through keys and values. Read/write +// transactions can set and delete keys. +// +// All transactions must be committed or rolled-back when done. +type Tx struct { + db *DB // the underlying database. + writable bool // when false mutable operations fail. + funcd bool // when true Commit and Rollback panic. + rollbacks map[string]*dbItem // cotnains details for rolling back tx. + commits map[string]*dbItem // contains details for committing tx. +} + +// begin opens a new transaction. +// Multiple read-only transactions can be opened at the same time but there can +// only be one read/write transaction at a time. Attempting to open a read/write +// transactions while another one is in progress will result in blocking until +// the current read/write transaction is completed. +// +// All transactions must be closed by calling Commit() or Rollback() when done. +func (db *DB) begin(writable bool) (*Tx, error) { + tx := &Tx{ + db: db, + writable: writable, + } + if writable { + tx.rollbacks = make(map[string]*dbItem) + tx.commits = make(map[string]*dbItem) + } + tx.lock() + if db.closed { + tx.unlock() + return nil, ErrDatabaseClosed + } + return tx, nil +} + +// lock locks the database based on the transaction type. +func (tx *Tx) lock() { + if tx.writable { + tx.db.mu.Lock() + } else { + tx.db.mu.RLock() + } +} + +// unlock unlocks the database based on the transaction type. +func (tx *Tx) unlock() { + if tx.writable { + tx.db.mu.Unlock() + } else { + tx.db.mu.RUnlock() + } +} + +// rollbackInner handles the underlying rollback logic. +// Intended to be called from Commit() and Rollback(). +func (tx *Tx) rollbackInner() { + for key, item := range tx.rollbacks { + tx.db.deleteFromDatabase(&dbItem{key: key}) + if item != nil { + // When an item is not nil, we will need to reinsert that item + // into the database overwriting the current one. + tx.db.insertIntoDatabase(item) + } + } +} + +// commit writes all changes to disk. +// An error is returned when a write error occurs, or when a Commit() is called +// from a read-only transaction. +func (tx *Tx) commit() error { + if tx.funcd { + panic("managed tx commit not allowed") + } + if tx.db == nil { + return ErrTxClosed + } else if !tx.writable { + return ErrTxNotWritable + } + var err error + if len(tx.commits) > 0 { + // Each committed record is written to disk + for key, item := range tx.commits { + if item == nil { + (&dbItem{key: key}).writeDeleteTo(tx.db.bufw) + } else { + item.writeSetTo(tx.db.bufw) + } + } + // Flushing the buffer only once per transaction. + // If this operation fails then the write did failed and we must + // rollback. + if err = tx.db.bufw.Flush(); err != nil { + tx.rollbackInner() + } + // Increment the number of flushes. The background syncing uses this. + tx.db.flushes++ + tx.db.aoflen += len(tx.commits) + } + // Unlock the database and allow for another writable transaction. + tx.unlock() + // Clear the db field to disable this transaction from future use. + tx.db = nil + return err +} + +// rollback closes the transaction and reverts all mutable operations that +// were performed on the transaction such as Set() and Delete(). +// +// Read-only transactions can only be rolled back, not committed. +func (tx *Tx) rollback() error { + if tx.funcd { + panic("managed tx rollback not allowed") + } + if tx.db == nil { + return ErrTxClosed + } + // The rollback func does the heavy lifting. + if tx.writable { + tx.rollbackInner() + } + // unlock the database for more transactions. + tx.unlock() + // Clear the db field to disable this transaction from future use. + tx.db = nil + return nil +} + +// dbItemOpts holds various meta information about an item. +type dbItemOpts struct { + ex bool // does this item expire? + exat time.Time // when does this item expire? +} +type dbItem struct { + key, val string // the binary key and value + opts *dbItemOpts // optional meta information +} + +// writeSetTo writes an item as a single SET record to the a bufio Writer. +func (dbi *dbItem) writeSetTo(wr *bufio.Writer) { + if dbi.opts != nil && dbi.opts.ex { + ex := strconv.FormatUint( + uint64(dbi.opts.exat.Sub(time.Now())/time.Second), + 10, + ) + fmt.Fprintf(wr, + "*5\r\n$%d\r\n%s\r\n$%d\r\n%s\r\n"+ + "$%d\r\n%s\r\n$%d\r\n%s\r\n$%d\r\n%s\r\n", + len("set"), "set", len(dbi.key), dbi.key, + len(dbi.val), dbi.val, len("ex"), "ex", len(ex), ex, + ) + } else { + fmt.Fprintf(wr, "*3\r\n$%d\r\n%s\r\n$%d\r\n%s\r\n$%d\r\n%s\r\n", + len("set"), "set", len(dbi.key), dbi.key, len(dbi.val), dbi.val, + ) + } +} + +// writeSetTo writes an item as a single DEL record to the a bufio Writer. +func (dbi *dbItem) writeDeleteTo(wr *bufio.Writer) { + fmt.Fprintf(wr, + "*2\r\n$%d\r\n%s\r\n$%d\r\n%s\r\n", + len("del"), "del", len(dbi.key), dbi.key) +} + +// expired evaluates id the item has expired. This will always return false when +// the item does not have `opts.ex` set to true. +func (dbi *dbItem) expired() bool { + return dbi.opts != nil && dbi.opts.ex && time.Now().After(dbi.opts.exat) +} + +// MaxTime from http://stackoverflow.com/questions/25065055#32620397 +// This is a long time in the future. It's an imaginary number that is +// used for b-tree ordering. +var maxTime = time.Unix(1<<63-62135596801, 999999999) + +// expiresAt will return the time when the item will expire. When an item does +// not expire `maxTime` is used. +func (dbi *dbItem) expiresAt() time.Time { + if dbi.opts == nil || !dbi.opts.ex { + return maxTime + } + return dbi.opts.exat +} + +// Less determines if a b-tree item is less than another. This is required +// for ordering, inserting, and deleting items from a b-tree. It's important +// to note that the ctx parameter is used to help with determine which +// formula to use on an item. Each b-tree should use a different ctx when +// sharing the same item. +func (dbi *dbItem) Less(item btree.Item, ctx interface{}) bool { + dbi2 := item.(*dbItem) + switch ctx := ctx.(type) { + case *exctx: + // The expires b-tree formula + if dbi2.expiresAt().After(dbi.expiresAt()) { + return true + } + if dbi.expiresAt().After(dbi2.expiresAt()) { + return false + } + case *index: + if ctx.less != nil { + // Using an index + if ctx.less(dbi.val, dbi2.val) { + return true + } + if ctx.less(dbi2.val, dbi.val) { + return false + } + } + } + // Always fall back to the key comparison. This creates absolute uniqueness. + return dbi.key < dbi2.key +} + +// Rect converts a string to a rectangle. +// An invalid rectangle will cause a panic. +func (dbi *dbItem) Rect(ctx interface{}) (min, max []float64) { + switch ctx := ctx.(type) { + case *index: + return ctx.rect(dbi.val) + } + return nil, nil +} + +// SetOptions represents options that may be included with the Set() command. +type SetOptions struct { + // Expires indicates that the Set() key-value will expire + Expires bool + // TTL is how much time the key-value will exist in the database + // before being evicted. The Expires field must also be set to true. + // TTL stands for Time-To-Live. + TTL time.Duration +} + +// Set inserts or replaces an item in the database based on the key. +// The opt params may be used for addtional functionality such as forcing +// the item to be evicted at a specified time. When the return value +// for err is nil the operation succeded. When the return value of +// replaced is true, then the operaton replaced an existing item whose +// value will be returned through the previousValue variable. +// The results of this operation will not be available to other +// transactions until the current transaction has successfully commited. +func (tx *Tx) Set(key, value string, opts *SetOptions) (previousValue string, + replaced bool, err error) { + if tx.db == nil { + return "", false, ErrTxClosed + } else if !tx.writable { + return "", false, ErrTxNotWritable + } + item := &dbItem{key: key, val: value} + if opts != nil { + if opts.Expires { + // The caller is requesting that this item expires. Convert the + // TTL to an absolute time and bind it to the item. + item.opts = &dbItemOpts{ex: true, exat: time.Now().Add(opts.TTL)} + } + } + // Insert the item into the keys tree. + prev := tx.db.insertIntoDatabase(item) + if prev == nil { + // An item with the same key did not previously exist. Let's create a + // rollback entry with a nil value. A nil value indicates that the + // entry should be deleted on rollback. When the value is *not* nil, + // that means the entry should be reverted. + tx.rollbacks[key] = nil + } else { + // A previous item already exists in the database. Let's create a + // rollback entry with the item as the value. We need to check the map + // to see if there isn't already an item that matches the same key. + if _, ok := tx.rollbacks[key]; !ok { + tx.rollbacks[key] = prev + } + if !item.expired() { + previousValue, replaced = item.val, true + } + } + // For commits we simply assign the item to the map. We use this map to + // write the entry to disk. + tx.commits[key] = item + return previousValue, replaced, nil +} + +// Get returns a value for a key. If the item does not exist or if the item +// has expired then ErrNotFound is returned. +func (tx *Tx) Get(key string) (val string, err error) { + if tx.db == nil { + return "", ErrTxClosed + } + prev := tx.db.keys.Get(&dbItem{key: key}) + if prev == nil { + return "", ErrNotFound + } + item := prev.(*dbItem) + if item.expired() { + // The item exists in the tree, but has expired. Let's assume that + // the caller is only interested in items that have not expired. + return "", ErrNotFound + } + return item.val, nil +} + +// Delete removes an item from the database based on the item's key. If the item +// does not exist or if the item has expired then ErrNotFound is returned. +// +// Only writable transaction can be used for Delete() calls. +func (tx *Tx) Delete(key string) (val string, err error) { + if tx.db == nil { + return "", ErrTxClosed + } else if !tx.writable { + return "", ErrTxNotWritable + } + item := tx.db.deleteFromDatabase(&dbItem{key: key}) + if item == nil { + return "", ErrNotFound + } + if _, ok := tx.rollbacks[key]; !ok { + tx.rollbacks[key] = item + } + tx.commits[key] = nil + // Even though the item has been deleted, we still want to check + // if it has expired. An expired item should not be returned. + if item.expired() { + // The item exists in the tree, but has expired. Let's assume that + // the caller is only interested in items that have not expired. + return "", ErrNotFound + } + return item.val, nil +} + +// scan iterates through a specified index and calls user-defined iterator +// function for each item encountered. +// The desc param indicates that the iterator should descend. +// The gt param indicates that there is a greaterThan limit. +// The lt param indicates that there is a lessThan limit. +// The index param tells the scanner to use the specified index tree. An +// empty string for the index means to scan the keys, not the values. +// The start and stop params are the greaterThan, lessThan limits. For +// descending order, these will be lessThan, greaterThan. +// An error will be returned if the tx is closed or the index is not found. +func (tx *Tx) scan( + desc, gt, lt bool, index, start, stop string, iterator Iterator, +) error { + if tx.db == nil { + return ErrTxClosed + } + // wrap a btree specific iterator around the user-defined iterator. + iter := func(item btree.Item) bool { + dbi := item.(*dbItem) + return iterator(dbi.key, dbi.val) + } + var tr *btree.BTree + if index == "" { + // empty index means we will use the keys tree. + tr = tx.db.keys + } else { + idx := tx.db.idxs[index] + if idx == nil { + // index was not found. return error + return ErrNotFound + } + tr = idx.btr + if tr == nil { + return nil + } + } + // create some limit items + var itemA, itemB *dbItem + if gt || lt { + itemA = &dbItem{key: start} + itemB = &dbItem{key: stop} + } + // execute the scan on the underlying tree. + if desc { + if gt { + if lt { + tr.DescendRange(itemA, itemB, iter) + } else { + tr.DescendGreaterThan(itemA, iter) + } + } else if lt { + tr.DescendLessOrEqual(itemA, iter) + } else { + tr.Descend(iter) + } + } else { + if gt { + if lt { + tr.AscendRange(itemA, itemB, iter) + } else { + tr.AscendGreaterOrEqual(itemA, iter) + } + } else if lt { + tr.AscendLessThan(itemA, iter) + } else { + tr.Ascend(iter) + } + } + return nil +} + +// Ascend calls the iterator for every item in the database within the range +// [first, last], until iterator returns false. +// When an index is provided, the results will be ordered by the item values +// as specified by the less() function of the defined index. +// When an index is not provided, the results will be ordered by the item key. +// An invalid index will return an error. +func (tx *Tx) Ascend(index string, iterator Iterator) error { + return tx.scan(false, false, false, index, "", "", iterator) +} + +// AscendGreaterOrEqual calls the iterator for every item in the database within +// the range [pivot, last], until iterator returns false. +// When an index is provided, the results will be ordered by the item values +// as specified by the less() function of the defined index. +// When an index is not provided, the results will be ordered by the item key. +// An invalid index will return an error. +func (tx *Tx) AscendGreaterOrEqual( + index, pivot string, iterator Iterator, +) error { + return tx.scan(false, true, false, index, pivot, "", iterator) +} + +// AscendLessThan calls the iterator for every item in the database within the +// range [first, pivot), until iterator returns false. +// When an index is provided, the results will be ordered by the item values +// as specified by the less() function of the defined index. +// When an index is not provided, the results will be ordered by the item key. +// An invalid index will return an error. +func (tx *Tx) AscendLessThan(index, pivot string, iterator Iterator) error { + return tx.scan(false, false, true, index, pivot, "", iterator) +} + +// AscendRange calls the iterator for every item in the database within +// the range [greaterOrEqual, lessThan), until iterator returns false. +// When an index is provided, the results will be ordered by the item values +// as specified by the less() function of the defined index. +// When an index is not provided, the results will be ordered by the item key. +// An invalid index will return an error. +func (tx *Tx) AscendRange(index, greaterOrEqual, lessThan string, + iterator Iterator) error { + return tx.scan( + false, true, true, index, greaterOrEqual, lessThan, iterator, + ) +} + +// Descend calls the iterator for every item in the database within the range +// [last, first], until iterator returns false. +// When an index is provided, the results will be ordered by the item values +// as specified by the less() function of the defined index. +// When an index is not provided, the results will be ordered by the item key. +// An invalid index will return an error. +func (tx *Tx) Descend(index string, iterator Iterator) error { + return tx.scan(true, false, false, index, "", "", iterator) +} + +// DescendGreaterThan calls the iterator for every item in the database within +// the range [last, pivot), until iterator returns false. +// When an index is provided, the results will be ordered by the item values +// as specified by the less() function of the defined index. +// When an index is not provided, the results will be ordered by the item key. +// An invalid index will return an error. +func (tx *Tx) DescendGreaterThan(index, pivot string, iterator Iterator) error { + return tx.scan(true, true, false, index, pivot, "", iterator) +} + +// DescendLessOrEqual calls the iterator for every item in the database within +// the range [pivot, first], until iterator returns false. +// When an index is provided, the results will be ordered by the item values +// as specified by the less() function of the defined index. +// When an index is not provided, the results will be ordered by the item key. +// An invalid index will return an error. +func (tx *Tx) DescendLessOrEqual(index, pivot string, iterator Iterator) error { + return tx.scan(true, false, true, index, pivot, "", iterator) +} + +// DescendRange calls the iterator for every item in the database within +// the range [lessOrEqual, greaterThan), until iterator returns false. +// When an index is provided, the results will be ordered by the item values +// as specified by the less() function of the defined index. +// When an index is not provided, the results will be ordered by the item key. +// An invalid index will return an error. +func (tx *Tx) DescendRange(index, lessOrEqual, greaterThan string, + iterator Iterator) error { + return tx.scan( + true, true, true, index, lessOrEqual, greaterThan, iterator, + ) +} + +// rect is used by Intersects +type rect struct { + min, max []float64 +} + +func (r *rect) Rect(ctx interface{}) (min, max []float64) { + return r.min, r.max +} + +// Intersects searches for rectangle items that intersect a target rect. +// The specified index must have been created by AddIndex() and the target +// is represented by the rect string. This string will be processed by the +// same bounds function that was passed to the CreateSpatialIndex() function. +// An invalid index will return an error. +func (tx *Tx) Intersects(index, bounds string, iterator Iterator) error { + if tx.db == nil { + return ErrTxClosed + } + if index == "" { + // cannot search on keys tree. just return nil. + return nil + } + // wrap a rtree specific iterator around the user-defined iterator. + iter := func(item rtree.Item) bool { + dbi := item.(*dbItem) + return iterator(dbi.key, dbi.val) + } + idx := tx.db.idxs[index] + if idx == nil { + // index was not found. return error + return ErrNotFound + } + if idx.rtr == nil { + // not an r-tree index. just return nil + return nil + } + // execute the search + var min, max []float64 + if idx.rect != nil { + min, max = idx.rect(bounds) + } + idx.rtr.Search(&rect{min, max}, iter) + return nil +} + +// Len returns the number of items in the database +func (tx *Tx) Len() (int, error) { + if tx.db == nil { + return 0, ErrTxClosed + } + return tx.db.keys.Len(), nil +} + +// Rect is helper function that returns a string representation +// of a rect. IndexRect() is the reverse function and can be used +// to generate a rect from a string. +func Rect(min, max []float64) string { + if min == nil && max == nil { + return "" + } + diff := len(min) != len(max) + if !diff { + for i := 0; i < len(min); i++ { + if min[i] != max[i] { + diff = true + break + } + } + } + var b bytes.Buffer + b.WriteByte('[') + for i, v := range min { + if i > 0 { + b.WriteByte(' ') + } + b.WriteString(strconv.FormatFloat(v, 'f', -1, 64)) + } + if diff { + b.WriteString("],[") + for i, v := range max { + if i > 0 { + b.WriteByte(' ') + } + b.WriteString(strconv.FormatFloat(v, 'f', -1, 64)) + } + } + b.WriteByte(']') + return b.String() +} + +// Point is a helper function that converts a series of float64s +// to a rectangle for a spatial index. +func Point(coords ...float64) string { + return Rect(coords, coords) +} + +// IndexRect is a helper function that converts string to a rect. +// Rect() is the reverse function and can be used to generate a string +// from a rect. +func IndexRect(a string) (min, max []float64) { + parts := strings.Split(a, ",") + for i := 0; i < len(parts) && i < 2; i++ { + part := parts[i] + if len(part) >= 2 && part[0] == '[' && part[len(part)-1] == ']' { + pieces := strings.Split(part[1:len(part)-1], " ") + if i == 0 { + min = make([]float64, 0, len(pieces)) + } else { + max = make([]float64, 0, len(pieces)) + } + for j := 0; j < len(pieces); j++ { + piece := pieces[j] + if piece != "" { + n, _ := strconv.ParseFloat(piece, 64) + if i == 0 { + min = append(min, n) + } else { + max = append(max, n) + } + } + } + } + } + if len(parts) == 1 { + max = min + } + return +} + +// IndexString is a helper function that return true if 'a' is less than 'b'. +// This is a case-insensitive comparison. Use the IndexBinary() for comparing +// case-sensitive strings. +func IndexString(a, b string) bool { + // This is a faster approach to strings.ToLower because it does not + // create new strings. + for i := 0; i < len(a) && i < len(b); i++ { + ca, cb := a[i], b[i] + if ca >= 'A' && ca <= 'Z' { + ca += 32 + } + if cb >= 'A' && cb <= 'Z' { + cb += 32 + } + if ca < cb { + return true + } else if ca > cb { + return false + } + } + return len(a) < len(b) +} + +// IndexBinary is a helper function that returns true if 'a' is less than 'b'. +// This compares the raw binary of the string. +func IndexBinary(a, b string) bool { + return a < b +} + +// IndexInt is a helper function that returns true if 'a' is less than 'b'. +func IndexInt(a, b string) bool { + ia, _ := strconv.ParseInt(a, 10, 64) + ib, _ := strconv.ParseInt(b, 10, 64) + return ia < ib +} + +// IndexUint is a helper function that returns true if 'a' is less than 'b'. +// This compares uint64s that are added to the database using the +// Uint() conversion function. +func IndexUint(a, b string) bool { + ia, _ := strconv.ParseUint(a, 10, 64) + ib, _ := strconv.ParseUint(b, 10, 64) + return ia < ib +} + +// IndexFloat is a helper function that returns true if 'a' is less than 'b'. +// This compares float64s that are added to the database using the +// Float() conversion function. +func IndexFloat(a, b string) bool { + ia, _ := strconv.ParseFloat(a, 64) + ib, _ := strconv.ParseFloat(b, 64) + return ia < ib +} diff --git a/buntdb_test.go b/buntdb_test.go new file mode 100644 index 0000000..11ae53e --- /dev/null +++ b/buntdb_test.go @@ -0,0 +1,791 @@ +package buntdb + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io/ioutil" + "math/rand" + "os" + "strings" + "testing" + "time" +) + +func TestBackgroudOperations(t *testing.T) { + os.RemoveAll("data.db") + db, err := Open("data.db") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll("data.db") + defer db.Close() + for i := 0; i < 1000; i++ { + if err := db.Update(func(tx *Tx) error { + for j := 0; j < 200; j++ { + tx.Set(fmt.Sprintf("hello%d", j), "planet", nil) + } + tx.Set("hi", "world", &SetOptions{Expires: true, TTL: time.Second / 2}) + return nil + }); err != nil { + t.Fatal(err) + } + } + n := 0 + db.View(func(tx *Tx) error { + n, _ = tx.Len() + return nil + }) + if n != 201 { + t.Fatalf("expecting '%v', got '%v'", 201, n) + } + time.Sleep(time.Millisecond * 1500) + db.Close() + db, err = Open("data.db") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll("data.db") + defer db.Close() + n = 0 + db.View(func(tx *Tx) error { + n, _ = tx.Len() + return nil + }) + if n != 200 { + t.Fatalf("expecting '%v', got '%v'", 200, n) + } +} +func TestVariousTx(t *testing.T) { + os.RemoveAll("data.db") + db, err := Open("data.db") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll("data.db") + defer db.Close() + if err := db.Update(func(tx *Tx) error { + tx.Set("hello", "planet", nil) + return nil + }); err != nil { + t.Fatal(err) + } + errBroken := errors.New("broken") + if err := db.Update(func(tx *Tx) error { + tx.Set("hello", "world", nil) + return errBroken + }); err != errBroken { + t.Fatalf("did not correctly receive the user-defined transaction error.") + } + var val string + db.View(func(tx *Tx) error { + val, _ = tx.Get("hello") + return nil + }) + if val == "world" { + t.Fatal("a rollbacked transaction got through") + } + if val != "planet" { + t.Fatal("expecting '%v', got '%v'", "planet", val) + } + if err := db.Update(func(tx *Tx) error { + tx.db = nil + if _, _, err := tx.Set("hello", "planet", nil); err != ErrTxClosed { + t.Fatal("expecting a tx closed error") + } + if _, err := tx.Delete("hello"); err != ErrTxClosed { + t.Fatal("expecting a tx closed error") + } + if _, err := tx.Get("hello"); err != ErrTxClosed { + t.Fatal("expecting a tx closed error") + } + tx.db = db + tx.writable = false + if _, _, err := tx.Set("hello", "planet", nil); err != ErrTxNotWritable { + t.Fatal("expecting a tx not writable error") + } + if _, err := tx.Delete("hello"); err != ErrTxNotWritable { + t.Fatal("expecting a tx not writable error") + } + tx.writable = true + if _, err := tx.Get("something"); err != ErrNotFound { + t.Fatalf("expecting not found error") + } + if _, err := tx.Delete("something"); err != ErrNotFound { + t.Fatalf("expecting not found error") + } + tx.Set("var", "val", &SetOptions{Expires: true, TTL: 0}) + if _, err := tx.Get("var"); err != ErrNotFound { + t.Fatalf("expecting not found error") + } + if _, err := tx.Delete("var"); err != ErrNotFound { + tx.unlock() + t.Fatalf("expecting not found error") + } + return nil + }); err != nil { + t.Fatal(err) + } + + // test for invalid commits + if err := db.Update(func(tx *Tx) error { + // we are going to do some hackery + defer func() { + if v := recover(); v != nil { + if v.(string) != "managed tx commit not allowed" { + t.Fatal(v.(string)) + } + } + }() + tx.commit() + return nil + }); err != nil { + t.Fatal(err) + } + + // test for invalid commits + if err := db.Update(func(tx *Tx) error { + // we are going to do some hackery + defer func() { + if v := recover(); v != nil { + if v.(string) != "managed tx rollback not allowed" { + t.Fatal(v.(string)) + } + } + }() + tx.rollback() + return nil + }); err != nil { + t.Fatal(err) + } + + // test for closed transactions + if err := db.Update(func(tx *Tx) error { + tx.db = nil + return nil + }); err != ErrTxClosed { + t.Fatal("expecting tx closed error") + } + db.mu.Unlock() + + // test for invalid writes + if err := db.Update(func(tx *Tx) error { + tx.writable = false + return nil + }); err != ErrTxNotWritable { + t.Fatal("expecting tx not writable error") + } + db.mu.Unlock() + // test for closed transactions + if err := db.View(func(tx *Tx) error { + tx.db = nil + return nil + }); err != ErrTxClosed { + t.Fatal("expecting tx closed error") + } + db.mu.RUnlock() + // flush to unwritable file + if err := db.Update(func(tx *Tx) error { + tx.Set("var1", "val1", nil) + tx.db.file.Close() + return nil + }); err == nil { + t.Fatal("should not be able to commit when the file is closed") + } + db.file, err = os.OpenFile("data.db", os.O_CREATE|os.O_RDWR, 0666) + if err != nil { + t.Fatal(err) + } + if _, err := db.file.Seek(0, 2); err != nil { + t.Fatal(err) + } + db.bufw = bufio.NewWriter(db.file) + db.CreateIndex("blank", "*", nil) + // test scanning + if err := db.Update(func(tx *Tx) error { + tx.Set("nothing", "here", nil) + return nil + }); err != nil { + t.Fatal(err) + } + if err := db.View(func(tx *Tx) error { + s := "" + tx.Ascend("", func(key, val string) bool { + s += key + ":" + val + "\n" + return true + }) + if s != "hello:planet\nnothing:here\n" { + t.Fatal("invalid scan") + } + tx.db = nil + err = tx.Ascend("", func(key, val string) bool { return true }) + if err != ErrTxClosed { + tx.unlock() + t.Fatal("expecting tx closed error") + } + tx.db = db + err = tx.Ascend("na", func(key, val string) bool { return true }) + if err != ErrNotFound { + t.Fatal("expecting not found error") + } + err = tx.Ascend("blank", func(key, val string) bool { return true }) + if err != nil { + t.Fatal(err) + } + s = "" + tx.AscendLessThan("", "liger", func(key, val string) bool { + s += key + ":" + val + "\n" + return true + }) + if s != "hello:planet\n" { + t.Fatal("invalid scan") + } + + s = "" + tx.Descend("", func(key, val string) bool { + s += key + ":" + val + "\n" + return true + }) + if s != "nothing:here\nhello:planet\n" { + t.Fatal("invalid scan") + } + + s = "" + tx.DescendLessOrEqual("", "liger", func(key, val string) bool { + s += key + ":" + val + "\n" + return true + }) + if s != "hello:planet\n" { + t.Fatal("invalid scan") + } + + s = "" + tx.DescendGreaterThan("", "liger", func(key, val string) bool { + s += key + ":" + val + "\n" + return true + }) + if s != "nothing:here\n" { + t.Fatal("invalid scan") + } + s = "" + tx.DescendRange("", "liger", "apple", func(key, val string) bool { + s += key + ":" + val + "\n" + return true + }) + if s != "hello:planet\n" { + t.Fatal("invalid scan") + } + return nil + }); err != nil { + t.Fatal(err) + } + + // test some spatial stuff + db.CreateSpatialIndex("spat", "rect:*", IndexRect) + db.CreateSpatialIndex("junk", "rect:*", nil) + db.Update(func(tx *Tx) error { + tx.Set("rect:1", "[10 10],[20 20]", nil) + tx.Set("rect:2", "[15 15],[25 25]", nil) + tx.Set("shape:1", "[12 12],[25 25]", nil) + s := "" + tx.Intersects("spat", "[5 5],[13 13]", func(key, val string) bool { + s += key + ":" + val + "\n" + return true + }) + if s != "rect:1:[10 10],[20 20]\n" { + t.Fatal("invalid scan") + } + tx.db = nil + err := tx.Intersects("spat", "[5 5],[13 13]", func(key, val string) bool { + return true + }) + if err != ErrTxClosed { + t.Fatal("expecting tx closed error") + } + tx.db = db + err = tx.Intersects("", "[5 5],[13 13]", func(key, val string) bool { + return true + }) + if err != nil { + t.Fatal(err) + } + err = tx.Intersects("na", "[5 5],[13 13]", func(key, val string) bool { + return true + }) + if err != ErrNotFound { + t.Fatal("expecting not found error") + } + err = tx.Intersects("junk", "[5 5],[13 13]", func(key, val string) bool { + return true + }) + if err != nil { + t.Fatal(err) + } + n, err := tx.Len() + if err != nil { + t.Fatal(err) + } + if n != 5 { + t.Fatal("expecting %v, got %v", 5, n) + } + tx.db = nil + _, err = tx.Len() + if err != ErrTxClosed { + t.Fatal("expecting tx closed error") + } + tx.db = db + return nil + }) + + // test after closing + db.Close() + if err := db.Update(func(tx *Tx) error { return nil }); err != ErrDatabaseClosed { + t.Fatalf("should not be able to perform transactionso on a closed database.") + } +} +func TestNoExpiringItem(t *testing.T) { + item := &dbItem{key: "key", val: "val"} + if !item.expiresAt().Equal(maxTime) { + t.Fatal("item.expiresAt() != maxTime") + } + if min, max := item.Rect(nil); min != nil || max != nil { + t.Fatal("item min,max should both be nil") + } +} + +// test database format loading +func TestDatabaseFormat(t *testing.T) { + // should succeed + func() { + resp := strings.Join([]string{ + "*3\r\n$3\r\nset\r\n$4\r\nvar1\r\n$4\r\n1234\r\n", + "*3\r\n$3\r\nset\r\n$4\r\nvar2\r\n$4\r\n1234\r\n", + "*2\r\n$3\r\ndel\r\n$4\r\nvar1\r\n", + "*5\r\n$3\r\nset\r\n$3\r\nvar\r\n$3\r\nval\r\n$2\r\nex\r\n$2\r\n10\r\n", + }, "") + os.RemoveAll("data.db") + ioutil.WriteFile("data.db", []byte(resp), 0666) + db, err := Open("data.db") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll("data.db") + defer db.Close() + }() + testBadFormat := func(resp string) { + os.RemoveAll("data.db") + ioutil.WriteFile("data.db", []byte(resp), 0666) + db, err := Open("data.db") + if err == nil { + db.Close() + os.RemoveAll("data.db") + t.Fatalf("invalid database should not be allowed") + } + } + testBadFormat("*3\r") + testBadFormat("*3\n") + testBadFormat("*a\r\n") + testBadFormat("*2\r\n") + testBadFormat("*2\r\n%3") + testBadFormat("*2\r\n$") + testBadFormat("*2\r\n$3\r\n") + testBadFormat("*2\r\n$3\r\ndel") + testBadFormat("*2\r\n$3\r\ndel\r\r") + testBadFormat("*0\r\n*2\r\n$3\r\ndel\r\r") + testBadFormat("*1\r\n$3\r\nnop\r\n") + testBadFormat("*1\r\n$3\r\ndel\r\n") + testBadFormat("*1\r\n$3\r\nset\r\n") + testBadFormat("*5\r\n$3\r\nset\r\n$3\r\nvar\r\n$3\r\nval\r\n$2\r\nxx\r\n$2\r\n10\r\n") + testBadFormat("*5\r\n$3\r\nset\r\n$3\r\nvar\r\n$3\r\nval\r\n$2\r\nex\r\n$2\r\naa\r\n") +} + +func TestInsertsAndDeleted(t *testing.T) { + os.RemoveAll("data.db") + db, err := Open("data.db") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll("data.db") + defer db.Close() + db.CreateIndex("any", "*", IndexString) + db.CreateSpatialIndex("rect", "*", IndexRect) + if err := db.Update(func(tx *Tx) error { + tx.Set("item1", "value1", &SetOptions{Expires: true, TTL: time.Second}) + tx.Set("item2", "value2", nil) + tx.Set("item3", "value3", &SetOptions{Expires: true, TTL: time.Second}) + return nil + }); err != nil { + t.Fatal(err) + } + + // test replacing items in the database + if err := db.Update(func(tx *Tx) error { + if _, _, err := tx.Set("item1", "nvalue1", nil); err != nil { + return err + } + if _, _, err := tx.Set("item2", "nvalue2", nil); err != nil { + return err + } + if _, err := tx.Delete("item3"); err != nil { + return err + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// test index compare functions +func TestIndexCompare(t *testing.T) { + if !IndexFloat("1.5", "1.6") { + t.Fatalf("expected true, got false") + } + if !IndexInt("-1", "2") { + t.Fatalf("expected true, got false") + } + if !IndexUint("10", "25") { + t.Fatalf("expected true, got false") + } + if !IndexBinary("Hello", "hello") { + t.Fatalf("expected true, got false") + } + if IndexString("hello", "hello") { + t.Fatalf("expected false, got true") + } + if IndexString("Hello", "hello") { + t.Fatalf("expected false, got true") + } + if IndexString("hello", "Hello") { + t.Fatalf("expected false, got true") + } + if !IndexString("gello", "Hello") { + t.Fatalf("expected true, got false") + } + if IndexString("Hello", "gello") { + t.Fatalf("expected false, got true") + } + if Rect(IndexRect("[1 2 3 4],[5 6 7 8]")) != "[1 2 3 4],[5 6 7 8]" { + t.Fatalf("expected '%v', got '%v'", "[1 2 3 4],[5 6 7 8]", Rect(IndexRect("[1 2 3 4],[5 6 7 8]"))) + } + if Rect(IndexRect("[1 2 3 4]")) != "[1 2 3 4]" { + t.Fatalf("expected '%v', got '%v'", "[1 2 3 4]", Rect(IndexRect("[1 2 3 4]"))) + } + if Rect(nil, nil) != "" { + t.Fatalf("expected '%v', got '%v'", "", Rect(nil, nil)) + } + if Point(1, 2, 3) != "[1 2 3]" { + t.Fatalf("expected '%v', got '%v'", "[1 2 3]", Point(1, 2, 3)) + } +} + +// test opening a folder. +func TestOpeningAFolder(t *testing.T) { + os.RemoveAll("dir.tmp") + os.Mkdir("dir.tmp", 0700) + defer os.RemoveAll("dir.tmp") + db, err := Open("dir.tmp") + if err == nil { + db.Close() + t.Fatalf("opening a directory should not be allowed") + } +} + +// test opening an invalid resp file. +func TestOpeningInvalidDatabaseFile(t *testing.T) { + os.RemoveAll("data.db") + ioutil.WriteFile("data.db", []byte("invalid\r\nfile"), 0666) + defer os.RemoveAll("data.db") + db, err := Open("data.db") + if err == nil { + db.Close() + t.Fatalf("invalid database should not be allowed") + } +} + +// test closing a closed database. +func TestOpeningClosedDatabase(t *testing.T) { + os.RemoveAll("data.db") + db, err := Open("data.db") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll("data.db") + if err := db.Close(); err != nil { + t.Fatal(err) + } + if err := db.Close(); err != ErrDatabaseClosed { + t.Fatal("should not be able to close a closed database") + } +} + +func TestVariousIndexOperations(t *testing.T) { + os.RemoveAll("data.db") + db, err := Open("data.db") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll("data.db") + defer db.Close() + // test creating an index with no index name. + err = db.CreateIndex("", "", nil) + if err == nil { + t.Fatal("should not be able to create an index with no name") + } + // test creating an index with a name that has already been used. + err = db.CreateIndex("hello", "", nil) + if err != nil { + t.Fatal(err) + } + err = db.CreateIndex("hello", "", nil) + if err == nil { + t.Fatal("should not be able to create a duplicate index") + } + db.Update(func(tx *Tx) error { + tx.Set("user:1", "tom", nil) + tx.Set("user:2", "janet", nil) + tx.Set("alt:1", "from", nil) + tx.Set("alt:2", "there", nil) + tx.Set("rect:1", "[1 2],[3 4]", nil) + tx.Set("rect:2", "[5 6],[7 8]", nil) + return nil + }) + // test creating an index after adding items. use pattern matching. have some items in the match and some not. + if err := db.CreateIndex("string", "user:*", IndexString); err != nil { + t.Fatal(err) + } + // test creating a spatial index after adding items. use pattern matching. have some items in the match and some not. + if err := db.CreateSpatialIndex("rect", "rect:*", IndexRect); err != nil { + t.Fatal(err) + } + // test dropping an index + if err := db.DropIndex("hello"); err != nil { + t.Fatal(err) + } + // test dropping an index with no name + if err := db.DropIndex(""); err == nil { + t.Fatal("should not be allowed to drop an index with no name") + } + // test dropping an index with no name + if err := db.DropIndex("na"); err == nil { + t.Fatal("should not be allowed to drop an index that does not exist") + } + // test retrieving index names + names, err := db.Indexes() + if err != nil { + t.Fatal(err) + } + if strings.Join(names, ",") != "rect,string" { + t.Fatalf("expecting '%v', got '%v'", "rect,string", strings.Join(names, ",")) + } + // test creating an index after closing database + if err := db.Close(); err != nil { + t.Fatal(err) + } + if err := db.CreateIndex("new-index", "", nil); err != ErrDatabaseClosed { + t.Fatal("should not be able to create an index on a closed database") + } + // test getting index names after closing database + if _, err := db.Indexes(); err != ErrDatabaseClosed { + t.Fatal("should not be able to get index names on a closed database") + } + // test dropping an index after closing database + if err := db.DropIndex("rect"); err != ErrDatabaseClosed { + t.Fatal("should not be able to drop an index on a closed database") + } +} + +func test(t *testing.T, a, b bool) { + if a != b { + t.Fatal("failed, bummer...") + } +} + +func TestPatternMatching(t *testing.T) { + test(t, wildcardMatch("hello", "hello"), true) + test(t, wildcardMatch("hello", "h*"), true) + test(t, wildcardMatch("hello", "h*o"), true) + test(t, wildcardMatch("hello", "h*l*o"), true) + test(t, wildcardMatch("hello", "h*z*o"), false) + test(t, wildcardMatch("hello", "*l*o"), true) + test(t, wildcardMatch("hello", "*l*"), true) + test(t, wildcardMatch("hello", "*?*"), true) + test(t, wildcardMatch("hello", "*"), true) + test(t, wildcardMatch("hello", "h?llo"), true) + test(t, wildcardMatch("hello", "h?l?o"), true) + test(t, wildcardMatch("", "*"), true) + test(t, wildcardMatch("", ""), true) + test(t, wildcardMatch("h", ""), false) + test(t, wildcardMatch("", "?"), false) +} + +func TestBasic(t *testing.T) { + rand.Seed(time.Now().UnixNano()) + os.RemoveAll("data.db") + db, err := Open("data.db") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll("data.db") + defer db.Close() + + // create a simple index + db.CreateIndex("users", "fun:user:*", IndexString) + + // create a spatial index + db.CreateSpatialIndex("rects", "rect:*", IndexRect) + if true { + db.Update(func(tx *Tx) error { + tx.Set("fun:user:0", "tom", nil) + tx.Set("fun:user:1", "Randi", nil) + tx.Set("fun:user:2", "jane", nil) + tx.Set("fun:user:4", "Janet", nil) + tx.Set("fun:user:5", "Paula", nil) + tx.Set("fun:user:6", "peter", nil) + tx.Set("fun:user:7", "Terri", nil) + return nil + }) + // add some random items + start := time.Now() + if err := db.Update(func(tx *Tx) error { + for _, i := range rand.Perm(100) { + tx.Set(fmt.Sprintf("tag:%d", i+100), fmt.Sprintf("val:%d", rand.Int()%100+100), nil) + } + return nil + }); err != nil { + t.Fatal(err) + } + if false { + println(time.Now().Sub(start).String(), db.keys.Len()) + } + // add some random rects + if err := db.Update(func(tx *Tx) error { + tx.Set("rect:1", Rect([]float64{10, 10}, []float64{20, 20}), nil) + tx.Set("rect:2", Rect([]float64{15, 15}, []float64{24, 24}), nil) + tx.Set("rect:3", Rect([]float64{17, 17}, []float64{27, 27}), nil) + return nil + }); err != nil { + t.Fatal(err) + } + } + // verify the data has been created + buf := &bytes.Buffer{} + db.View(func(tx *Tx) error { + tx.Ascend("users", func(key, val string) bool { + fmt.Fprintf(buf, "%s %s\n", key, val) + return true + }) + err = tx.AscendRange("", "tag:170", "tag:172", func(key, val string) bool { + fmt.Fprintf(buf, "%s\n", key) + return true + }) + if err != nil { + t.Fatal(err) + } + err = tx.AscendGreaterOrEqual("", "tag:195", func(key, val string) bool { + fmt.Fprintf(buf, "%s\n", key) + return true + }) + if err != nil { + t.Fatal(err) + } + err = tx.AscendGreaterOrEqual("", "rect:", func(key, val string) bool { + if !strings.HasPrefix(key, "rect:") { + return false + } + min, max := IndexRect(val) + fmt.Fprintf(buf, "%s: %v,%v\n", key, min, max) + return true + }) + expect := make([]string, 2) + n := 0 + tx.Intersects("rects", "[0 0],[15 15]", func(key, val string) bool { + if n == 2 { + t.Fatalf("too many rects where received, expecting only two") + } + min, max := IndexRect(val) + s := fmt.Sprintf("%s: %v,%v\n", key, min, max) + if key == "rect:1" { + expect[0] = s + } else if key == "rect:2" { + expect[1] = s + } + n++ + return true + }) + for _, s := range expect { + buf.WriteString(s) + } + return nil + }) + res := ` +fun:user:2 jane +fun:user:4 Janet +fun:user:5 Paula +fun:user:6 peter +fun:user:1 Randi +fun:user:7 Terri +fun:user:0 tom +tag:170 +tag:171 +tag:195 +tag:196 +tag:197 +tag:198 +tag:199 +rect:1: [10 10],[20 20] +rect:2: [15 15],[24 24] +rect:3: [17 17],[27 27] +rect:1: [10 10],[20 20] +rect:2: [15 15],[24 24] +` + res = strings.Replace(res, "\r", "", -1) + if strings.TrimSpace(buf.String()) != strings.TrimSpace(res) { + t.Fatalf("expected [%v], got [%v]", strings.TrimSpace(res), strings.TrimSpace(buf.String())) + } +} +func testRectStringer(min, max []float64) error { + nmin, nmax := IndexRect(Rect(min, max)) + if len(nmin) != len(min) { + return fmt.Errorf("rect=%v,%v, expect=%v,%v", nmin, nmax, min, max) + } + for i := 0; i < len(min); i++ { + if min[i] != nmin[i] || max[i] != nmax[i] { + return fmt.Errorf("rect=%v,%v, expect=%v,%v", nmin, nmax, min, max) + } + } + return nil +} +func TestRectStrings(t *testing.T) { + test(t, Rect(IndexRect(Point(1))) == "[1]", true) + test(t, Rect(IndexRect(Point(1, 2, 3, 4))) == "[1 2 3 4]", true) + test(t, Rect(IndexRect(Rect(IndexRect("[1 2],[1 2]")))) == "[1 2]", true) + test(t, Rect(IndexRect(Rect(IndexRect("[1 2],[2 2]")))) == "[1 2],[2 2]", true) + test(t, Rect(IndexRect(Rect(IndexRect("[1 2],[2 2],[3]")))) == "[1 2],[2 2]", true) + test(t, Rect(IndexRect(Rect(IndexRect("[1 2]")))) == "[1 2]", true) + test(t, Rect(IndexRect(Rect(IndexRect("[1.5 2 4.5 5.6]")))) == "[1.5 2 4.5 5.6]", true) + test(t, Rect(IndexRect(Rect(IndexRect("[1.5 2 4.5 5.6 -1],[]")))) == "[1.5 2 4.5 5.6 -1],[]", true) + test(t, Rect(IndexRect(Rect(IndexRect("[]")))) == "[]", true) + test(t, Rect(IndexRect(Rect(IndexRect("")))) == "", true) + if err := testRectStringer(nil, nil); err != nil { + t.Fatal(err) + } + if err := testRectStringer([]float64{}, []float64{}); err != nil { + t.Fatal(err) + } + if err := testRectStringer([]float64{1}, []float64{2}); err != nil { + t.Fatal(err) + } + if err := testRectStringer([]float64{1, 2}, []float64{3, 4}); err != nil { + t.Fatal(err) + } + if err := testRectStringer([]float64{1, 2, 3}, []float64{4, 5, 6}); err != nil { + t.Fatal(err) + } + if err := testRectStringer([]float64{1, 2, 3, 4}, []float64{5, 6, 7, 8}); err != nil { + t.Fatal(err) + } + if err := testRectStringer([]float64{1, 2, 3, 4, 5}, []float64{6, 7, 8, 9, 0}); err != nil { + t.Fatal(err) + } +} diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..01c6d75c10b97194fe384e717107fca81d3e4e60 GIT binary patch literal 95733 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

37`Ez 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%0Du6;7?CUs+ss?vxEy}Bo2QA=gc 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