pax_global_header00006660000000000000000000000064137766034100014521gustar00rootroot0000000000000052 comment=13599ea4825acd1376400804a089f0f8fc1e2546 buntdb-1.1.7/000077500000000000000000000000001377660341000130055ustar00rootroot00000000000000buntdb-1.1.7/.github/000077500000000000000000000000001377660341000143455ustar00rootroot00000000000000buntdb-1.1.7/.github/workflows/000077500000000000000000000000001377660341000164025ustar00rootroot00000000000000buntdb-1.1.7/.github/workflows/go.yml000066400000000000000000000012221377660341000175270ustar00rootroot00000000000000name: Go on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: name: Build runs-on: ubuntu-latest steps: - name: Set up Go 1.x uses: actions/setup-go@v2 with: go-version: ^1.13 - name: Check out code into the Go module directory uses: actions/checkout@v2 - name: Get dependencies run: | go get -v -t -d ./... if [ -f Gopkg.toml ]; then curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh dep ensure fi - name: Build run: go build -v . - name: Test run: go test -v . buntdb-1.1.7/LICENSE000066400000000000000000000020651377660341000140150ustar00rootroot00000000000000The 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. buntdb-1.1.7/README.md000066400000000000000000000506151377660341000142730ustar00rootroot00000000000000

BuntDB
Code Coverage Go Report Card GoDoc

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. Features ======== - In-memory database for [fast reads and writes](#performance) - Embeddable with a [simple API](https://godoc.org/github.com/tidwall/buntdb) - [Spatial indexing](#spatial-indexes) for up to 20 dimensions; Useful for Geospatial data - Index fields inside [JSON](#json-indexes) documents - [Collate i18n Indexes](#collate-i18n-indexes) using the optional [collate package](https://github.com/tidwall/collate) - Create [custom indexes](#custom-indexes) for any data type - Support for [multi value indexes](#multi-value-index); Similar to a SQL multi column index - [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](#append-only-file) format for persistence - Option to evict old items with an [expiration](#data-expiration) TTL - Tight codebase, under 2K 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 -u 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 also possible to open a database that does not persist to disk by using `:memory:` as the path of the file. ```go buntdb.Open(":memory:") // Open a file that does not persist to disk. ``` ## 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 changes that occurred 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 transaction: ```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 cause 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`, `AscendEqual`, `Descend`, `DescendLessOrEqual`, `DescendGreaterThan`, `DescendRange`, and `DescendEqual`. 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. Feel free to peruse the [B-tree implementation](https://github.com/tidwall/btree). 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:age 3 user:5:age 8 user:2:age 13 user:7:age 16 user:0:age 35 user:1:age 49 user:4:age 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 balanced 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. ### k-Nearest Neighbors Use the `Nearby` function to get all the positions in order of nearest to farthest : ```go db.View(func(tx *buntdb.Tx) error { tx.Nearby("fleet", "[-113 33]", func(key, val string, dist float64) bool { ... return true }) return nil }) ``` ### Spatial bracket syntax 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 parameter, in this case it's `IndexRect`. - **2D rectangle:** `[10 15],[20 25]` *Min XY: "10x15", Max XY: "20x25"* - **3D rectangle:** `[10 15 12],[20 25 18]` *Min XYZ: "10x15x12", Max XYZ: "20x25x18"* - **2D point:** `[10 15]` *XY: "10x15"* - **LonLat point:** `[-112.2693 33.5123]` *LatLon: "33.5123 -112.2693"* - **LonLat bounding box:** `[-112.26 33.51],[-112.18 33.67]` *Min LatLon: "33.51 -112.26", Max LatLon: "33.67 -112.18"* **Notice:** The longitude is the Y axis and is on the left, and latitude is the X axis and is on the right. You can also represent `Infinity` by using `-inf` and `+inf`. For example, you might have the following points (`[X Y M]` where XY is a point and M is a timestamp): ``` [3 9 1] [3 8 2] [4 8 3] [4 7 4] [5 7 5] [5 6 6] ``` You can then do a search for all points with `M` between 2-4 by calling `Intersects`. ```go tx.Intersects("points", "[-inf -inf 2],[+inf +inf 4]", func(key, val string) bool { println(val) return true }) ``` Which will return: ``` [3 8 2] [4 8 3] [4 7 4] ``` ## JSON Indexes Indexes can be created on individual fields inside JSON documents. BuntDB uses [GJSON](https://github.com/tidwall/gjson) under the hood. For example: ```go package main import ( "fmt" "github.com/tidwall/buntdb" ) func main() { db, _ := buntdb.Open(":memory:") db.CreateIndex("last_name", "*", buntdb.IndexJSON("name.last")) db.CreateIndex("age", "*", buntdb.IndexJSON("age")) db.Update(func(tx *buntdb.Tx) error { tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":38}`, nil) tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":47}`, nil) tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":52}`, nil) tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":28}`, nil) return nil }) db.View(func(tx *buntdb.Tx) error { fmt.Println("Order by last name") tx.Ascend("last_name", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) fmt.Println("Order by age") tx.Ascend("age", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) fmt.Println("Order by age range 30-50") tx.AscendRange("age", `{"age":30}`, `{"age":50}`, func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) return nil }) } ``` Results: ``` Order by last name 3: {"name":{"first":"Carol","last":"Anderson"},"age":52} 4: {"name":{"first":"Alan","last":"Cooper"},"age":28} 1: {"name":{"first":"Tom","last":"Johnson"},"age":38} 2: {"name":{"first":"Janet","last":"Prichard"},"age":47} Order by age 4: {"name":{"first":"Alan","last":"Cooper"},"age":28} 1: {"name":{"first":"Tom","last":"Johnson"},"age":38} 2: {"name":{"first":"Janet","last":"Prichard"},"age":47} 3: {"name":{"first":"Carol","last":"Anderson"},"age":52} Order by age range 30-50 1: {"name":{"first":"Tom","last":"Johnson"},"age":38} 2: {"name":{"first":"Janet","last":"Prichard"},"age":47} ``` ## Multi Value Index With BuntDB it's possible to join multiple values on a single index. This is similar to a [multi column index](http://dev.mysql.com/doc/refman/5.7/en/multiple-column-indexes.html) in a traditional SQL database. In this example we are creating a multi value index on "name.last" and "age": ```go db, _ := buntdb.Open(":memory:") db.CreateIndex("last_name_age", "*", buntdb.IndexJSON("name.last"), buntdb.IndexJSON("age")) db.Update(func(tx *buntdb.Tx) error { tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":38}`, nil) tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":47}`, nil) tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":52}`, nil) tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":28}`, nil) tx.Set("5", `{"name":{"first":"Sam","last":"Anderson"},"age":51}`, nil) tx.Set("6", `{"name":{"first":"Melinda","last":"Prichard"},"age":44}`, nil) return nil }) db.View(func(tx *buntdb.Tx) error { tx.Ascend("last_name_age", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) return nil }) // Output: // 5: {"name":{"first":"Sam","last":"Anderson"},"age":51} // 3: {"name":{"first":"Carol","last":"Anderson"},"age":52} // 4: {"name":{"first":"Alan","last":"Cooper"},"age":28} // 1: {"name":{"first":"Tom","last":"Johnson"},"age":38} // 6: {"name":{"first":"Melinda","last":"Prichard"},"age":44} // 2: {"name":{"first":"Janet","last":"Prichard"},"age":47} ``` ## Descending Ordered Index Any index can be put in descending order by wrapping it's less function with `buntdb.Desc`. ```go db.CreateIndex("last_name_age", "*", buntdb.IndexJSON("name.last"), buntdb.Desc(buntdb.IndexJSON("age"))) ``` This will create a multi value index where the last name is ascending and the age is descending. ## Collate i18n Indexes Using the external [collate package](https://github.com/tidwall/collate) it's possible to create indexes that are sorted by the specified language. This is similar to the [SQL COLLATE keyword](https://msdn.microsoft.com/en-us/library/ms174596.aspx) found in traditional databases. To install: ``` go get -u github.com/tidwall/collate ``` For example: ```go import "github.com/tidwall/collate" // To sort case-insensitive in French. db.CreateIndex("name", "*", collate.IndexString("FRENCH_CI")) // To specify that numbers should sort numerically ("2" < "12") // and use a comma to represent a decimal point. db.CreateIndex("amount", "*", collate.IndexString("FRENCH_NUM")) ``` There's also support for Collation on JSON indexes: ```go db.CreateIndex("last_name", "*", collate.IndexJSON("CHINESE_CI", "name.last")) ``` Check out the [collate project](https://github.com/tidwall/collate) for more information. ## 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. ## Delete while iterating BuntDB does not currently support deleting a key while in the process of iterating. As a workaround you'll need to delete keys following the completion of the iterator. ```go var delkeys []string tx.AscendKeys("object:*", func(k, v string) bool { if someCondition(k) == true { delkeys = append(delkeys, k) } return true // continue }) for _, k := range delkeys { if _, err = tx.Delete(k); err != nil { return err } } ``` ## Append-only File BuntDB uses an AOF (append-only file) which is a log of all database changes that occur from operations like `Set()` and `Delete()`. The format of this file looks like: ``` set key:1 value1 set key:2 value2 set key:1 value3 del key:2 ... ``` When the database opens again, it will read back the aof file and process each command in exact order. This read process happens one time when the database opens. From there on the file is only appended. As you may guess this log file can grow large over time. There's a background routine that automatically shrinks the log file when it gets too large. There is also a `Shrink()` function which will rewrite the aof file so that it contains only the items in the database. The shrink operation does not lock up the database so read and write transactions can continue while shrinking is in process. ### Durability and fsync By default BuntDB executes an `fsync` once every second on the [aof file](#append-only-file). Which simply means that there's a chance that up to one second of data might be lost. If you need higher durability then there's an optional database config setting `Config.SyncPolicy` which can be set to `Always`. The `Config.SyncPolicy` has the following options: - `Never` - fsync is managed by the operating system, less safe - `EverySecond` - fsync every second, fast and safer, this is the default - `Always` - fsync after every write, very durable, slower ## Config Here are some configuration options that can be use to change various behaviors of the database. - **SyncPolicy** adjusts how often the data is synced to disk. This value can be Never, EverySecond, or Always. Default is EverySecond. - **AutoShrinkPercentage** is used by the background process to trigger a shrink of the aof file when the size of the file is larger than the percentage of the result of the previous shrunk file. For example, if this value is 100, and the last shrink process resulted in a 100mb file, then the new aof file must be 200mb before a shrink is triggered. Default is 100. - **AutoShrinkMinSize** defines the minimum size of the aof file before an automatic shrink can occur. Default is 32MB. - **AutoShrinkDisabled** turns off automatic background shrinking. Default is false. To update the configuration you should call `ReadConfig` followed by `SetConfig`. For example: ```go var config buntdb.Config if err := db.ReadConfig(&config); err != nil{ log.Fatal(err) } if err := db.SetConfig(config); err != nil{ log.Fatal(err) } ``` ## Performance How fast is BuntDB? Here are some example [benchmarks](https://github.com/tidwall/raft-buntdb#raftstore-performance-comparison) when using BuntDB in a Raft Store implementation. You can also run the standard Go benchmark tool from the project root directory: ``` go test --bench=. ``` ### BuntDB-Benchmark There's a [custom utility](https://github.com/tidwall/buntdb-benchmark) that was created specifically for benchmarking BuntDB. *These are the results from running the benchmarks on a MacBook Pro 15" 2.8 GHz Intel Core i7:* ``` $ buntdb-benchmark -q GET: 4609604.74 operations per second SET: 248500.33 operations per second ASCEND_100: 2268998.79 operations per second ASCEND_200: 1178388.14 operations per second ASCEND_400: 679134.20 operations per second ASCEND_800: 348445.55 operations per second DESCEND_100: 2313821.69 operations per second DESCEND_200: 1292738.38 operations per second DESCEND_400: 675258.76 operations per second DESCEND_800: 337481.67 operations per second SPATIAL_SET: 134824.60 operations per second SPATIAL_INTERSECTS_100: 939491.47 operations per second SPATIAL_INTERSECTS_200: 561590.40 operations per second SPATIAL_INTERSECTS_400: 306951.15 operations per second SPATIAL_INTERSECTS_800: 159673.91 operations per second ``` To install this utility: ``` go get github.com/tidwall/buntdb-benchmark ``` ## Contact Josh Baker [@tidwall](http://twitter.com/tidwall) ## License BuntDB source code is available under the MIT [License](/LICENSE). buntdb-1.1.7/buntdb.go000066400000000000000000002040241377660341000146140ustar00rootroot00000000000000// 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" "errors" "io" "os" "sort" "strconv" "strings" "sync" "time" "github.com/tidwall/btree" "github.com/tidwall/gjson" "github.com/tidwall/grect" "github.com/tidwall/match" "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") // ErrInvalidSyncPolicy is returned for an invalid SyncPolicy value. ErrInvalidSyncPolicy = errors.New("invalid sync policy") // ErrShrinkInProcess is returned when a shrink operation is in-process. ErrShrinkInProcess = errors.New("shrink is in-process") // ErrPersistenceActive is returned when post-loading data from an database // not opened with Open(":memory:"). ErrPersistenceActive = errors.New("persistence active") // ErrTxIterating is returned when Set or Delete are called while iterating. ErrTxIterating = errors.New("tx is iterating") ) // 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 buf []byte // a buffer to write to 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 config Config // the database configuration persist bool // do we write to disk shrinking bool // when an aof shrink is in-process. lastaofsz int // the size of the last shrink aof size } // SyncPolicy represents how often data is synced to disk. type SyncPolicy int const ( // Never is used to disable syncing data to disk. // The faster and less safe method. Never SyncPolicy = 0 // EverySecond is used to sync data to disk every second. // It's pretty fast and you can lose 1 second of data if there // is a disaster. // This is the recommended setting. EverySecond = 1 // Always is used to sync data after every write to disk. // Slow. Very safe. Always = 2 ) // Config represents database configuration options. These // options are used to change various behaviors of the database. type Config struct { // SyncPolicy adjusts how often the data is synced to disk. // This value can be Never, EverySecond, or Always. // The default is EverySecond. SyncPolicy SyncPolicy // AutoShrinkPercentage is used by the background process to trigger // a shrink of the aof file when the size of the file is larger than the // percentage of the result of the previous shrunk file. // For example, if this value is 100, and the last shrink process // resulted in a 100mb file, then the new aof file must be 200mb before // a shrink is triggered. AutoShrinkPercentage int // AutoShrinkMinSize defines the minimum size of the aof file before // an automatic shrink can occur. AutoShrinkMinSize int // AutoShrinkDisabled turns off automatic background shrinking AutoShrinkDisabled bool // OnExpired is used to custom handle the deletion option when a key // has been expired. OnExpired func(keys []string) // OnExpiredSync will be called inside the same transaction that is // performing the deletion of expired items. If OnExpired is present then // this callback will not be called. If this callback is present, then the // deletion of the timeed-out item is the explicit responsibility of this // callback. OnExpiredSync func(key, value string, tx *Tx) error } // exctx is a simple b-tree context for ordering by expiration. type exctx struct { db *DB } // Default number of btree degrees const btreeDegrees = 64 // 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{} // initialize trees and indexes db.keys = btree.New(lessCtx(nil)) db.exps = btree.New(lessCtx(&exctx{db})) db.idxs = make(map[string]*index) // initialize default configuration db.config = Config{ SyncPolicy: EverySecond, AutoShrinkPercentage: 100, AutoShrinkMinSize: 32 * 1024 * 1024, } // turn off persistence for pure in-memory db.persist = path != ":memory:" if db.persist { 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 } // load the database from disk if err := db.load(); err != nil { // close on error, ignore close error _ = db.file.Close() return nil, err } } // 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 if db.persist { db.file.Sync() // do a sync but ignore the error if err := db.file.Close(); err != nil { return err } } // Let's release all references to nil. This will help both with debugging // late usage panics and it provides a hint to the garbage collector db.keys, db.exps, db.idxs, db.file = nil, nil, nil, nil return nil } // Save writes a snapshot of the database to a writer. This operation blocks all // writes, but not reads. This can be used for snapshots and backups for pure // in-memory databases using the ":memory:". Database that persist to disk // can be snapshotted by simply copying the database file. func (db *DB) Save(wr io.Writer) error { var err error db.mu.RLock() defer db.mu.RUnlock() // use a buffered writer and flush every 4MB var buf []byte // iterated through every item in the database and write to the buffer btreeAscend(db.keys, func(item interface{}) bool { dbi := item.(*dbItem) buf = dbi.writeSetTo(buf) if len(buf) > 1024*1024*4 { // flush when buffer is over 4MB _, err = wr.Write(buf) if err != nil { return false } buf = buf[:0] } return true }) if err != nil { return err } // one final flush if len(buf) > 0 { _, err = wr.Write(buf) if err != nil { return err } } return nil } // Load loads commands from reader. This operation blocks all reads and writes. // Note that this can only work for fully in-memory databases opened with // Open(":memory:"). func (db *DB) Load(rd io.Reader) error { db.mu.Lock() defer db.mu.Unlock() if db.persist { // cannot load into databases that persist to disk return ErrPersistenceActive } return db.readLoad(rd, time.Now()) } // 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 opts IndexOptions // index options } // match matches the pattern to the key func (idx *index) match(key string) bool { if idx.pattern == "*" { return true } if idx.opts.CaseInsensitiveKeyMatching { for i := 0; i < len(key); i++ { if key[i] >= 'A' && key[i] <= 'Z' { key = strings.ToLower(key) break } } } return match.Match(key, idx.pattern) } // clearCopy creates a copy of the index, but with an empty dataset. func (idx *index) clearCopy() *index { // copy the index meta information nidx := &index{ name: idx.name, pattern: idx.pattern, db: idx.db, less: idx.less, rect: idx.rect, opts: idx.opts, } // initialize with empty trees if nidx.less != nil { nidx.btr = btree.New(lessCtx(nidx)) } if nidx.rect != nil { nidx.rtr = rtree.New(nidx) } return nidx } // rebuild rebuilds the index func (idx *index) rebuild() { // initialize trees if idx.less != nil { idx.btr = btree.New(lessCtx(idx)) } if idx.rect != nil { idx.rtr = rtree.New(idx) } // iterate through all keys and fill the index btreeAscend(idx.db.keys, func(item interface{}) bool { dbi := item.(*dbItem) if !idx.match(dbi.key) { // does not match the pattern, continue return true } if idx.less != nil { idx.btr.Set(dbi) } if idx.rect != nil { idx.rtr.Insert(dbi) } return true }) } // 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. This is a very simple pattern // match where '*' matches on any number characters and '?' matches on // any one character. // 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.Update(func(tx *Tx) error { return tx.CreateIndex(name, pattern, less...) }) } // ReplaceIndex 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. // If a previous index with the same name exists, that index will be deleted. func (db *DB) ReplaceIndex(name, pattern string, less ...func(a, b string) bool) error { return db.Update(func(tx *Tx) error { err := tx.CreateIndex(name, pattern, less...) if err != nil { if err == ErrIndexExists { err := tx.DropIndex(name) if err != nil { return err } return tx.CreateIndex(name, pattern, less...) } return err } return 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 20, 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. // There is support for up to 20 dimensions. // 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.Update(func(tx *Tx) error { return tx.CreateSpatialIndex(name, pattern, rect) }) } // ReplaceSpatialIndex 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. // If a previous index with the same name exists, that index will be deleted. func (db *DB) ReplaceSpatialIndex(name, pattern string, rect func(item string) (min, max []float64)) error { return db.Update(func(tx *Tx) error { err := tx.CreateSpatialIndex(name, pattern, rect) if err != nil { if err == ErrIndexExists { err := tx.DropIndex(name) if err != nil { return err } return tx.CreateSpatialIndex(name, pattern, rect) } return err } return nil }) } // DropIndex removes an index. func (db *DB) DropIndex(name string) error { return db.Update(func(tx *Tx) error { return tx.DropIndex(name) }) } // Indexes returns a list of index names. func (db *DB) Indexes() ([]string, error) { var names []string var err = db.View(func(tx *Tx) error { var err error names, err = tx.Indexes() return err }) return names, err } // ReadConfig returns the database configuration. func (db *DB) ReadConfig(config *Config) error { db.mu.RLock() defer db.mu.RUnlock() if db.closed { return ErrDatabaseClosed } *config = db.config return nil } // SetConfig updates the database configuration. func (db *DB) SetConfig(config Config) error { db.mu.Lock() defer db.mu.Unlock() if db.closed { return ErrDatabaseClosed } switch config.SyncPolicy { default: return ErrInvalidSyncPolicy case Never, EverySecond, Always: } db.config = config return 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.Set(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.Set(item) } for _, idx := range db.idxs { if !idx.match(item.key) { continue } if idx.btr != nil { // Add new item to btree index. idx.btr.Set(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 { var shrink bool // Open a standard view. This will take a full lock of the // database thus allowing for access to anything we need. var onExpired func([]string) var expired []*dbItem var onExpiredSync func(key, value string, tx *Tx) error err := db.Update(func(tx *Tx) error { onExpired = db.config.OnExpired if onExpired == nil { onExpiredSync = db.config.OnExpiredSync } if db.persist && !db.config.AutoShrinkDisabled { pos, err := db.file.Seek(0, 1) if err != nil { return err } aofsz := int(pos) if aofsz > db.config.AutoShrinkMinSize { prc := float64(db.config.AutoShrinkPercentage) / 100.0 shrink = aofsz > db.lastaofsz+int(float64(db.lastaofsz)*prc) } } // produce a list of expired items that need removing btreeAscendLessThan(db.exps, &dbItem{ opts: &dbItemOpts{ex: true, exat: time.Now()}, }, func(item interface{}) bool { expired = append(expired, item.(*dbItem)) return true }) if onExpired == nil && onExpiredSync == nil { for _, itm := range expired { if _, err := tx.Delete(itm.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 } } } } else if onExpiredSync != nil { for _, itm := range expired { if err := onExpiredSync(itm.key, itm.val, tx); err != nil { return err } } } return nil }) if err == ErrDatabaseClosed { break } // send expired event, if needed if onExpired != nil && len(expired) > 0 { keys := make([]string, 0, 32) for _, itm := range expired { keys = append(keys, itm.key) } onExpired(keys) } // execute a disk sync, if needed func() { db.mu.Lock() defer db.mu.Unlock() if db.persist && db.config.SyncPolicy == EverySecond && flushes != db.flushes { _ = db.file.Sync() flushes = db.flushes } }() if shrink { if err = db.Shrink(); err != nil { if err == ErrDatabaseClosed { break } } } } } // Shrink will make the database file smaller by removing redundant // log entries. This operation does not block the database. func (db *DB) Shrink() error { db.mu.Lock() if db.closed { db.mu.Unlock() return ErrDatabaseClosed } if !db.persist { // The database was opened with ":memory:" as the path. // There is no persistence, and no need to do anything here. db.mu.Unlock() return nil } if db.shrinking { // The database is already in the process of shrinking. db.mu.Unlock() return ErrShrinkInProcess } db.shrinking = true defer func() { db.mu.Lock() db.shrinking = false db.mu.Unlock() }() 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. endpos, err := db.file.Seek(0, 2) if err != nil { return err } db.mu.Unlock() time.Sleep(time.Second / 4) // wait just a bit before starting 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. var buf []byte pivot := "" done := false for !done { err := func() error { db.mu.RLock() defer db.mu.RUnlock() if db.closed { return ErrDatabaseClosed } done = true var n int btreeAscendGreaterOrEqual(db.keys, &dbItem{key: pivot}, func(item interface{}) bool { dbi := item.(*dbItem) // 1000 items or 64MB buffer if n > 1000 || len(buf) > 64*1024*1024 { pivot = dbi.key done = false return false } buf = dbi.writeSetTo(buf) n++ return true }, ) if len(buf) > 0 { if _, err := f.Write(buf); err != nil { return err } buf = buf[:0] } 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. return 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() if db.closed { return ErrDatabaseClosed } // 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 func() { _ = aof.Close() }() if _, err := aof.Seek(endpos, 0); err != nil { return err } // Just copy all of the new commands that have occurred 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 } // Any 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) } pos, err := db.file.Seek(0, 2) if err != nil { return err } db.lastaofsz = int(pos) return nil }() } var errValidEOF = errors.New("valid eof") // readLoad reads from the reader and loads commands into the database. // modTime is the modified time of the reader, should be no greater than // the current time.Now(). func (db *DB) readLoad(rd io.Reader, modTime time.Time) error { data := make([]byte, 4096) parts := make([]string, 0, 8) r := bufio.NewReader(rd) for { // read a single command. // first we should read the number of parts that the of the command line, err := r.ReadBytes('\n') if err != nil { if len(line) > 0 { // got an eof but also data. this should be an unexpected eof. return io.ErrUnexpectedEOF } if err == io.EOF { break } return err } if line[0] != '*' { return ErrInvalid } // convert the string number to and int var n int if len(line) == 4 && line[len(line)-2] == '\r' { if line[1] < '0' || line[1] > '9' { return ErrInvalid } n = int(line[1] - '0') } else { if len(line) < 5 || line[len(line)-2] != '\r' { return ErrInvalid } for i := 1; i < len(line)-2; i++ { if line[i] < '0' || line[i] > '9' { return ErrInvalid } n = n*10 + int(line[i]-'0') } } // read each part of the command. parts = parts[:0] for i := 0; i < n; i++ { // read the number of bytes of the part. line, err := r.ReadBytes('\n') if err != nil { return err } if line[0] != '$' { return ErrInvalid } // convert the string number to and int var n int if len(line) == 4 && line[len(line)-2] == '\r' { if line[1] < '0' || line[1] > '9' { return ErrInvalid } n = int(line[1] - '0') } else { if len(line) < 5 || line[len(line)-2] != '\r' { return ErrInvalid } for i := 1; i < len(line)-2; i++ { if line[i] < '0' || line[i] > '9' { return ErrInvalid } n = n*10 + int(line[i]-'0') } } // resize the read buffer if len(data) < n+2 { dataln := len(data) for dataln < n+2 { dataln *= 2 } data = make([]byte, dataln) } if _, err = io.ReadFull(r, data[:n+2]); err != nil { return err } if data[n] != '\r' || data[n+1] != '\n' { return ErrInvalid } // copy string parts = append(parts, string(data[:n])) } // finished reading the command if len(parts) == 0 { continue } if (parts[0][0] == 's' || parts[0][0] == 'S') && (parts[0][1] == 'e' || parts[0][1] == 'E') && (parts[0][2] == 't' || parts[0][2] == 'T') { // SET if len(parts) < 3 || len(parts) == 4 || len(parts) > 5 { return ErrInvalid } if len(parts) == 5 { if strings.ToLower(parts[3]) != "ex" { return ErrInvalid } ex, err := strconv.ParseUint(parts[4], 10, 64) if err != nil { return err } now := time.Now() dur := (time.Duration(ex) * time.Second) - now.Sub(modTime) if dur > 0 { db.insertIntoDatabase(&dbItem{ key: parts[1], val: parts[2], opts: &dbItemOpts{ ex: true, exat: now.Add(dur), }, }) } } else { db.insertIntoDatabase(&dbItem{key: parts[1], val: parts[2]}) } } else if (parts[0][0] == 'd' || parts[0][0] == 'D') && (parts[0][1] == 'e' || parts[0][1] == 'E') && (parts[0][2] == 'l' || parts[0][2] == 'L') { // DEL if len(parts) != 2 { return ErrInvalid } db.deleteFromDatabase(&dbItem{key: parts[1]}) } else if (parts[0][0] == 'f' || parts[0][0] == 'F') && strings.ToLower(parts[0]) == "flushdb" { db.keys = btree.New(lessCtx(nil)) db.exps = btree.New(lessCtx(&exctx{db})) db.idxs = make(map[string]*index) } else { return ErrInvalid } } return 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 { fi, err := db.file.Stat() if err != nil { return err } if err := db.readLoad(db.file, fi.ModTime()); err != nil { return err } pos, err := db.file.Seek(0, 2) if err != nil { return err } db.lastaofsz = int(pos) 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) } // get return an item or nil if not found. func (db *DB) get(key string) *dbItem { item := db.keys.Get(&dbItem{key: key}) if item != nil { return item.(*dbItem) } return nil } // 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. wc *txWriteContext // context for writable transactions. } type txWriteContext struct { // rollback when deleteAll is called rbkeys *btree.BTree // a tree of all item ordered by key rbexps *btree.BTree // a tree of items ordered by expiration rbidxs map[string]*index // the index trees. rollbackItems map[string]*dbItem // details for rolling back tx. commitItems map[string]*dbItem // details for committing tx. itercount int // stack of iterators rollbackIndexes map[string]*index // details for dropped indexes. } // DeleteAll deletes all items from the database. func (tx *Tx) DeleteAll() error { if tx.db == nil { return ErrTxClosed } else if !tx.writable { return ErrTxNotWritable } else if tx.wc.itercount > 0 { return ErrTxIterating } // check to see if we've already deleted everything if tx.wc.rbkeys == nil { // we need to backup the live data in case of a rollback. tx.wc.rbkeys = tx.db.keys tx.wc.rbexps = tx.db.exps tx.wc.rbidxs = tx.db.idxs } // now reset the live database trees tx.db.keys = btree.New(lessCtx(nil)) tx.db.exps = btree.New(lessCtx(&exctx{tx.db})) tx.db.idxs = make(map[string]*index) // finally re-create the indexes for name, idx := range tx.wc.rbidxs { tx.db.idxs[name] = idx.clearCopy() } // always clear out the commits tx.wc.commitItems = make(map[string]*dbItem) return nil } // 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, } tx.lock() if db.closed { tx.unlock() return nil, ErrDatabaseClosed } if writable { // writable transactions have a writeContext object that // contains information about changes to the database. tx.wc = &txWriteContext{} tx.wc.rollbackItems = make(map[string]*dbItem) tx.wc.rollbackIndexes = make(map[string]*index) if db.persist { tx.wc.commitItems = make(map[string]*dbItem) } } 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() { // rollback the deleteAll if needed if tx.wc.rbkeys != nil { tx.db.keys = tx.wc.rbkeys tx.db.idxs = tx.wc.rbidxs tx.db.exps = tx.wc.rbexps } for key, item := range tx.wc.rollbackItems { 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) } } for name, idx := range tx.wc.rollbackIndexes { delete(tx.db.idxs, name) if idx != nil { // When an index is not nil, we will need to rebuilt that index // this could be an expensive process if the database has many // items or the index is complex. tx.db.idxs[name] = idx idx.rebuild() } } } // 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 tx.db.persist && (len(tx.wc.commitItems) > 0 || tx.wc.rbkeys != nil) { tx.db.buf = tx.db.buf[:0] // write a flushdb if a deleteAll was called. if tx.wc.rbkeys != nil { tx.db.buf = append(tx.db.buf, "*1\r\n$7\r\nflushdb\r\n"...) } // Each committed record is written to disk for key, item := range tx.wc.commitItems { if item == nil { tx.db.buf = (&dbItem{key: key}).writeDeleteTo(tx.db.buf) } else { tx.db.buf = item.writeSetTo(tx.db.buf) } } // Flushing the buffer only once per transaction. // If this operation fails then the write did failed and we must // rollback. if _, err = tx.db.file.Write(tx.db.buf); err != nil { tx.rollbackInner() } if tx.db.config.SyncPolicy == Always { _ = tx.db.file.Sync() } // Increment the number of flushes. The background syncing uses this. tx.db.flushes++ } // 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 keyless bool // keyless item for scanning } func appendArray(buf []byte, count int) []byte { buf = append(buf, '*') buf = append(buf, strconv.FormatInt(int64(count), 10)...) buf = append(buf, '\r', '\n') return buf } func appendBulkString(buf []byte, s string) []byte { buf = append(buf, '$') buf = append(buf, strconv.FormatInt(int64(len(s)), 10)...) buf = append(buf, '\r', '\n') buf = append(buf, s...) buf = append(buf, '\r', '\n') return buf } // writeSetTo writes an item as a single SET record to the a bufio Writer. func (dbi *dbItem) writeSetTo(buf []byte) []byte { if dbi.opts != nil && dbi.opts.ex { ex := dbi.opts.exat.Sub(time.Now()) / time.Second buf = appendArray(buf, 5) buf = appendBulkString(buf, "set") buf = appendBulkString(buf, dbi.key) buf = appendBulkString(buf, dbi.val) buf = appendBulkString(buf, "ex") buf = appendBulkString(buf, strconv.FormatUint(uint64(ex), 10)) } else { buf = appendArray(buf, 3) buf = appendBulkString(buf, "set") buf = appendBulkString(buf, dbi.key) buf = appendBulkString(buf, dbi.val) } return buf } // writeSetTo writes an item as a single DEL record to the a bufio Writer. func (dbi *dbItem) writeDeleteTo(buf []byte) []byte { buf = appendArray(buf, 2) buf = appendBulkString(buf, "del") buf = appendBulkString(buf, dbi.key) return buf } // 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(dbi2 *dbItem, ctx interface{}) bool { 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. if dbi.keyless { return false } else if dbi2.keyless { return true } return dbi.key < dbi2.key } func lessCtx(ctx interface{}) func(a, b interface{}) bool { return func(a, b interface{}) bool { return a.(*dbItem).Less(b.(*dbItem), ctx) } } // 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 } // GetLess returns the less function for an index. This is handy for // doing ad-hoc compares inside a transaction. // Returns ErrNotFound if the index is not found or there is no less // function bound to the index func (tx *Tx) GetLess(index string) (func(a, b string) bool, error) { if tx.db == nil { return nil, ErrTxClosed } idx, ok := tx.db.idxs[index] if !ok || idx.less == nil { return nil, ErrNotFound } return idx.less, nil } // GetRect returns the rect function for an index. This is handy for // doing ad-hoc searches inside a transaction. // Returns ErrNotFound if the index is not found or there is no rect // function bound to the index func (tx *Tx) GetRect(index string) (func(s string) (min, max []float64), error) { if tx.db == nil { return nil, ErrTxClosed } idx, ok := tx.db.idxs[index] if !ok || idx.rect == nil { return nil, ErrNotFound } return idx.rect, nil } // Set inserts or replaces an item in the database based on the key. // The opt params may be used for additional functionality such as forcing // the item to be evicted at a specified time. When the return value // for err is nil the operation succeeded. 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 committed. // // Only a writable transaction can be used with this operation. // This operation is not allowed during iterations such as Ascend* & Descend*. 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 } else if tx.wc.itercount > 0 { return "", false, ErrTxIterating } 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) // insert into the rollback map if there has not been a deleteAll. if tx.wc.rbkeys == nil { 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. if _, ok := tx.wc.rollbackItems[key]; !ok { tx.wc.rollbackItems[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.wc.rollbackItems[key]; !ok { tx.wc.rollbackItems[key] = prev } if !prev.expired() { previousValue, replaced = prev.val, true } } } // For commits we simply assign the item to the map. We use this map to // write the entry to disk. if tx.db.persist { tx.wc.commitItems[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. If ignoreExpired is true, then // the found value will be returned even if it is expired. func (tx *Tx) Get(key string, ignoreExpired ...bool) (val string, err error) { if tx.db == nil { return "", ErrTxClosed } var ignore bool if len(ignoreExpired) != 0 { ignore = ignoreExpired[0] } item := tx.db.get(key) if item == nil || (item.expired() && !ignore) { // The item does not exists or 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 a writable transaction can be used for this operation. // This operation is not allowed during iterations such as Ascend* & Descend*. func (tx *Tx) Delete(key string) (val string, err error) { if tx.db == nil { return "", ErrTxClosed } else if !tx.writable { return "", ErrTxNotWritable } else if tx.wc.itercount > 0 { return "", ErrTxIterating } item := tx.db.deleteFromDatabase(&dbItem{key: key}) if item == nil { return "", ErrNotFound } // create a rollback entry if there has not been a deleteAll call. if tx.wc.rbkeys == nil { if _, ok := tx.wc.rollbackItems[key]; !ok { tx.wc.rollbackItems[key] = item } } if tx.db.persist { tx.wc.commitItems[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 } // TTL returns the remaining time-to-live for an item. // A negative duration will be returned for items that do not have an // expiration. func (tx *Tx) TTL(key string) (time.Duration, error) { if tx.db == nil { return 0, ErrTxClosed } item := tx.db.get(key) if item == nil { return 0, ErrNotFound } else if item.opts == nil || !item.opts.ex { return -1, nil } dur := item.opts.exat.Sub(time.Now()) if dur < 0 { return 0, ErrNotFound } return dur, 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 func(key, value string) bool) error { if tx.db == nil { return ErrTxClosed } // wrap a btree specific iterator around the user-defined iterator. iter := func(item interface{}) 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 { if index == "" { itemA = &dbItem{key: start} itemB = &dbItem{key: stop} } else { itemA = &dbItem{val: start} itemB = &dbItem{val: stop} if desc { itemA.keyless = true itemB.keyless = true } } } // execute the scan on the underlying tree. if tx.wc != nil { tx.wc.itercount++ defer func() { tx.wc.itercount-- }() } if desc { if gt { if lt { btreeDescendRange(tr, itemA, itemB, iter) } else { btreeDescendGreaterThan(tr, itemA, iter) } } else if lt { btreeDescendLessOrEqual(tr, itemA, iter) } else { btreeDescend(tr, iter) } } else { if gt { if lt { btreeAscendRange(tr, itemA, itemB, iter) } else { btreeAscendGreaterOrEqual(tr, itemA, iter) } } else if lt { btreeAscendLessThan(tr, itemA, iter) } else { btreeAscend(tr, iter) } } return nil } // Match returns true if the specified key matches the pattern. This is a very // simple pattern matcher where '*' matches on any number characters and '?' // matches on any one character. func Match(key, pattern string) bool { return match.Match(key, pattern) } // AscendKeys allows for iterating through keys based on the specified pattern. func (tx *Tx) AscendKeys(pattern string, iterator func(key, value string) bool) error { if pattern == "" { return nil } if pattern[0] == '*' { if pattern == "*" { return tx.Ascend("", iterator) } return tx.Ascend("", func(key, value string) bool { if match.Match(key, pattern) { if !iterator(key, value) { return false } } return true }) } min, max := match.Allowable(pattern) return tx.AscendGreaterOrEqual("", min, func(key, value string) bool { if key > max { return false } if match.Match(key, pattern) { if !iterator(key, value) { return false } } return true }) } // DescendKeys allows for iterating through keys based on the specified pattern. func (tx *Tx) DescendKeys(pattern string, iterator func(key, value string) bool) error { if pattern == "" { return nil } if pattern[0] == '*' { if pattern == "*" { return tx.Descend("", iterator) } return tx.Descend("", func(key, value string) bool { if match.Match(key, pattern) { if !iterator(key, value) { return false } } return true }) } min, max := match.Allowable(pattern) return tx.DescendLessOrEqual("", max, func(key, value string) bool { if key < min { return false } if match.Match(key, pattern) { if !iterator(key, value) { return false } } return true }) } // 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 func(key, value string) bool) 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 func(key, value string) bool) 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 func(key, value string) bool) 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 func(key, value string) bool) 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 func(key, value string) bool) 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 func(key, value string) bool) 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 func(key, value string) bool) 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 func(key, value string) bool) error { return tx.scan( true, true, true, index, lessOrEqual, greaterThan, iterator, ) } // AscendEqual calls the iterator for every item in the database that equals // 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) AscendEqual(index, pivot string, iterator func(key, value string) bool) error { var err error var less func(a, b string) bool if index != "" { less, err = tx.GetLess(index) if err != nil { return err } } return tx.AscendGreaterOrEqual(index, pivot, func(key, value string) bool { if less == nil { if key != pivot { return false } } else if less(pivot, value) { return false } return iterator(key, value) }) } // DescendEqual calls the iterator for every item in the database that equals // 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) DescendEqual(index, pivot string, iterator func(key, value string) bool) error { var err error var less func(a, b string) bool if index != "" { less, err = tx.GetLess(index) if err != nil { return err } } return tx.DescendLessOrEqual(index, pivot, func(key, value string) bool { if less == nil { if key != pivot { return false } } else if less(value, pivot) { return false } return iterator(key, value) }) } // rect is used by Intersects and Nearby type rect struct { min, max []float64 } func (r *rect) Rect(ctx interface{}) (min, max []float64) { return r.min, r.max } // Nearby searches for rectangle items that are nearby a target rect. // All items belonging to the specified index will be returned in order of // nearest to farthest. // 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. // The dist param is the distance of the bounding boxes. In the case of // simple 2D points, it's the distance of the two 2D points squared. func (tx *Tx) Nearby(index, bounds string, iterator func(key, value string, dist float64) bool) 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, dist float64) bool { dbi := item.(*dbItem) return iterator(dbi.key, dbi.val, dist) } 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 nearby search var min, max []float64 if idx.rect != nil { min, max = idx.rect(bounds) } // set the center param to false, which uses the box dist calc. idx.rtr.KNN(&rect{min, max}, false, iter) return nil } // 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 func(key, value string) bool) 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 } // IndexOptions provides an index with additional features or // alternate functionality. type IndexOptions struct { // CaseInsensitiveKeyMatching allow for case-insensitive // matching on keys when setting key/values. CaseInsensitiveKeyMatching bool } // 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. This is a very simple pattern // match where '*' matches on any number characters and '?' matches on // any one character. // 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 (tx *Tx) CreateIndex(name, pattern string, less ...func(a, b string) bool) error { return tx.createIndex(name, pattern, less, nil, nil) } // CreateIndexOptions is the same as CreateIndex except that it allows // for additional options. func (tx *Tx) CreateIndexOptions(name, pattern string, opts *IndexOptions, less ...func(a, b string) bool) error { return tx.createIndex(name, pattern, less, nil, opts) } // 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 20, 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. // There is support for up to 20 dimensions. // 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 (tx *Tx) CreateSpatialIndex(name, pattern string, rect func(item string) (min, max []float64)) error { return tx.createIndex(name, pattern, nil, rect, nil) } // CreateSpatialIndexOptions is the same as CreateSpatialIndex except that // it allows for additional options. func (tx *Tx) CreateSpatialIndexOptions(name, pattern string, opts *IndexOptions, rect func(item string) (min, max []float64)) error { return tx.createIndex(name, pattern, nil, rect, nil) } // createIndex is called by CreateIndex() and CreateSpatialIndex() func (tx *Tx) createIndex(name string, pattern string, lessers []func(a, b string) bool, rect func(item string) (min, max []float64), opts *IndexOptions, ) error { if tx.db == nil { return ErrTxClosed } else if !tx.writable { return ErrTxNotWritable } else if tx.wc.itercount > 0 { return ErrTxIterating } if name == "" { // cannot create an index without a name. // an empty name index is designated for the main "keys" tree. return ErrIndexExists } // check if an index with that name already exists. if _, ok := tx.db.idxs[name]; ok { // index with name already exists. error. return ErrIndexExists } // genreate a less function var less func(a, b string) bool switch len(lessers) { default: // multiple less functions specified. // create a compound less function. less = func(a, b string) bool { for i := 0; i < len(lessers)-1; i++ { if lessers[i](a, b) { return true } if lessers[i](b, a) { return false } } return lessers[len(lessers)-1](a, b) } case 0: // no less function case 1: less = lessers[0] } var sopts IndexOptions if opts != nil { sopts = *opts } if sopts.CaseInsensitiveKeyMatching { pattern = strings.ToLower(pattern) } // intialize new index idx := &index{ name: name, pattern: pattern, less: less, rect: rect, db: tx.db, opts: sopts, } idx.rebuild() // save the index tx.db.idxs[name] = idx if tx.wc.rbkeys == nil { // store the index in the rollback map. if _, ok := tx.wc.rollbackIndexes[name]; !ok { // we use nil to indicate that the index should be removed upon // rollback. tx.wc.rollbackIndexes[name] = nil } } return nil } // DropIndex removes an index. func (tx *Tx) DropIndex(name string) error { if tx.db == nil { return ErrTxClosed } else if !tx.writable { return ErrTxNotWritable } else if tx.wc.itercount > 0 { return ErrTxIterating } if name == "" { // cannot drop the default "keys" index return ErrInvalidOperation } idx, ok := tx.db.idxs[name] if !ok { return ErrNotFound } // delete from the map. // this is all that is needed to delete an index. delete(tx.db.idxs, name) if tx.wc.rbkeys == nil { // store the index in the rollback map. if _, ok := tx.wc.rollbackIndexes[name]; !ok { // we use a non-nil copy of the index without the data to indicate // that the index should be rebuilt upon rollback. tx.wc.rollbackIndexes[name] = idx.clearCopy() } } return nil } // Indexes returns a list of index names. func (tx *Tx) Indexes() ([]string, error) { if tx.db == nil { return nil, ErrTxClosed } names := make([]string, 0, len(tx.db.idxs)) for name := range tx.db.idxs { names = append(names, name) } sort.Strings(names) return names, 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 { r := grect.Rect{Min: min, Max: max} return r.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) { r := grect.Get(a) return r.Min, r.Max } // 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 { for i := 0; i < len(a) && i < len(b); i++ { if a[i] >= 'A' && a[i] <= 'Z' { if b[i] >= 'A' && b[i] <= 'Z' { // both are uppercase, do nothing if a[i] < b[i] { return true } else if a[i] > b[i] { return false } } else { // a is uppercase, convert a to lowercase if a[i]+32 < b[i] { return true } else if a[i]+32 > b[i] { return false } } } else if b[i] >= 'A' && b[i] <= 'Z' { // b is uppercase, convert b to lowercase if a[i] < b[i]+32 { return true } else if a[i] > b[i]+32 { return false } } else { // neither are uppercase if a[i] < b[i] { return true } else if a[i] > b[i] { 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 } // IndexJSON provides for the ability to create an index on any JSON field. // When the field is a string, the comparison will be case-insensitive. // It returns a helper function used by CreateIndex. func IndexJSON(path string) func(a, b string) bool { return func(a, b string) bool { return gjson.Get(a, path).Less(gjson.Get(b, path), false) } } // IndexJSONCaseSensitive provides for the ability to create an index on // any JSON field. // When the field is a string, the comparison will be case-sensitive. // It returns a helper function used by CreateIndex. func IndexJSONCaseSensitive(path string) func(a, b string) bool { return func(a, b string) bool { return gjson.Get(a, path).Less(gjson.Get(b, path), true) } } // Desc is a helper function that changes the order of an index. func Desc(less func(a, b string) bool) func(a, b string) bool { return func(a, b string) bool { return less(b, a) } } //// Wrappers around btree Ascend/Descend func bLT(tr *btree.BTree, a, b interface{}) bool { return tr.Less(a, b) } func bGT(tr *btree.BTree, a, b interface{}) bool { return tr.Less(b, a) } // func bLTE(tr *btree.BTree, a, b interface{}) bool { return !tr.Less(b, a) } // func bGTE(tr *btree.BTree, a, b interface{}) bool { return !tr.Less(a, b) } // Ascend func btreeAscend(tr *btree.BTree, iter func(item interface{}) bool) { tr.Ascend(nil, iter) } func btreeAscendLessThan(tr *btree.BTree, pivot interface{}, iter func(item interface{}) bool, ) { tr.Ascend(nil, func(item interface{}) bool { return bLT(tr, item, pivot) && iter(item) }) } func btreeAscendGreaterOrEqual(tr *btree.BTree, pivot interface{}, iter func(item interface{}) bool, ) { tr.Ascend(pivot, iter) } func btreeAscendRange(tr *btree.BTree, greaterOrEqual, lessThan interface{}, iter func(item interface{}) bool, ) { tr.Ascend(greaterOrEqual, func(item interface{}) bool { return bLT(tr, item, lessThan) && iter(item) }) } // Descend func btreeDescend(tr *btree.BTree, iter func(item interface{}) bool) { tr.Descend(nil, iter) } func btreeDescendGreaterThan(tr *btree.BTree, pivot interface{}, iter func(item interface{}) bool, ) { tr.Descend(nil, func(item interface{}) bool { return bGT(tr, item, pivot) && iter(item) }) } func btreeDescendRange(tr *btree.BTree, lessOrEqual, greaterThan interface{}, iter func(item interface{}) bool, ) { tr.Descend(lessOrEqual, func(item interface{}) bool { return bGT(tr, item, greaterThan) && iter(item) }) } func btreeDescendLessOrEqual(tr *btree.BTree, pivot interface{}, iter func(item interface{}) bool, ) { tr.Descend(pivot, iter) } buntdb-1.1.7/buntdb_test.go000066400000000000000000002007271377660341000156610ustar00rootroot00000000000000package buntdb import ( "bytes" "errors" "fmt" "io/ioutil" "math/rand" "os" "strconv" "strings" "sync" "testing" "time" ) func testOpen(t testing.TB) *DB { if err := os.RemoveAll("data.db"); err != nil { t.Fatal(err) } return testReOpen(t, nil) } func testReOpen(t testing.TB, db *DB) *DB { return testReOpenDelay(t, db, 0) } func testReOpenDelay(t testing.TB, db *DB, dur time.Duration) *DB { if db != nil { if err := db.Close(); err != nil { t.Fatal(err) } } time.Sleep(dur) db, err := Open("data.db") if err != nil { t.Fatal(err) } return db } func testClose(db *DB) { _ = db.Close() _ = os.RemoveAll("data.db") } func TestBackgroudOperations(t *testing.T) { db := testOpen(t) defer testClose(db) for i := 0; i < 1000; i++ { if err := db.Update(func(tx *Tx) error { for j := 0; j < 200; j++ { if _, _, err := tx.Set(fmt.Sprintf("hello%d", j), "planet", nil); err != nil { return err } } if _, _, err := tx.Set("hi", "world", &SetOptions{Expires: true, TTL: time.Second / 2}); err != nil { return err } return nil }); err != nil { t.Fatal(err) } } n := 0 err := db.View(func(tx *Tx) error { var err error n, err = tx.Len() return err }) if err != nil { t.Fatal(err) } if n != 201 { t.Fatalf("expecting '%v', got '%v'", 201, n) } time.Sleep(time.Millisecond * 1500) db = testReOpen(t, db) defer testClose(db) n = 0 err = db.View(func(tx *Tx) error { var err error n, err = tx.Len() return err }) if err != nil { t.Fatal(err) } if n != 200 { t.Fatalf("expecting '%v', got '%v'", 200, n) } } func TestSaveLoad(t *testing.T) { db, _ := Open(":memory:") defer db.Close() if err := db.Update(func(tx *Tx) error { for i := 0; i < 20; i++ { _, _, err := tx.Set(fmt.Sprintf("key:%d", i), fmt.Sprintf("planet:%d", i), nil) if err != nil { return err } } return nil }); err != nil { t.Fatal(err) } f, err := os.Create("temp.db") if err != nil { t.Fatal(err) } defer func() { f.Close() os.RemoveAll("temp.db") }() if err := db.Save(f); err != nil { t.Fatal(err) } if err := f.Close(); err != nil { t.Fatal(err) } db.Close() db, _ = Open(":memory:") defer db.Close() f, err = os.Open("temp.db") if err != nil { t.Fatal(err) } defer f.Close() if err := db.Load(f); err != nil { t.Fatal(err) } if err := db.View(func(tx *Tx) error { for i := 0; i < 20; i++ { ex := fmt.Sprintf("planet:%d", i) val, err := tx.Get(fmt.Sprintf("key:%d", i)) if err != nil { return err } if ex != val { t.Fatalf("expected %s, got %s", ex, val) } } return nil }); err != nil { t.Fatal(err) } } func TestMutatingIterator(t *testing.T) { db := testOpen(t) defer testClose(db) count := 1000 if err := db.CreateIndex("ages", "user:*:age", IndexInt); err != nil { t.Fatal(err) } for i := 0; i < 10; i++ { if err := db.Update(func(tx *Tx) error { for j := 0; j < count; j++ { key := fmt.Sprintf("user:%d:age", j) val := fmt.Sprintf("%d", rand.Intn(100)) if _, _, err := tx.Set(key, val, nil); err != nil { return err } } return nil }); err != nil { t.Fatal(err) } if err := db.Update(func(tx *Tx) error { return tx.Ascend("ages", func(key, val string) bool { _, err := tx.Delete(key) if err != ErrTxIterating { t.Fatal("should not be able to call Delete while iterating.") } _, _, err = tx.Set(key, "", nil) if err != ErrTxIterating { t.Fatal("should not be able to call Set while iterating.") } return true }) }); err != nil { t.Fatal(err) } } } func TestCaseInsensitiveIndex(t *testing.T) { db := testOpen(t) defer testClose(db) count := 1000 if err := db.Update(func(tx *Tx) error { opts := &IndexOptions{ CaseInsensitiveKeyMatching: true, } return tx.CreateIndexOptions("ages", "User:*:age", opts, IndexInt) }); err != nil { t.Fatal(err) } if err := db.Update(func(tx *Tx) error { for j := 0; j < count; j++ { key := fmt.Sprintf("user:%d:age", j) val := fmt.Sprintf("%d", rand.Intn(100)) if _, _, err := tx.Set(key, val, nil); err != nil { return err } } return nil }); err != nil { t.Fatal(err) } if err := db.View(func(tx *Tx) error { var vals []string err := tx.Ascend("ages", func(key, value string) bool { vals = append(vals, value) return true }) if err != nil { return err } if len(vals) != count { return fmt.Errorf("expected '%v', got '%v'", count, len(vals)) } return nil }); err != nil { t.Fatal(err) } } func TestIndexTransaction(t *testing.T) { db := testOpen(t) defer testClose(db) var errFine = errors.New("this is fine") ascend := func(tx *Tx, index string) ([]string, error) { var vals []string if err := tx.Ascend(index, func(key, val string) bool { vals = append(vals, key, val) return true }); err != nil { return nil, err } return vals, nil } ascendEqual := func(tx *Tx, index string, vals []string) error { vals2, err := ascend(tx, index) if err != nil { return err } if len(vals) != len(vals2) { return errors.New("invalid size match") } for i := 0; i < len(vals); i++ { if vals[i] != vals2[i] { return errors.New("invalid order") } } return nil } // test creating an index and adding items if err := db.Update(func(tx *Tx) error { tx.Set("1", "3", nil) tx.Set("2", "2", nil) tx.Set("3", "1", nil) if err := tx.CreateIndex("idx1", "*", IndexInt); err != nil { return err } if err := ascendEqual(tx, "idx1", []string{"3", "1", "2", "2", "1", "3"}); err != nil { return err } return nil }); err != nil { t.Fatal(err) } // test to see if the items persisted from previous transaction // test add item. // test force rollback. if err := db.Update(func(tx *Tx) error { if err := ascendEqual(tx, "idx1", []string{"3", "1", "2", "2", "1", "3"}); err != nil { return err } tx.Set("4", "0", nil) if err := ascendEqual(tx, "idx1", []string{"4", "0", "3", "1", "2", "2", "1", "3"}); err != nil { return err } return errFine }); err != errFine { t.Fatalf("expected '%v', got '%v'", errFine, err) } // test to see if the rollback happened if err := db.View(func(tx *Tx) error { if err := ascendEqual(tx, "idx1", []string{"3", "1", "2", "2", "1", "3"}); err != nil { return err } return nil }); err != nil { t.Fatalf("expected '%v', got '%v'", nil, err) } // del item, drop index, rollback if err := db.Update(func(tx *Tx) error { if err := tx.DropIndex("idx1"); err != nil { return err } return errFine }); err != errFine { t.Fatalf("expected '%v', got '%v'", errFine, err) } // test to see if the rollback happened if err := db.View(func(tx *Tx) error { if err := ascendEqual(tx, "idx1", []string{"3", "1", "2", "2", "1", "3"}); err != nil { return err } return nil }); err != nil { t.Fatalf("expected '%v', got '%v'", nil, err) } various := func(reterr error) error { // del item 3, add index 2, add item 4, test index 1 and 2. // flushdb, test index 1 and 2. // add item 1 and 2, add index 2 and 3, test index 2 and 3 return db.Update(func(tx *Tx) error { tx.Delete("3") tx.CreateIndex("idx2", "*", IndexInt) tx.Set("4", "0", nil) if err := ascendEqual(tx, "idx1", []string{"4", "0", "2", "2", "1", "3"}); err != nil { return fmt.Errorf("err: %v", err) } if err := ascendEqual(tx, "idx2", []string{"4", "0", "2", "2", "1", "3"}); err != nil { return fmt.Errorf("err: %v", err) } tx.DeleteAll() if err := ascendEqual(tx, "idx1", []string{}); err != nil { return fmt.Errorf("err: %v", err) } if err := ascendEqual(tx, "idx2", []string{}); err != nil { return fmt.Errorf("err: %v", err) } tx.Set("1", "3", nil) tx.Set("2", "2", nil) tx.CreateIndex("idx1", "*", IndexInt) tx.CreateIndex("idx2", "*", IndexInt) if err := ascendEqual(tx, "idx1", []string{"2", "2", "1", "3"}); err != nil { return fmt.Errorf("err: %v", err) } if err := ascendEqual(tx, "idx2", []string{"2", "2", "1", "3"}); err != nil { return fmt.Errorf("err: %v", err) } return reterr }) } // various rollback if err := various(errFine); err != errFine { t.Fatalf("expected '%v', got '%v'", errFine, err) } // test to see if the rollback happened if err := db.View(func(tx *Tx) error { if err := ascendEqual(tx, "idx1", []string{"3", "1", "2", "2", "1", "3"}); err != nil { return fmt.Errorf("err: %v", err) } if err := ascendEqual(tx, "idx2", []string{"3", "1", "2", "2", "1", "3"}); err != ErrNotFound { return fmt.Errorf("err: %v", err) } return nil }); err != nil { t.Fatalf("expected '%v', got '%v'", nil, err) } // various commit if err := various(nil); err != nil { t.Fatalf("expected '%v', got '%v'", nil, err) } // test to see if the commit happened if err := db.View(func(tx *Tx) error { if err := ascendEqual(tx, "idx1", []string{"2", "2", "1", "3"}); err != nil { return fmt.Errorf("err: %v", err) } if err := ascendEqual(tx, "idx2", []string{"2", "2", "1", "3"}); err != nil { return fmt.Errorf("err: %v", err) } return nil }); err != nil { t.Fatalf("expected '%v', got '%v'", nil, err) } } func TestDeleteAll(t *testing.T) { db := testOpen(t) defer testClose(db) db.Update(func(tx *Tx) error { tx.Set("hello1", "planet1", nil) tx.Set("hello2", "planet2", nil) tx.Set("hello3", "planet3", nil) return nil }) db.CreateIndex("all", "*", IndexString) db.Update(func(tx *Tx) error { tx.Set("hello1", "planet1.1", nil) tx.DeleteAll() tx.Set("bb", "11", nil) tx.Set("aa", "**", nil) tx.Delete("aa") tx.Set("aa", "22", nil) return nil }) var res string var res2 string db.View(func(tx *Tx) error { tx.Ascend("", func(key, val string) bool { res += key + ":" + val + "\n" return true }) tx.Ascend("all", func(key, val string) bool { res2 += key + ":" + val + "\n" return true }) return nil }) if res != "aa:22\nbb:11\n" { t.Fatal("fail") } if res2 != "bb:11\naa:22\n" { t.Fatal("fail") } db = testReOpen(t, db) defer testClose(db) res = "" res2 = "" db.CreateIndex("all", "*", IndexString) db.View(func(tx *Tx) error { tx.Ascend("", func(key, val string) bool { res += key + ":" + val + "\n" return true }) tx.Ascend("all", func(key, val string) bool { res2 += key + ":" + val + "\n" return true }) return nil }) if res != "aa:22\nbb:11\n" { t.Fatal("fail") } if res2 != "bb:11\naa:22\n" { t.Fatal("fail") } db.Update(func(tx *Tx) error { tx.Set("1", "1", nil) tx.Set("2", "2", nil) tx.Set("3", "3", nil) tx.Set("4", "4", nil) return nil }) err := db.Update(func(tx *Tx) error { tx.Set("1", "a", nil) tx.Set("5", "5", nil) tx.Delete("2") tx.Set("6", "6", nil) tx.DeleteAll() tx.Set("7", "7", nil) tx.Set("8", "8", nil) tx.Set("6", "c", nil) return errors.New("please rollback") }) if err == nil || err.Error() != "please rollback" { t.Fatal("expecteding 'please rollback' error") } res = "" res2 = "" db.View(func(tx *Tx) error { tx.Ascend("", func(key, val string) bool { res += key + ":" + val + "\n" return true }) tx.Ascend("all", func(key, val string) bool { res2 += key + ":" + val + "\n" return true }) return nil }) if res != "1:1\n2:2\n3:3\n4:4\naa:22\nbb:11\n" { t.Fatal("fail") } if res2 != "1:1\nbb:11\n2:2\naa:22\n3:3\n4:4\n" { t.Fatal("fail") } } func TestAscendEqual(t *testing.T) { db := testOpen(t) defer testClose(db) if err := db.Update(func(tx *Tx) error { for i := 0; i < 300; i++ { _, _, err := tx.Set(fmt.Sprintf("key:%05dA", i), fmt.Sprintf("%d", i+1000), nil) if err != nil { return err } _, _, err = tx.Set(fmt.Sprintf("key:%05dB", i), fmt.Sprintf("%d", i+1000), nil) if err != nil { return err } } return tx.CreateIndex("num", "*", IndexInt) }); err != nil { t.Fatal(err) } var res []string if err := db.View(func(tx *Tx) error { return tx.AscendEqual("", "key:00055A", func(key, value string) bool { res = append(res, key) return true }) }); err != nil { t.Fatal(err) } if len(res) != 1 { t.Fatalf("expected %v, got %v", 1, len(res)) } if res[0] != "key:00055A" { t.Fatalf("expected %v, got %v", "key:00055A", res[0]) } res = nil if err := db.View(func(tx *Tx) error { return tx.AscendEqual("num", "1125", func(key, value string) bool { res = append(res, key) return true }) }); err != nil { t.Fatal(err) } if len(res) != 2 { t.Fatalf("expected %v, got %v", 2, len(res)) } if res[0] != "key:00125A" { t.Fatalf("expected %v, got %v", "key:00125A", res[0]) } if res[1] != "key:00125B" { t.Fatalf("expected %v, got %v", "key:00125B", res[1]) } } func TestDescendEqual(t *testing.T) { db := testOpen(t) defer testClose(db) if err := db.Update(func(tx *Tx) error { for i := 0; i < 300; i++ { _, _, err := tx.Set(fmt.Sprintf("key:%05dA", i), fmt.Sprintf("%d", i+1000), nil) if err != nil { return err } _, _, err = tx.Set(fmt.Sprintf("key:%05dB", i), fmt.Sprintf("%d", i+1000), nil) if err != nil { return err } } return tx.CreateIndex("num", "*", IndexInt) }); err != nil { t.Fatal(err) } var res []string if err := db.View(func(tx *Tx) error { return tx.DescendEqual("", "key:00055A", func(key, value string) bool { res = append(res, key) return true }) }); err != nil { t.Fatal(err) } if len(res) != 1 { t.Fatalf("expected %v, got %v", 1, len(res)) } if res[0] != "key:00055A" { t.Fatalf("expected %v, got %v", "key:00055A", res[0]) } res = nil if err := db.View(func(tx *Tx) error { return tx.DescendEqual("num", "1125", func(key, value string) bool { res = append(res, key) return true }) }); err != nil { t.Fatal(err) } if len(res) != 2 { t.Fatalf("expected %v, got %v", 2, len(res)) } if res[0] != "key:00125B" { t.Fatalf("expected %v, got %v", "key:00125B", res[0]) } if res[1] != "key:00125A" { t.Fatalf("expected %v, got %v", "key:00125A", res[1]) } } func TestVariousTx(t *testing.T) { db := testOpen(t) defer testClose(db) if err := db.Update(func(tx *Tx) error { _, _, err := tx.Set("hello", "planet", nil) return err }); 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 err := db.View(func(tx *Tx) error { var err error val, err = tx.Get("hello") return err }) if err != nil { t.Fatal(err) } if val == "world" { t.Fatal("a rollbacked transaction got through") } if val != "planet" { t.Fatalf("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") } if _, _, err := tx.Set("var", "val", &SetOptions{Expires: true, TTL: 0}); err != nil { t.Fatal(err) } 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 non-managed transactions tx, err := db.Begin(true) if err != nil { t.Fatal(err) } defer tx.Rollback() _, _, err = tx.Set("howdy", "world", nil) if err != nil { t.Fatal(err) } if err := tx.Commit(); err != nil { t.Fatal(err) } tx, err = db.Begin(false) if err != nil { t.Fatal(err) } defer tx.Rollback() v, err := tx.Get("howdy") if err != nil { t.Fatal(err) } if v != "world" { t.Fatalf("expecting '%v', got '%v'", "world", v) } if err := tx.Rollback(); err != nil { t.Fatal(err) } tx, err = db.Begin(true) if err != nil { t.Fatal(err) } defer tx.Rollback() v, err = tx.Get("howdy") if err != nil { t.Fatal(err) } if v != "world" { t.Fatalf("expecting '%v', got '%v'", "world", v) } _, err = tx.Delete("howdy") if err != nil { t.Fatal(err) } if err := tx.Commit(); 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)) } } }() return tx.Commit() }); 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)) } } }() return tx.Rollback() }); 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 { _, _, err := tx.Set("var1", "val1", nil) if err != nil { t.Fatal(err) } return tx.db.file.Close() }); 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.buf = nil if err := db.CreateIndex("blank", "*", nil); err != nil { t.Fatal(err) } if err := db.CreateIndex("real", "*", IndexInt); err != nil { t.Fatal(err) } // test scanning if err := db.Update(func(tx *Tx) error { less, err := tx.GetLess("junk") if err != ErrNotFound { t.Fatalf("expecting a not found, got %v", err) } if less != nil { t.Fatal("expecting nil, got a less function") } less, err = tx.GetLess("blank") if err != ErrNotFound { t.Fatalf("expecting a not found, got %v", err) } if less != nil { t.Fatal("expecting nil, got a less function") } less, err = tx.GetLess("real") if err != nil { return err } if less == nil { t.Fatal("expecting a less function, got nil") } _, _, err = tx.Set("nothing", "here", nil) return err }); err != nil { t.Fatal(err) } if err := db.View(func(tx *Tx) error { s := "" err := tx.Ascend("", func(key, val string) bool { s += key + ":" + val + "\n" return true }) if err != nil { return err } 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 = "" err = tx.AscendLessThan("", "liger", func(key, val string) bool { s += key + ":" + val + "\n" return true }) if err != nil { return err } if s != "hello:planet\n" { t.Fatal("invalid scan") } s = "" err = tx.Descend("", func(key, val string) bool { s += key + ":" + val + "\n" return true }) if err != nil { return err } if s != "nothing:here\nhello:planet\n" { t.Fatal("invalid scan") } s = "" err = tx.DescendLessOrEqual("", "liger", func(key, val string) bool { s += key + ":" + val + "\n" return true }) if err != nil { return err } if s != "hello:planet\n" { t.Fatal("invalid scan") } s = "" err = tx.DescendGreaterThan("", "liger", func(key, val string) bool { s += key + ":" + val + "\n" return true }) if err != nil { return err } if s != "nothing:here\n" { t.Fatal("invalid scan") } s = "" err = tx.DescendRange("", "liger", "apple", func(key, val string) bool { s += key + ":" + val + "\n" return true }) if err != nil { return err } if s != "hello:planet\n" { t.Fatal("invalid scan") } return nil }); err != nil { t.Fatal(err) } // test some spatial stuff if err := db.CreateSpatialIndex("spat", "rect:*", IndexRect); err != nil { t.Fatal(err) } if err := db.CreateSpatialIndex("junk", "rect:*", nil); err != nil { t.Fatal(err) } err = db.Update(func(tx *Tx) error { rect, err := tx.GetRect("spat") if err != nil { return err } if rect == nil { t.Fatal("expecting a rect function, got nil") } rect, err = tx.GetRect("junk") if err != ErrNotFound { t.Fatalf("expecting a not found, got %v", err) } if rect != nil { t.Fatal("expecting nil, got a rect function") } rect, err = tx.GetRect("na") if err != ErrNotFound { t.Fatalf("expecting a not found, got %v", err) } if rect != nil { t.Fatal("expecting nil, got a rect function") } if _, _, err := tx.Set("rect:1", "[10 10],[20 20]", nil); err != nil { return err } if _, _, err := tx.Set("rect:2", "[15 15],[25 25]", nil); err != nil { return err } if _, _, err := tx.Set("shape:1", "[12 12],[25 25]", nil); err != nil { return err } s := "" err = tx.Intersects("spat", "[5 5],[13 13]", func(key, val string) bool { s += key + ":" + val + "\n" return true }) if err != nil { return err } 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.Fatalf("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 }) if err != nil { t.Fatal(err) } // test after closing if err := db.Close(); err != nil { t.Fatal(err) } 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 TestNearby(t *testing.T) { rand.Seed(time.Now().UnixNano()) N := 100000 db, _ := Open(":memory:") db.CreateSpatialIndex("points", "*", IndexRect) db.Update(func(tx *Tx) error { for i := 0; i < N; i++ { p := Point( rand.Float64()*100, rand.Float64()*100, rand.Float64()*100, rand.Float64()*100, ) tx.Set(fmt.Sprintf("p:%d", i), p, nil) } return nil }) var keys, values []string var dists []float64 var pdist float64 var i int db.View(func(tx *Tx) error { tx.Nearby("points", Point(0, 0, 0, 0), func(key, value string, dist float64) bool { if i != 0 && dist < pdist { t.Fatal("out of order") } keys = append(keys, key) values = append(values, value) dists = append(dists, dist) pdist = dist i++ return true }) return nil }) if len(keys) != N { t.Fatalf("expected '%v', got '%v'", N, len(keys)) } } func Example_descKeys() { db, _ := Open(":memory:") db.CreateIndex("name", "*", IndexString) db.Update(func(tx *Tx) error { tx.Set("user:100:first", "Tom", nil) tx.Set("user:100:last", "Johnson", nil) tx.Set("user:101:first", "Janet", nil) tx.Set("user:101:last", "Prichard", nil) tx.Set("user:102:first", "Alan", nil) tx.Set("user:102:last", "Cooper", nil) return nil }) db.View(func(tx *Tx) error { tx.AscendKeys("user:101:*", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) tx.AscendKeys("user:10?:*", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) tx.AscendKeys("*2*", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) tx.DescendKeys("user:101:*", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) tx.DescendKeys("*", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) return nil }) // Output: // user:101:first: Janet // user:101:last: Prichard // user:100:first: Tom // user:100:last: Johnson // user:101:first: Janet // user:101:last: Prichard // user:102:first: Alan // user:102:last: Cooper // user:102:first: Alan // user:102:last: Cooper // user:101:last: Prichard // user:101:first: Janet // user:102:last: Cooper // user:102:first: Alan // user:101:last: Prichard // user:101:first: Janet // user:100:last: Johnson // user:100:first: Tom } func ExampleDesc() { db, _ := Open(":memory:") db.CreateIndex("last_name_age", "*", IndexJSON("name.last"), Desc(IndexJSON("age"))) db.Update(func(tx *Tx) error { tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":38}`, nil) tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":47}`, nil) tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":52}`, nil) tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":28}`, nil) tx.Set("5", `{"name":{"first":"Sam","last":"Anderson"},"age":51}`, nil) tx.Set("6", `{"name":{"first":"Melinda","last":"Prichard"},"age":44}`, nil) return nil }) db.View(func(tx *Tx) error { tx.Ascend("last_name_age", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) return nil }) // Output: //3: {"name":{"first":"Carol","last":"Anderson"},"age":52} //5: {"name":{"first":"Sam","last":"Anderson"},"age":51} //4: {"name":{"first":"Alan","last":"Cooper"},"age":28} //1: {"name":{"first":"Tom","last":"Johnson"},"age":38} //2: {"name":{"first":"Janet","last":"Prichard"},"age":47} //6: {"name":{"first":"Melinda","last":"Prichard"},"age":44} } func ExampleDB_CreateIndex_jSON() { db, _ := Open(":memory:") db.CreateIndex("last_name", "*", IndexJSON("name.last")) db.CreateIndex("age", "*", IndexJSON("age")) db.Update(func(tx *Tx) error { tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":38}`, nil) tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":47}`, nil) tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":52}`, nil) tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":28}`, nil) return nil }) db.View(func(tx *Tx) error { fmt.Println("Order by last name") tx.Ascend("last_name", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) fmt.Println("Order by age") tx.Ascend("age", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) fmt.Println("Order by age range 30-50") tx.AscendRange("age", `{"age":30}`, `{"age":50}`, func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) return nil }) // Output: // Order by last name // 3: {"name":{"first":"Carol","last":"Anderson"},"age":52} // 4: {"name":{"first":"Alan","last":"Cooper"},"age":28} // 1: {"name":{"first":"Tom","last":"Johnson"},"age":38} // 2: {"name":{"first":"Janet","last":"Prichard"},"age":47} // Order by age // 4: {"name":{"first":"Alan","last":"Cooper"},"age":28} // 1: {"name":{"first":"Tom","last":"Johnson"},"age":38} // 2: {"name":{"first":"Janet","last":"Prichard"},"age":47} // 3: {"name":{"first":"Carol","last":"Anderson"},"age":52} // Order by age range 30-50 // 1: {"name":{"first":"Tom","last":"Johnson"},"age":38} // 2: {"name":{"first":"Janet","last":"Prichard"},"age":47} } func ExampleDB_CreateIndex_strings() { db, _ := Open(":memory:") db.CreateIndex("name", "*", IndexString) db.Update(func(tx *Tx) error { tx.Set("1", "Tom", nil) tx.Set("2", "Janet", nil) tx.Set("3", "Carol", nil) tx.Set("4", "Alan", nil) tx.Set("5", "Sam", nil) tx.Set("6", "Melinda", nil) return nil }) db.View(func(tx *Tx) error { tx.Ascend("name", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) return nil }) // Output: //4: Alan //3: Carol //2: Janet //6: Melinda //5: Sam //1: Tom } func ExampleDB_CreateIndex_ints() { db, _ := Open(":memory:") db.CreateIndex("age", "*", IndexInt) db.Update(func(tx *Tx) error { tx.Set("1", "30", nil) tx.Set("2", "51", nil) tx.Set("3", "16", nil) tx.Set("4", "76", nil) tx.Set("5", "23", nil) tx.Set("6", "43", nil) return nil }) db.View(func(tx *Tx) error { tx.Ascend("age", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) return nil }) // Output: //3: 16 //5: 23 //1: 30 //6: 43 //2: 51 //4: 76 } func ExampleDB_CreateIndex_multipleFields() { db, _ := Open(":memory:") db.CreateIndex("last_name_age", "*", IndexJSON("name.last"), IndexJSON("age")) db.Update(func(tx *Tx) error { tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":38}`, nil) tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":47}`, nil) tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":52}`, nil) tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":28}`, nil) tx.Set("5", `{"name":{"first":"Sam","last":"Anderson"},"age":51}`, nil) tx.Set("6", `{"name":{"first":"Melinda","last":"Prichard"},"age":44}`, nil) return nil }) db.View(func(tx *Tx) error { tx.Ascend("last_name_age", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) return nil }) // Output: //5: {"name":{"first":"Sam","last":"Anderson"},"age":51} //3: {"name":{"first":"Carol","last":"Anderson"},"age":52} //4: {"name":{"first":"Alan","last":"Cooper"},"age":28} //1: {"name":{"first":"Tom","last":"Johnson"},"age":38} //6: {"name":{"first":"Melinda","last":"Prichard"},"age":44} //2: {"name":{"first":"Janet","last":"Prichard"},"age":47} } 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") } } func TestAutoShrink(t *testing.T) { db := testOpen(t) defer testClose(db) for i := 0; i < 1000; i++ { err := db.Update(func(tx *Tx) error { for i := 0; i < 20; i++ { if _, _, err := tx.Set(fmt.Sprintf("HELLO:%d", i), "WORLD", nil); err != nil { return err } } return nil }) if err != nil { t.Fatal(err) } } db = testReOpen(t, db) defer testClose(db) db.config.AutoShrinkMinSize = 64 * 1024 // 64K for i := 0; i < 2000; i++ { err := db.Update(func(tx *Tx) error { for i := 0; i < 20; i++ { if _, _, err := tx.Set(fmt.Sprintf("HELLO:%d", i), "WORLD", nil); err != nil { return err } } return nil }) if err != nil { t.Fatal(err) } } time.Sleep(time.Second * 3) db = testReOpen(t, db) defer testClose(db) err := db.View(func(tx *Tx) error { n, err := tx.Len() if err != nil { return err } if n != 20 { t.Fatalf("expecting 20, got %v", n) } return nil }) if err != nil { t.Fatal(err) } } // 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", }, "") if err := os.RemoveAll("data.db"); err != nil { t.Fatal(err) } if err := ioutil.WriteFile("data.db", []byte(resp), 0666); err != nil { t.Fatal(err) } db := testOpen(t) defer testClose(db) }() testBadFormat := func(resp string) { if err := os.RemoveAll("data.db"); err != nil { t.Fatal(err) } if err := ioutil.WriteFile("data.db", []byte(resp), 0666); err != nil { t.Fatal(err) } db, err := Open("data.db") if err == nil { if err := db.Close(); err != nil { t.Fatal(err) } if err := os.RemoveAll("data.db"); err != nil { t.Fatal(err) } 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") testBadFormat("*15\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") testBadFormat("*1A\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") testBadFormat("*5\r\n$13\r\nset\r\n$3\r\nvar\r\n$3\r\nval\r\n$2\r\nex\r\n$2\r\naa\r\n") testBadFormat("*5\r\n$1A\r\nset\r\n$3\r\nvar\r\n$3\r\nval\r\n$2\r\nex\r\n$2\r\naa\r\n") testBadFormat("*5\r\n$3\r\nset\r\n$5000\r\nvar\r\n$3\r\nval\r\n$2\r\nex\r\n$2\r\naa\r\n") } func TestInsertsAndDeleted(t *testing.T) { db := testOpen(t) defer testClose(db) if err := db.CreateIndex("any", "*", IndexString); err != nil { t.Fatal(err) } if err := db.CreateSpatialIndex("rect", "*", IndexRect); err != nil { t.Fatal(err) } if err := db.Update(func(tx *Tx) error { if _, _, err := tx.Set("item1", "value1", &SetOptions{Expires: true, TTL: time.Second}); err != nil { return err } if _, _, err := tx.Set("item2", "value2", nil); err != nil { return err } if _, _, err := tx.Set("item3", "value3", &SetOptions{Expires: true, TTL: time.Second}); err != nil { return err } 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) { if err := os.RemoveAll("dir.tmp"); err != nil { t.Fatal(err) } if err := os.Mkdir("dir.tmp", 0700); err != nil { t.Fatal(err) } defer func() { _ = os.RemoveAll("dir.tmp") }() db, err := Open("dir.tmp") if err == nil { if err := db.Close(); err != nil { t.Fatal(err) } t.Fatalf("opening a directory should not be allowed") } } // test opening an invalid resp file. func TestOpeningInvalidDatabaseFile(t *testing.T) { if err := os.RemoveAll("data.db"); err != nil { t.Fatal(err) } if err := ioutil.WriteFile("data.db", []byte("invalid\r\nfile"), 0666); err != nil { t.Fatal(err) } defer func() { _ = os.RemoveAll("data.db") }() db, err := Open("data.db") if err == nil { if err := db.Close(); err != nil { t.Fatal(err) } t.Fatalf("invalid database should not be allowed") } } // test closing a closed database. func TestOpeningClosedDatabase(t *testing.T) { if err := os.RemoveAll("data.db"); err != nil { t.Fatal(err) } db, err := Open("data.db") if err != nil { t.Fatal(err) } defer func() { _ = 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") } db, err = Open(":memory:") if err != nil { t.Fatal(err) } 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") } } // test shrinking a database. func TestShrink(t *testing.T) { db := testOpen(t) defer testClose(db) if err := db.Shrink(); err != nil { t.Fatal(err) } fi, err := os.Stat("data.db") if err != nil { t.Fatal(err) } if fi.Size() != 0 { t.Fatalf("expected %v, got %v", 0, fi.Size()) } // add 10 items err = db.Update(func(tx *Tx) error { for i := 0; i < 10; i++ { if _, _, err := tx.Set(fmt.Sprintf("key%d", i), fmt.Sprintf("val%d", i), nil); err != nil { return err } } return nil }) if err != nil { t.Fatal(err) } // add the same 10 items // this will create 10 duplicate log entries err = db.Update(func(tx *Tx) error { for i := 0; i < 10; i++ { if _, _, err := tx.Set(fmt.Sprintf("key%d", i), fmt.Sprintf("val%d", i), nil); err != nil { return err } } return nil }) if err != nil { t.Fatal(err) } fi, err = os.Stat("data.db") if err != nil { t.Fatal(err) } sz1 := fi.Size() if sz1 == 0 { t.Fatalf("expected > 0, got %v", sz1) } if err := db.Shrink(); err != nil { t.Fatal(err) } fi, err = os.Stat("data.db") if err != nil { t.Fatal(err) } sz2 := fi.Size() if sz2 >= sz1 { t.Fatalf("expected < %v, got %v", sz1, sz2) } if err := db.Close(); err != nil { t.Fatal(err) } if err := db.Shrink(); err != ErrDatabaseClosed { t.Fatal("shrink on a closed databse should not be allowed") } // Now we will open a db that does not persist db, err = Open(":memory:") if err != nil { t.Fatal(err) } defer func() { _ = db.Close() }() // add 10 items err = db.Update(func(tx *Tx) error { for i := 0; i < 10; i++ { if _, _, err := tx.Set(fmt.Sprintf("key%d", i), fmt.Sprintf("val%d", i), nil); err != nil { return err } } return nil }) if err != nil { t.Fatal(err) } // add the same 10 items // this will create 10 duplicate log entries err = db.Update(func(tx *Tx) error { for i := 0; i < 10; i++ { if _, _, err := tx.Set(fmt.Sprintf("key%d", i), fmt.Sprintf("val%d", i), nil); err != nil { return err } } return nil }) if err != nil { t.Fatal(err) } err = db.View(func(tx *Tx) error { n, err := tx.Len() if err != nil { t.Fatal(err) } if n != 10 { t.Fatalf("expecting %v, got %v", 10, n) } return nil }) if err != nil { t.Fatal(err) } // this should succeed even though it's basically a noop. if err := db.Shrink(); err != nil { t.Fatal(err) } } func TestVariousIndexOperations(t *testing.T) { db := testOpen(t) defer testClose(db) // 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") } err = db.Update(func(tx *Tx) error { if _, _, err := tx.Set("user:1", "tom", nil); err != nil { return err } if _, _, err := tx.Set("user:2", "janet", nil); err != nil { return err } if _, _, err := tx.Set("alt:1", "from", nil); err != nil { return err } if _, _, err := tx.Set("alt:2", "there", nil); err != nil { return err } if _, _, err := tx.Set("rect:1", "[1 2],[3 4]", nil); err != nil { return err } if _, _, err := tx.Set("rect:2", "[5 6],[7 8]", nil); err != nil { return err } return nil }) if err != nil { t.Fatal(err) } // 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 TestBasic(t *testing.T) { rand.Seed(time.Now().UnixNano()) db := testOpen(t) defer testClose(db) // create a simple index if err := db.CreateIndex("users", "fun:user:*", IndexString); err != nil { t.Fatal(err) } // create a spatial index if err := db.CreateSpatialIndex("rects", "rect:*", IndexRect); err != nil { t.Fatal(err) } if true { err := db.Update(func(tx *Tx) error { if _, _, err := tx.Set("fun:user:0", "tom", nil); err != nil { return err } if _, _, err := tx.Set("fun:user:1", "Randi", nil); err != nil { return err } if _, _, err := tx.Set("fun:user:2", "jane", nil); err != nil { return err } if _, _, err := tx.Set("fun:user:4", "Janet", nil); err != nil { return err } if _, _, err := tx.Set("fun:user:5", "Paula", nil); err != nil { return err } if _, _, err := tx.Set("fun:user:6", "peter", nil); err != nil { return err } if _, _, err := tx.Set("fun:user:7", "Terri", nil); err != nil { return err } return nil }) if err != nil { t.Fatal(err) } // add some random items start := time.Now() if err := db.Update(func(tx *Tx) error { for _, i := range rand.Perm(100) { if _, _, err := tx.Set(fmt.Sprintf("tag:%d", i+100), fmt.Sprintf("val:%d", rand.Int()%100+100), nil); err != nil { return err } } 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 { if _, _, err := tx.Set("rect:1", Rect([]float64{10, 10}, []float64{20, 20}), nil); err != nil { return err } if _, _, err := tx.Set("rect:2", Rect([]float64{15, 15}, []float64{24, 24}), nil); err != nil { return err } if _, _, err := tx.Set("rect:3", Rect([]float64{17, 17}, []float64{27, 27}), nil); err != nil { return err } return nil }); err != nil { t.Fatal(err) } } // verify the data has been created buf := &bytes.Buffer{} err := db.View(func(tx *Tx) error { err := tx.Ascend("users", func(key, val string) bool { fmt.Fprintf(buf, "%s %s\n", key, val) return true }) if err != nil { t.Fatal(err) } 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 err = 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 }) if err != nil { t.Fatal(err) } for _, s := range expect { if _, err := buf.WriteString(s); err != nil { return err } } return nil }) if err != nil { t.Fatal(err) } 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 TestIndexAscend(t *testing.T) { rand.Seed(time.Now().UnixNano()) db := testOpen(t) defer testClose(db) // create a simple index if err := db.CreateIndex("usr", "usr:*", IndexInt); err != nil { t.Fatal(err) } if err := db.Update(func(tx *Tx) error { for i := 10; i > 0; i-- { tx.Set(fmt.Sprintf("usr:%d", i), fmt.Sprintf("%d", 10-i), nil) } return nil }); err != nil { t.Fatal(err) } buf := &bytes.Buffer{} err := db.View(func(tx *Tx) error { tx.Ascend("usr", func(key, value string) bool { fmt.Fprintf(buf, "%s %s\n", key, value) return true }) fmt.Fprintln(buf) tx.AscendGreaterOrEqual("usr", "8", func(key, value string) bool { fmt.Fprintf(buf, "%s %s\n", key, value) return true }) fmt.Fprintln(buf) tx.AscendLessThan("usr", "3", func(key, value string) bool { fmt.Fprintf(buf, "%s %s\n", key, value) return true }) fmt.Fprintln(buf) tx.AscendRange("usr", "4", "8", func(key, value string) bool { fmt.Fprintf(buf, "%s %s\n", key, value) return true }) return nil }) if err != nil { t.Fatal(err) } res := ` usr:10 0 usr:9 1 usr:8 2 usr:7 3 usr:6 4 usr:5 5 usr:4 6 usr:3 7 usr:2 8 usr:1 9 usr:2 8 usr:1 9 usr:10 0 usr:9 1 usr:8 2 usr:6 4 usr:5 5 usr:4 6 usr:3 7 ` res = strings.Replace(res, "\r", "", -1) s1 := strings.TrimSpace(buf.String()) s2 := strings.TrimSpace(res) if s1 != s2 { t.Fatalf("expected [%v], got [%v]", s1, s2) } } 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, 10}); err != nil { t.Fatal(err) } } // TestTTLReOpen test setting a TTL and then immediately closing the database and // then waiting the TTL before reopening. The key should not be accessible. func TestTTLReOpen(t *testing.T) { ttl := time.Second * 3 db := testOpen(t) defer testClose(db) err := db.Update(func(tx *Tx) error { if _, _, err := tx.Set("key1", "val1", &SetOptions{Expires: true, TTL: ttl}); err != nil { return err } return nil }) if err != nil { t.Fatal(err) } db = testReOpenDelay(t, db, ttl/4) err = db.View(func(tx *Tx) error { val, err := tx.Get("key1") if err != nil { return err } if val != "val1" { t.Fatalf("expecting '%v', got '%v'", "val1", val) } return nil }) if err != nil { t.Fatal(err) } db = testReOpenDelay(t, db, ttl-ttl/4) defer testClose(db) err = db.View(func(tx *Tx) error { val, err := tx.Get("key1") if err == nil || err != ErrNotFound || val != "" { t.Fatal("expecting not found") } return nil }) if err != nil { t.Fatal(err) } } func TestTTL(t *testing.T) { db := testOpen(t) defer testClose(db) err := db.Update(func(tx *Tx) error { if _, _, err := tx.Set("key1", "val1", &SetOptions{Expires: true, TTL: time.Second}); err != nil { return err } if _, _, err := tx.Set("key2", "val2", nil); err != nil { return err } return nil }) if err != nil { t.Fatal(err) } err = db.View(func(tx *Tx) error { dur1, err := tx.TTL("key1") if err != nil { t.Fatal(err) } if dur1 > time.Second || dur1 <= 0 { t.Fatalf("expecting between zero and one second, got '%v'", dur1) } dur1, err = tx.TTL("key2") if err != nil { t.Fatal(err) } if dur1 >= 0 { t.Fatalf("expecting a negative value, got '%v'", dur1) } return nil }) if err != nil { t.Fatal(err) } } func TestConfig(t *testing.T) { db := testOpen(t) defer testClose(db) err := db.SetConfig(Config{SyncPolicy: SyncPolicy(-1)}) if err == nil { t.Fatal("expecting a config syncpolicy error") } err = db.SetConfig(Config{SyncPolicy: SyncPolicy(3)}) if err == nil { t.Fatal("expecting a config syncpolicy error") } err = db.SetConfig(Config{SyncPolicy: Never}) if err != nil { t.Fatal(err) } err = db.SetConfig(Config{SyncPolicy: EverySecond}) if err != nil { t.Fatal(err) } err = db.SetConfig(Config{AutoShrinkMinSize: 100, AutoShrinkPercentage: 200, SyncPolicy: Always}) if err != nil { t.Fatal(err) } var c Config if err := db.ReadConfig(&c); err != nil { t.Fatal(err) } if c.AutoShrinkMinSize != 100 || c.AutoShrinkPercentage != 200 && c.SyncPolicy != Always { t.Fatalf("expecting %v, %v, and %v, got %v, %v, and %v", 100, 200, Always, c.AutoShrinkMinSize, c.AutoShrinkPercentage, c.SyncPolicy) } } func testUint64Hex(n uint64) string { s := strconv.FormatUint(n, 16) s = "0000000000000000" + s return s[len(s)-16:] } func textHexUint64(s string) uint64 { n, _ := strconv.ParseUint(s, 16, 64) return n } func benchClose(t *testing.B, persist bool, db *DB) { if persist { if err := os.RemoveAll("data.db"); err != nil { t.Fatal(err) } } if err := db.Close(); err != nil { t.Fatal(err) } } func benchOpenFillData(t *testing.B, N int, set, persist, random bool, geo bool, batch int) (db *DB, keys, vals []string) { /// t.StopTimer() rand.Seed(time.Now().UnixNano()) var err error if persist { if err := os.RemoveAll("data.db"); err != nil { t.Fatal(err) } db, err = Open("data.db") } else { db, err = Open(":memory:") } if err != nil { t.Fatal(err) } keys = make([]string, N) vals = make([]string, N) perm := rand.Perm(N) for i := 0; i < N; i++ { if random && set { keys[perm[i]] = testUint64Hex(uint64(i)) vals[perm[i]] = strconv.FormatInt(rand.Int63()%1000+1000, 10) } else { keys[i] = testUint64Hex(uint64(i)) vals[i] = strconv.FormatInt(rand.Int63()%1000+1000, 10) } } if set { t.StartTimer() } for i := 0; i < N; { err := db.Update(func(tx *Tx) error { var err error for j := 0; j < batch && i < N; j++ { _, _, err = tx.Set(keys[i], vals[i], nil) i++ } return err }) if err != nil { t.Fatal(err) } } if set { t.StopTimer() } var n uint64 err = db.View(func(tx *Tx) error { err := tx.Ascend("", func(key, value string) bool { n2 := textHexUint64(key) if n2 != n { t.Fatalf("expecting '%v', got '%v'", n2, n) } n++ return true }) return err }) if err != nil { t.Fatal(err) } if n != uint64(N) { t.Fatalf("expecting '%v', got '%v'", N, n) } t.StartTimer() return db, keys, vals } func benchSetGet(t *testing.B, set, persist, random bool, batch int) { N := t.N for N > 0 { n := 0 if N >= 100000 { n = 100000 } else { n = N } func() { db, keys, _ := benchOpenFillData(t, n, set, persist, random, false, batch) defer benchClose(t, persist, db) if !set { for i := 0; i < n; { err := db.View(func(tx *Tx) error { var err error for j := 0; j < batch && i < n; j++ { _, err = tx.Get(keys[i]) i++ } return err }) if err != nil { t.Fatal(err) } } } }() N -= n } } // Set Persist func Benchmark_Set_Persist_Random_1(t *testing.B) { benchSetGet(t, true, true, true, 1) } func Benchmark_Set_Persist_Random_10(t *testing.B) { benchSetGet(t, true, true, true, 10) } func Benchmark_Set_Persist_Random_100(t *testing.B) { benchSetGet(t, true, true, true, 100) } func Benchmark_Set_Persist_Sequential_1(t *testing.B) { benchSetGet(t, true, true, false, 1) } func Benchmark_Set_Persist_Sequential_10(t *testing.B) { benchSetGet(t, true, true, false, 10) } func Benchmark_Set_Persist_Sequential_100(t *testing.B) { benchSetGet(t, true, true, false, 100) } // Set NoPersist func Benchmark_Set_NoPersist_Random_1(t *testing.B) { benchSetGet(t, true, false, true, 1) } func Benchmark_Set_NoPersist_Random_10(t *testing.B) { benchSetGet(t, true, false, true, 10) } func Benchmark_Set_NoPersist_Random_100(t *testing.B) { benchSetGet(t, true, false, true, 100) } func Benchmark_Set_NoPersist_Sequential_1(t *testing.B) { benchSetGet(t, true, false, false, 1) } func Benchmark_Set_NoPersist_Sequential_10(t *testing.B) { benchSetGet(t, true, false, false, 10) } func Benchmark_Set_NoPersist_Sequential_100(t *testing.B) { benchSetGet(t, true, false, false, 100) } // Get func Benchmark_Get_1(t *testing.B) { benchSetGet(t, false, false, false, 1) } func Benchmark_Get_10(t *testing.B) { benchSetGet(t, false, false, false, 10) } func Benchmark_Get_100(t *testing.B) { benchSetGet(t, false, false, false, 100) } func benchScan(t *testing.B, asc bool, count int) { N := count db, _, _ := benchOpenFillData(t, N, false, false, false, false, 100) defer benchClose(t, false, db) for i := 0; i < t.N; i++ { count := 0 err := db.View(func(tx *Tx) error { if asc { return tx.Ascend("", func(key, val string) bool { count++ return true }) } return tx.Descend("", func(key, val string) bool { count++ return true }) }) if err != nil { t.Fatal(err) } if count != N { t.Fatalf("expecting '%v', got '%v'", N, count) } } } func Benchmark_Ascend_1(t *testing.B) { benchScan(t, true, 1) } func Benchmark_Ascend_10(t *testing.B) { benchScan(t, true, 10) } func Benchmark_Ascend_100(t *testing.B) { benchScan(t, true, 100) } func Benchmark_Ascend_1000(t *testing.B) { benchScan(t, true, 1000) } func Benchmark_Ascend_10000(t *testing.B) { benchScan(t, true, 10000) } func Benchmark_Descend_1(t *testing.B) { benchScan(t, false, 1) } func Benchmark_Descend_10(t *testing.B) { benchScan(t, false, 10) } func Benchmark_Descend_100(t *testing.B) { benchScan(t, false, 100) } func Benchmark_Descend_1000(t *testing.B) { benchScan(t, false, 1000) } func Benchmark_Descend_10000(t *testing.B) { benchScan(t, false, 10000) } /* func Benchmark_Spatial_2D(t *testing.B) { N := 100000 db, _, _ := benchOpenFillData(t, N, true, true, false, true, 100) defer benchClose(t, false, db) } */ func TestCoverCloseAlreadyClosed(t *testing.T) { db := testOpen(t) defer testClose(db) _ = db.file.Close() if err := db.Close(); err == nil { t.Fatal("expecting an error") } } func TestCoverConfigClosed(t *testing.T) { db := testOpen(t) defer testClose(db) _ = db.Close() var config Config if err := db.ReadConfig(&config); err != ErrDatabaseClosed { t.Fatal("expecting database closed error") } if err := db.SetConfig(config); err != ErrDatabaseClosed { t.Fatal("expecting database closed error") } } func TestCoverShrinkShrink(t *testing.T) { db := testOpen(t) defer testClose(db) if err := db.Update(func(tx *Tx) error { for i := 0; i < 10000; i++ { _, _, err := tx.Set(fmt.Sprintf("%d", i), fmt.Sprintf("%d", i), nil) if err != nil { return err } } return nil }); err != nil { t.Fatal(err) } if err := db.Update(func(tx *Tx) error { for i := 250; i < 250+100; i++ { _, err := tx.Delete(fmt.Sprintf("%d", i)) if err != nil { return err } } return nil }); err != nil { t.Fatal(err) } var err1, err2 error var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() err1 = db.Shrink() }() go func() { defer wg.Done() err2 = db.Shrink() }() wg.Wait() //println(123) //fmt.Printf("%v\n%v\n", err1, err2) if err1 != ErrShrinkInProcess && err2 != ErrShrinkInProcess { t.Fatal("expecting a shrink in process error") } db = testReOpen(t, db) defer testClose(db) if err := db.View(func(tx *Tx) error { n, err := tx.Len() if err != nil { return err } if n != 9900 { t.Fatal("expecting 9900 items") } return nil }); err != nil { t.Fatal(err) } } func TestPreviousItem(t *testing.T) { db := testOpen(t) defer testClose(db) err := db.Update(func(tx *Tx) error { _, _, err := tx.Set("hello", "world", nil) if err != nil { return err } prev, replaced, err := tx.Set("hello", "planet", nil) if err != nil { return err } if !replaced { t.Fatal("should be replaced") } if prev != "world" { t.Fatalf("expecting '%v', got '%v'", "world", prev) } return nil }) if err != nil { t.Fatal(err) } } func TestJSONIndex(t *testing.T) { db := testOpen(t) defer testClose(db) _ = db.CreateIndex("last_name", "*", IndexJSON("name.last")) _ = db.CreateIndex("last_name_cs", "*", IndexJSONCaseSensitive("name.last")) _ = db.CreateIndex("age", "*", IndexJSON("age")) _ = db.CreateIndex("student", "*", IndexJSON("student")) _ = db.Update(func(tx *Tx) error { _, _, _ = tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":38,"student":false}`, nil) _, _, _ = tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":47,"student":true}`, nil) _, _, _ = tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":52,"student":true}`, nil) _, _, _ = tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":28,"student":false}`, nil) _, _, _ = tx.Set("5", `{"name":{"first":"bill","last":"frank"},"age":21,"student":true}`, nil) _, _, _ = tx.Set("6", `{"name":{"first":"sally","last":"randall"},"age":68,"student":false}`, nil) return nil }) var keys []string _ = db.View(func(tx *Tx) error { _ = tx.Ascend("last_name_cs", func(key, value string) bool { //fmt.Printf("%s: %s\n", key, value) keys = append(keys, key) return true }) _ = tx.Ascend("last_name", func(key, value string) bool { //fmt.Printf("%s: %s\n", key, value) keys = append(keys, key) return true }) _ = tx.Ascend("age", func(key, value string) bool { //fmt.Printf("%s: %s\n", key, value) keys = append(keys, key) return true }) _ = tx.Ascend("student", func(key, value string) bool { //fmt.Printf("%s: %s\n", key, value) keys = append(keys, key) return true }) return nil }) expect := "3,4,1,2,5,6,3,4,5,1,2,6,5,4,1,2,3,6,1,4,6,2,3,5" if strings.Join(keys, ",") != expect { t.Fatalf("expected %v, got %v", expect, strings.Join(keys, ",")) } } func TestOnExpiredSync(t *testing.T) { db := testOpen(t) defer testClose(db) var config Config if err := db.ReadConfig(&config); err != nil { t.Fatal(err) } hits := make(chan int, 3) config.OnExpiredSync = func(key, value string, tx *Tx) error { n, err := strconv.Atoi(value) if err != nil { return err } defer func() { hits <- n }() if n >= 2 { _, err = tx.Delete(key) if err != ErrNotFound { return err } return nil } n++ _, _, err = tx.Set(key, strconv.Itoa(n), &SetOptions{Expires: true, TTL: time.Millisecond * 100}) return err } if err := db.SetConfig(config); err != nil { t.Fatal(err) } err := db.Update(func(tx *Tx) error { _, _, err := tx.Set("K", "0", &SetOptions{Expires: true, TTL: time.Millisecond * 100}) return err }) if err != nil { t.Fail() } done := make(chan struct{}) go func() { ticks := time.NewTicker(time.Millisecond * 50) defer ticks.Stop() for { select { case <-done: return case <-ticks.C: err := db.View(func(tx *Tx) error { v, err := tx.Get("K", true) if err != nil { return err } n, err := strconv.Atoi(v) if err != nil { return err } if n < 0 || n > 2 { t.Fail() } return nil }) if err != nil { t.Fail() } } } }() OUTER1: for { select { case <-time.After(time.Second * 2): t.Fail() case v := <-hits: if v >= 2 { break OUTER1 } } } err = db.View(func(tx *Tx) error { defer close(done) v, err := tx.Get("K") if err != nil { t.Fail() return err } if v != "2" { t.Fail() } return nil }) if err != nil { t.Fail() } } func TestTransactionLeak(t *testing.T) { // This tests an bug identified in Issue #69. When inside a Update // transaction, a Set after a Delete for a key that previously exists will // remove the key when the transaction was rolledback. buntDB, err := Open(":memory:") if err != nil { t.Fatal(err) } bd := buntDB err = bd.Update(func(tx *Tx) error { _, _, err := tx.Set("a", "a", nil) return err }) if err != nil { t.Fatal(err) } bd.View(func(tx *Tx) error { val, err := tx.Get("a") if err != nil { return err } if val != "a" { return errors.New("mismatch") } return nil }) if err != nil { t.Fatal(err) } bd.Update(func(tx *Tx) error { val, err := tx.Delete("a") if err != nil { return err } if val != "a" { return errors.New("mismatch") } val, err = tx.Get("a") if err != ErrNotFound { return fmt.Errorf("expected NotFound, got %v", err) } if val != "" { return errors.New("mismatch") } val, rep, err := tx.Set("a", "b", nil) if err != nil { return err } if rep { return errors.New("replaced") } if val != "" { return errors.New("mismatch") } val, err = tx.Get("a") if err != nil { return err } if val != "b" { return errors.New("mismatch") } return errors.New("rollback") }) if err != nil { t.Fatal(err) } bd.View(func(tx *Tx) error { val, err := tx.Get("a") if err != nil { return err } if val != "a" { return errors.New("mismatch") } return nil }) if err != nil { t.Fatal(err) } } buntdb-1.1.7/go.mod000066400000000000000000000005411377660341000141130ustar00rootroot00000000000000module github.com/tidwall/buntdb go 1.15 require ( github.com/tidwall/btree v0.3.0 github.com/tidwall/gjson v1.6.7 github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb github.com/tidwall/match v1.0.3 github.com/tidwall/rtree v0.0.0-20201103190202-0d877048965d github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563 // indirect ) buntdb-1.1.7/go.sum000066400000000000000000000040271377660341000141430ustar00rootroot00000000000000github.com/tidwall/btree v0.2.2 h1:VVo0JW/tdidNdQzNsDR4wMbL3heaxA1DGleyzQ3/niY= github.com/tidwall/btree v0.2.2/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8= github.com/tidwall/btree v0.3.0 h1:LcwmLI5kv+AaH/xnBgOuKfbu5eLBWVdWTpD2o+qSRdU= github.com/tidwall/btree v0.3.0/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8= github.com/tidwall/gjson v1.6.1 h1:LRbvNuNuvAiISWg6gxLEFuCe72UKy5hDqhxW/8183ws= github.com/tidwall/gjson v1.6.1/go.mod h1:BaHyNc5bjzYkPqgLq7mdVzeiRtULKULXLgZFKsxEHI0= github.com/tidwall/gjson v1.6.7 h1:Mb1M9HZCRWEcXQ8ieJo7auYyyiSux6w9XN3AdTpxJrE= github.com/tidwall/gjson v1.6.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb h1:5NSYaAdrnblKByzd7XByQEJVT8+9v0W/tIY0Oo4OwrE= github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb/go.mod h1:lKYYLFIr9OIgdgrtgkZ9zgRxRdvPYsExnYBsEAd8W5M= github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc= github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE= github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/rtree v0.0.0-20201027154624-32188eeb08a8 h1:BsKSRhu0TDB6Snq8SutN9KQHc6vqHEXJTcAFwyGNius= github.com/tidwall/rtree v0.0.0-20201027154624-32188eeb08a8/go.mod h1:/h+UnNGt0IhNNJLkGikcdcJqm66zGD/uJGMRxK/9+Ao= github.com/tidwall/rtree v0.0.0-20201103190202-0d877048965d h1:RfyBHwIzDt48a28uBzW+W+6rvTJzuYH9OpXBru+CDeo= github.com/tidwall/rtree v0.0.0-20201103190202-0d877048965d/go.mod h1:/h+UnNGt0IhNNJLkGikcdcJqm66zGD/uJGMRxK/9+Ao= github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563 h1:Otn9S136ELckZ3KKDyCkxapfufrqDqwmGjcHfAyXRrE= github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563/go.mod h1:mLqSmt7Dv/CNneF2wfcChfN1rvapyQr01LGKnKex0DQ= buntdb-1.1.7/logo.png000066400000000000000000002727651377660341000144760ustar00rootroot00000000000000PNG  IHDRg, ttEXtSoftwareAdobe ImageReadyqe<xiTXtXML:com.adobe.xmp  HrIDATx]`^xq"kBib+ >^CQAG[\LC9AA׮]A۸qjժ1ɀ > ;w}:<<ԩS߾}۵kgRɸ i!I.++Tj-8$ʒgqI'-@P`ki`å{}wgHHopݻwKO ~7?bnd/*ի{~: rذa*Ug⨔Oâc3er :`KhUZy-jZUBl#JݻiӦ$͛77hЀaM 0`#;;ȑ# $k4i2zқ)UQw KJ`<.q*ϑTT<.ɶ4qjMMKK[fͯh? ɰ& 0Ŷ{nebb">}N/gbkЧ 'ukjAϚ5Ɛ[nZ*Ú 0`۷mۖƍ0aMTJ倠σ%T*!'+\Y mBiga:K{3肆 ͛ 7YCZh& 0 vرqF246uT𥭭-@V ;AHdvAdeزwu]ѥqѣ~T*%MMM80x`50`'˖-{-)SXZZo瞼>eP\X$p9*J].) 0tи8Á7& 0DEE-\ԩS,*v|9k,z0!#蝧gȆ?D |)j8TrLLL~m;6rH50`Asŋ!Ç/]ۛ=uZnX,PY hwV;٪ٮ];'V,_zkdF!..n̙.\rZ~} v_?z/|) x\@i2plbZ86rus:9Q/"S$i#6 sySNdggO:991ɀ *ٳgd,,]Lg^) KqJ?|IDKܤiU3 S c"t2(B2_+,JO͖&fdKӥyyEryONP<ޡǷ뉏Ν8p m=\°& 0(*jſ =<%-7T5RN*©^t Νaua Ú 0`$%%?֭[X4hɬ"7z F${Tj 4ݹkwgw+Srje2\.W(J dP(Q脨ߦ={}sy\H[@xv43вe7ogƍ3ɀ Pz E.:|!0. vZV)U cz5TVjuzzzbbbLLLԴBP|GO,[XXX[[;99zyyy{{{zz:::<#7w'_c?V B٤N'O7oCÚ 0`]6z謬,SSC{[/^Rg#XPyn=}unX`ߠׯ_c*6//ܼVZ:uѣGfm? yv \:6wbdžq{ݵkה)Sd: 6L&9;;:ue˖X[P4wߙOߘ\ԲE kZ#5mY eBBƒnܸM˵Zv>>>zKu}9rdz^s7fba*Sj9_^X(>im@t`X 2 999dN"D"(9+++[[۪URKC^rTQS{h!PY[\\(WT15Ժέ<=@;wӧr2^^^cǎ߿?D{n۫W3ftygd/:|ʋSRd??/M/ k2`"999,,,00088 ` jԨQƍAɃ& tqq󁫰UDGrMEBb/|V՞aQݰW^=t͛7|T^5j zZWϋMBd6lR l2 5nxWMjlRq6{ qTT&<򕻻;1ɀFAVGFFxѣG^ | $EPip`mڴiѢEݺu ;v9;;_~ͣw6-Ib*Ll[cM?h#GڵK? GgܹPrPeF6 IT{QWdԩ8hg͚E>c~|yEuN 8<ՙ3ga0ɀF!&&4y'O@SjZ+kkkWWW5jUfcccii HJʊFDD^СC۶mw.̯}8Oy… D}ڵkW\@M4i֬Ytք!HWg47z͛>A\.2ŀ2A_$oA]>8wܲep:x.] W޹sg*b9[.9rdP`7* h6O611ګ}\z*2;xժU322W8T{{{Fk2`!^zuҥ/|^զMN:,!KM1.S(s eyE"yBRkTj@7 o)!DBAPtBTϺt2}t:'bЋ, !kvgb|+FS;o<ٴhѢsθQp)@WC%1c͛ʻo/:|>,9T/^EfDV<Ç?y$-JKK+z "mREM̓gjӨ>8ϳ&Ú 0 ""V̙34cAuMf L̉HNHJJLɐ)T*R)@mU2[UdWCM'fMII;|0zp6ثO>r3a߁WXT6Aw&T/>`ٲIБ`ҥ1n8\~5k֐>kٛZV1ejP& Cb!!!>}Zp3g$qF/Uu\,roٶmÚ 0#''ˠ;w( e˖`Hz: AL.>EDԜ&^Wá?GqE5T]Zb[پwu: WeUL;wN*{ʕ 6+7i]\\ }yX<~h*Fр7nXn;tFĽҗXzŋ0eԣh~}:))GBA0RrVw_F'fʔ*$l3Z#']sDrҰVD_~*̚3g… P -֬YڔH$ k2`"@*E oȑm۶O:u?( %\ߡ7`jR6Zת>C.jw^dqVH\BEĉ#FWqVՅ++W3N':<|]p 4M6VVV.9|~KRXHl^ܒiB>[n=0aJ֭[׀t͡iB~2]Ƣk .U!Z yaLG'0?nRr]ye_= kkZ$mV l6Uf-.7zL޶wG:tشiի%,*d kVZpMcQA?3Jբ*P -OK(3//PH!ANV\IYfS÷V\;Uc aٳgJOe(۷k |d kng~rssgMgȚf0A>}vJ(gϞ/_~ԩS eWMh%g9u;0LTE&m"Q jǃD ,xXr|ܹ$!XvmBB-ZDO0 r '.RF6mK.pɔ_[jE}vVVN5zs?dw2($v`& w=<22f֭^zM4_e^MJJZ|}Hv Ȧm۶R>~}yPTj&HYoo#(Ty!mڲQFw]x13/$$`믿ݐ̜W25,6L|$JIqWn%ϟOj4ڟ^̗)MEV[tz]F٢"Rwkke">p| ǪbZpZ We ;~nA k2`Enٳg333 _lF>>>{8p>w$s'ڵk۹sgaG<.-,%BvcqJGG,k@FBpݺu@Tʢ#jd7n$+~wr%"h%#z:XgϞRaرweՈ. N" $KȲ<"rX&dvQry\iӦcǎP#D6XD.-p++ĤIh$)#K-3HOOHNaM |/]L?o֬ـZn aammpJ%˗/Μ9CcN ch@,Xrg̘h"z$Λ_/{^"岝XѪZ{ʣfrY,MDȲIU6 r~a55jtΝÇ?yl3|d<W ODBs efN﻾̞=dF\_G˗|~y{{cW!)e MZ]},j44&&`vѝA3ᰌ#ץ|mLsJ\.A5024}tF  r:HNX :KKK5k։'ܰaC MZס @P.CGu)rt, M`ehmT˖&&!KU"YN,=7/);7!#'13'57/H*>=H\"~0xͮ-_o_ʕ+pVHrWvP(H:X=[XHp Fn1bŋɖ[n]ԩS>DUdd$ʩEK e[8_0 J>][{97twh`eaRi'lixR)ylD_4~_ ئĹ|rPOnĽ.<}.7e֍6l(yݻZ!BBBkiӦ &3l ۪srr 7n\!Qj?04Q ͪb[9eXϷ޿~gw^S(1tٯ֍ǎkٲ%cQcf[6Zр?R۾}ޭ^|(p6f&y߻w JOeOEN`TRnJׯ 4NgQ rWZ$ϓɍ0/f~̏5amj50`PKH܂5BVHIz۱ovر}EAkB}&>}:FZ:x𠧧'JuY)T*J٭qu<\ ӨGEE>&&&!!!+++77If be 133qppSXm'!m}/13_%C򊍫Q|.Wb?KzGCOGEba;vԍ_''q*u՜h .,~R3,H*>(3(`Uh(QفjѺi`I@"hW$^zn^\a9^NGgm'r'Ú 00 #CA|oc$"SakN]ԾTK ֏@NNիX?~mۈ I˨x3)UͩݛqY={"l.4*~D#۴iΝ;wGgm߹n>~>+?NwFټ4mۖS`Qݤ9}_(WKD`zNd'w1-Կ~A۷Я,*,z>3(e}W[k2bȑ#?I\`HBtR]a'^K0x̸& *H{ t *~ƃ| 2mzrI&Ѭ ٳYr%l~˗'?_rbLNKL_*kCqZv_`ڵkׯ_%%XjU `Kb KiѰww'06D1x 5g+SId>ܷo_" ȢA >QWId7n ޵kWw Sr N3]ϟwN:555;^7=}eJ]PoND(OwBp#TVI7!&$JeQѳrCIlz<s 'l:](ZR٧i#{7o\zuƆP5apޯJטM<"ВPfgiz\D!Zk׮˗/[n"Ř~\P"xՉ-%OzݳH6p@ZEf5D|t, {rE*L, M[ʞNdL0l9ǎ3ya(Ú |v" bk8Li~gV56  z= "+8lN^ ڌgඇd2Xl)|HE/H>L0[WXl䀉]?DFF.YxlҤd;lȑ# eΚ5kڵdjGZtۥ{'/B_⳩bC" ,#Foܸw`dU,m?.-Ju↯FZ5;0b)5֨E|nJ bӲxN+*H !) <|L?ԩ`!8.cqO[`lЮ] eX /&VZEXm1eO$PT;Pՠk B9XDHKK3{Ν;?ۼy3%8SSʗ+''o߾Ox֭~-Y5fd2qݜ&Qܝ;w{ٹ}}#Ǐt(PV)q8E C. AaÆk׮ |I%%5qT1<\>k~Ĩ* \[5[N(*#WQn^ UV\I'sQ'TM֌jBP B[˰&8:uJG~mB`mTʞj,0lYՑ>2E#v!YYYfffĉ:Dr#tdKLʫoGASN888 Һu3١2eٳgҤI.=zW?+cP01߾}{???zgtsa,B}NˋV,C 0 Bit`G 66EկvuaLɐ)TΒp$tˀXT 'pEPϑH{D+2q  UbBU|`0FK-[Fl*u9 9lceBU4جRGرO|DD(k?L$yVZUHД uԩ/8.۸q#̜qW'boz.ҥK$>wpuT ! +{C'#h~XSTŮoG[z#w=cƌsN޽{Wx`@k%L֞zE@&Rq^5? HX:>ƍGyxx\xq…ۄ5ӷL%eji~??o/mTПpc(fQ"@H*ݒLXj钅Ap*U9{Og˦a65heua;Yp`L9L4Pi]^juvvvFFFJJ Zsjj*,NNNN~~>C|P(VFw5-s66600 $IΡ;O {s!+&C=l8D$NckG 8zh,'no*jd'!IO:UNαcǂt;$޽{4(8nYa Jtׯ_k\IW^:Z?0|`*8hꝸXcH@" {.17g$fI~<{`W8%b KJJ"K 9Xrd=,^wETCnHD\e*j飪;<~K^`Aeg0FaaaBBBdddxx%&&2/VVV5jԨW^Fׯ31 \NF% )͘} '4zn0pj\!J)EN[ڈ!ՃKʕ+\t)~CZ${I st&y^|W_:w 5fo^:2{/ˢ-=. y}<\SěK~bܐG:g.!Cy_~i׮]Ϟ={~۫Ͽ(rH^7Ƽ}š6V?<}7m\n3gWvn޽+==(< 3 OI$f͚EHgxd&.2*jSդIva 7o^x KRb X sss R44/RKd(?a;M!88… '4z{{uѣ(5L!X~'t:#''j$P v Nx/HZQUbǟE_yb*)TMjTݿ3J j-ZMX$}'jce(PԄ.Hq~ ou>Lʫ#G?~6mګWT2OkB%.) Ekhs N5#`+ K-[W`QIq2njܸJKK#AOpP,-u1̹k4Ő,j& REe33gN@@KDm|e@P%SiMMvMٶ.(""[®n߾>06gϞ<́4IXn o#XSkrz9H\0%jLL 8,, %S 30I]vm۶- gOm 8Y-<׺4|OG;bCIn"&-)#gWbjKbݺuE~]h6k^BEΝw===)V1^-5ħgaU![gϖJBk-ZsNP¡=F?P*fԥPy ֬( e\o:5۴FNѣq 4˵k6Xt;qք'a3^GPe+i1  g_ \_z+;O&hszdox(d.]y9&,$$޽{`JnN:ׯWsPf9 )Vt/lXIСCвOx^tc4( NL,wqa)!ɂ2yŠA:X)IpNALrqd`A"h@̑@$BG]_D 3 劾M뵭p}9իW r[ByduUrԶBjSHh3H΢eƽ|@\Blڴ^3f ^̎ kuoR`SeI$BWRsNv.6ViYE/>rѻC}w9r*vɓƤš 333qW$d͒<4N' U'.o|B*pnjm< }~a d"##p@͛7oٲe zRBxp29LFLPkjFAC sXXTbmnZԤԖ_ [ɓ7nȱLPٳq|9M&ݳH{A2Eݳ\6hrfLHTk$BF+%Zr ]DL8Cܭ YMَp0 \Ikooҥl0Je>vX,ĥgyDT^g]^,jHм>\\\HlӅ*0 ~4Hr{!-8[zWՕSH&_zS Tj=x`/YfP]bdx)+*뗾 ?+:|sH… ɣ3 jfTŪw5{ogUͩcwĈO4JX"a8RhڴԩS 9XbڶmaMH$ψYwjVtL2M:ߊ'H,ƒN$5uFK U #3gٓ|>ZVL&.TҸ _zceʉu*xЗM4ÇϞ= J_ZOg6+7D?}עϊR%~tZM̙X 4 5a;D5c ܓ%K\ P WIr<5.?v׮]k׮'Ԯ] $?_>y51+j e+FiCL@nٲ[VZ/Yw7ҍf)w3Tzu֖Wgfs8lxJb*"@W.!D$OM^:JDB4.T^vСC7o$c)[ogh ?,SFZj6VDAIˠ;-C h$cM,^x&S^"FCS%jժrL|9?}+.;*jm;'K!9rdebʕ6}ENr@]B@$kQdxb*+BP\{lhr۶m?IjBɎ1:.j]05TB9@ 0P:uЯ썫/yܲRʕ&zw{zLuR&np_ &Lĉ'\BT۷e˖scc^G%DgH*54#!&Uo&Uh K^D'I&`eQձc}/z5UFkE i夲 &X[nڵn^\ҙ7/533351bĐ!C 'QW_? Qi4hhƧ/l߳XboS//?~իl۶ԂG Pp\KJ3F[lm*qYYYD&edLZCDL||oE"Qt d ge8A>=J4u |Ց#GH]ztƑ /"r$ewS9`'C\x =}n8Hu*Uw[7>s0Sau<޶}}/̚5kΡkSŋ<Q^8 LiGEGd!FMv9UM}zlԷqn7lذb 27nv0H4ӣwmyֿX[, |[61x}9w*UnVFbtQ rE9!q#D6Q_")[|(L4 'wގ;H{hJUe' i|4xHoO*g,uЮ;vNL2ڵeg˗/?)pHƉ'XT-[023eM֭[O>MqԨQ&LO8  K`)b5Bj>\BfμgO?aP΍j9܄SFds}JjtXZZBX`!<9=_&ђZ錥XmL"=ŦR6cNNͪRҠcd4*<^E"гPHʛ@~ի]6 bvGCVgH K,}jTq_&#? **_PҙuJLL`9s&͚W\3fLW:.obD|:iyiYU,A~+Gf S鲩$ WQ cݬU͡K=7vp\t8lѣG/ZL[ڥbl#X)yY3wQBhhx/APcǎ"֭[6~݉G/N7n j@FkĹBwѪ 4U?çdX|vpIq7l05; (䃗ZV3ի KV V6q]'~V``ÇI]_(>%d(]|Zcts@{vvv\.-$>O1\š%9rM(44 C&$2&C v+%̇͛,%oV^AbFNTjjdnsZnH7wl' Eҥ ͚AHd:SrR38=ٌ_JJ9Nkr8"dMDvAa`L">+& 9ETo\!7`Z)$o';OWߺ59D 6%?cZ"TE n1մt-AtJ5*9֠Q#;/@Mlժw}[Cg\xIpg;f/O{o7d)5$.}b*.e\`AVV'2dHW'#'M9loFmǏ=+Uǣsk4%}+}uy U [6t#s co}dJ&I9OJBHM^z/H6h!rl X  ~$4:(6 [$SDg%. ݝ[׮uwwL6$l-}/5I%P&i>>>?# 6"G_)|3?Pꖻ㠸StoذJW[d dK[FfF^~\Uf}:ȣApD"##A$h%ClllHqΝ;BEZ6ĿۯC7_$,PH6VCωد_}_$& )T[ゼqO@Kyjmx~^tiԩt40x7h" ￟5k ,fǕp2T8S hCkѾ9nјh o̽RKItJBŚ?3sxL IP\J͋KώLNHNJLIj]<1Ia.bHi)-ɶQj`:$Er<""ʕ+pkWFijKXr镊vڒ,.)/L+ֵA6DeuW15k5{J3K+sy8HP)CH_`cJEfhu cQ,z8¤B$gFdD$o|Fv4?_&WRy:qI:; M ҕNU,u3"`cgϞ`|S@$RyEJio<8O>͢Bژ@6,j*A~~ŋq? ٰz%G/*kY=4>hfMZ6Θpl CP Wɻ)tu޼y:uqmSc3F+--:|Ԃ)<*ŋA7o&wg8ysX+{hRm(n|ƛP/x\H[2RW|׮] tj 64k֌d`X3<<իX^f w~ۥ{ơ~B.u*rZW\O%EA&bE,X> Ѐѓ*0Χ@&DJJ#Q[7PW1[pޯq[A%NV?~Ǐg2 !JU!IٹFrZH?iXt46)=ۙy0 1$j#&W. Pax ܝk8ؖ0xG R4c-Q*;+qգqM֭aM5p@k3ת֩v?rرiYdo'8=kXښW<~?7KS0qt܄5{uwEummE X+{1$$j%{7X( "3Tލ7;ˀ 3{^kˠ%KxxxTuu::wwێyLqn6 :uDh[.+*BѫQI]ZrgZT*XTĿ-~R.2hǾ}4h՛2e 8jݻwِYfay2nGzbΠZV+F+ '[e\lڔ-c`mamn7j+>ؙ%ILmG&p+[J^/U_ads={`9֭[ӯ^E-=v~lxDqۛ9I65v-SѮcrem,MĥV*K ፫z|&5􈸤^ ɇ93*|.F_? w֎.v6+W?͠? D|mxxxHH㵣*ʕ+3ZjաC $e'xE'v^Mz`J!q`}vzr%"a@Ļ] =z4nMg 8IGGGxINx*{SL0g5lxJΎ\59#+GҬ[lժX,nSǻE'v^]I0*TaFm7)#kf^[Kkc5|̾ⵥq%zgˎ]q 3Oٳ-c[ly?88{޽{7Mp hn"إUС;y&pA_5qKǏ{\IbfHkN]}!@} v T(o^Ύ+4+ة  Ǚ쐱C3rW > b_Z}(^,A[X4+/>bg  Rr-ƍ0KT(H,r-jja#\ˉgҳxBB%hȱLJɉRͧ3Fw ̍/Cٹ̙b *ف\zbz/!ǒ6j7,--=== 5RRR+32%90w=^~ٳ3e>M^yrn&GȐxva=jT`pD;jFFLjBq k0_|y ܜ9ۖ1 pOBDZh"( v[).5X|㡭C{M s`3u;w ӧOgdd?tnΏ£FŐY\칅SMŋ/>q)_OMuVh ,\4;{Y/.41)2 Cnq+SZ΍*WW .NZzD #']* յr5k֬U, p,oHy~IY&bb 3QdǹO9 ߟ+6eʔƍSFi BA ˗Б111ڇCX8X$իW'Tj/5+,0)1N8#ysccw2\ypRV;q 9 qy{N=fh߾}04k/Xҭcǎjp!b J5El=j:ExxY9lUwͫSaF/4:r[á39 TסGw9r6i@biW:tP-߯[T!l؟kX4-~>M\gFFRuk \jJv1c#ny.ogϠ\0[+hI&-[Ot_5a_͛7d2q+O\늯bZ>ǛThSjj^˗񴴴ׯ_xٳg`xg6:;`cU]v֭ׯobbbcaF>:GG} u//q>Ů~c;5rCǏ3~+﹢: H$X%,0`dhh(x0R\Fz{{CFR]|mnj؏Q 0"⒢S)YM %zbrLj;dA75 0qD"/wc#ƘX_~::ujWVl`4iҤaÆ"s*Y oI KqPU}ZP?RZ DaرcgΜ۷/0 WssƿY}ڿM ~ULbDҠ]2#Fs1=Fx AM\r9s&;HR:VLŒɔʋ/^&I֖k 4/n߾}РAmڴiZݫM*>_x5?{[Ks  _ 5?}tu>|M(!<:~ή_EaRv pWZ^sK*,6CR,+ 5?vo0:uo p{ݾ)sF{ifl䵖x9;=+})նm0v@JBD}A>8"YJwwwv)qR&|@F&K%*Ћi_jYM*uL(gc [ͭ\]\my#( %X RFF~ӧO ]mc?G-Lמ`m9c3kkgΚ5kƍ< ԨQ@#-My6#i].|$ DKfQw V TyvO.?̎͛6k׮...XUʗ?فܻwb*Jbu ^z2ٳ`M={6v.-o>+ xǵR)Y0sy_ X\ȤpVZFi|=8ON9xbp.1xP+fxx( @•H=}g`A R9ScFժ[n]z? #An?vV{jaj2u^Mp˥{q:e`pӲwh+@92?j?5޽{ 0ϟS| けd%[ȒħK1.%Y*v`b Y,IzelF)#˗% gߦ Юd|-:tCr]M}m߾=3!!o9s%KF"V85[D}+_dyT1AlHF<,[&WKKKvuu:To߂h:VBT<5RW#U%LZ50M65:֫+:ItTgj%au0)Sdh]f jWa1 O#Uh֬Zl|cJ5ݻׯ_?Gv҅Krf8~SKSbd.>5vlڬz%WgΜ@Y9;(P$33FaCa@[;wK/`ܹsQ\l7NҦvE'edFm=֊>}k,6駟0iXlӗNqрЀѷk׮aÆ Ij\:W]9/V:|>$#30ljV!]OzGF]V vmdghѢoرc`TҟtﰧQ..H-D==R`!hRx [AyYl}0Ә.+Vv5)Q(iM[ |Q7[* ~Æ ۷i&jV,`oi- XKOW:5̳ҚbklC׬Y-$"7%"6m /.\hܘɽMn$NrTa%zk_ ]suX{ر`UQbEpO<7oq=#FL4w%ɒ`MBM4 ɛA.] ) e*ލx%ۮY0X`Bu ώ-[d:ˆsIe+G%||/S222*#@ɣ\_{`:WsnVr)+)Wܼyݻ8-kV֠igV$U3M'Թ]=|XF4.gkM7XW\y#u(;8x#)4pKyp)FL.Yi)GpԨQπq<*foOZڨ).0m۶j˜RK2fYBPjH)0hBh)XR>} Qk[ <+'ǩGmbٔCAMpԎ- KFPc?CJ'ѱ)iX̱N|̙td+b˗/YP7,K߿{majexG:wDˏ_=(ҍ7t֭:/EY9sDl3;vb{a1Gk5,#ET#Ss@TM]`Ś<6HlLXYIzypo ejհC߻wd]Mc3 z`~)Y'߻q-{1믿~,K5y J'rsD܇3(-DAel M!ۀ.P-Zر#xDv 'O&\>)GJĦZ}|X\&bVh^vJkV,DlZ`g-v@:t1ߔaÆEEE-XlոV%nč:㺑Ajtӥo7Z[Z-kTj$فel>q/^!upd(ZAv$''0°XNv>-r`9X2^.wTFFIr>au6'yݺut\*PЬ$L$&Ty`-%r'~y;4Pj {HPm@ջ2S߿B*+oghp^4E<(]tѣyI8.uڴiX \ܹs8m]D'AYj:b@Ň$5]\\0ETTsc߿e˖kV^ 3rllɹ%$ yTLmΝ;sT !!poƿa@PL|˖- xUScc/ٺF{3HX v*xתP=~2n|;w`v6֬᪍k@DvӦMaMMPuޠbbVnenZ4Yan<{uixY:z{~ ekkۍƍ$Q(AR 65js*Z7ӣu?60/9;UV^PkD)X2}Kf2yQM 59T|OGX*R'fѦMp>!3 rm04=\ >ikM˥AQh;4Fanʒ:׫lToJV߮_~ɒ%"L2.X5w"k'1/X}FpT0_bW Kqٚ۸b5iϜ9s۶m>^f ߪUk|`*> jbBs/__ Wae\b_25˽ !+9;pRVssr/-aʡ c#wy}Ҏ:uTN i߾=/²1bĝ;w`w~9>%"ڼEHeHs_~ja"tr|]qU\6ޭ['t/U 05&SEyVI2D0+W d[=5AyuF *h`R? XiYR}ze,:o.!5fIE(,/#vPsΜ9Dxbl-F~Jc2?Yh@طÜ}w,dԨQ~޹QFQrwIK$15cMd >lizTf <}WVMϚ5k;t'.Ma֌{VUI o>{cNb#ܣs\YZMzVTR<iUrb72ޮypZN+ 8zXC&{_%gi5zAڹ4 1 -jV 3i$ZH+Αz,89 '>0<&mW/ːjUquuݽ{Ǚ\3/tx){?#&@ٺ:jXj1˔*Q 'grr2o-Ty㦎 S34q1##( }znJ+ \E)ӦM%sY=$|d;RY*lT'ښ^7prrڴiS^ƍG'駟`!!umO~ӠJEHc=x ==M6]_g;hZǖ{_WXp@89 V-0k9?~+Ng _||mFϑ+fE7JU=yhSq6lXg!,!2ْȔgRv6o)&9xZ{ \NVD{̑:#5t|pA^}Lȑ# 2_Uh{zǵL{FVAϚM8+@׌[h͛͌ӺPI|F^5oҤf2^QS3Ϗ8ahإ~o?9GB$R[=O:Ć~PPPDDDAݲC\u2QN"k.WR.vɱ˰&b]֪(,6mrzym@<`L.Tk,P M:GhVplh ^!L]j'NT0,A%wzu(qT?)-#/= 3䬝v jbВ%KoxK)W(gu/}BYoGd”[dN&ӽ{Ν;wN2F=rnxLid` ,_<ܸ:w铝ݸ/|"JTa_Y{V\jcj!^K}Wr.KJT-~7Xb˃._`bU]td|%S X|F3!E_\:("5K/):-mm3jjv! 5U\߽{CRDMl42xK5*!ɛ&EY6ƧmdЪV{̓(\6!P6". Z{@֖5###yFLEl݇3gEBJN>%ǬgY{2/]4dvK3f g%!;kk׮'AMQ juq˱!Ǎ;ź*< \X8X<&BVlh8E{茚0F3&|4\_IrM'?~?V\icacD\41v/Pm^DDl T˗/g[- OC/i'2|شicٲe[֬rn_<~?+V_OseWКt`fIeVm,/{NtƆFzDUrrEbhwMJ fyEm@0l!=???vԬYX^"I3:Wk=Sބ74@b,tHɓ'>[ZZn޼V}gL6;8+gc{H̋/ׯk֬}!#&5i$o>{ȑЧ=Û0aB>AqرcroNիW}a}QsF?0)#'챟>P̻v~O nܸQB݆;IM|xLnhw=4شiSA,ٳgCH4lؐrX 8P (*GKer1u\u.} psݺu&o|2$NJJ B._vjI ,, 0mvo7SY0ypƆY~HVjֺd+RZ*j6T=X}UV-NJ%TB++kcn Ĉޯ>i)MnqGo*l,$8=$ݔ)SxcŀUVi{qfx%)0JwiU?l&NcC* 0@x …tΑ}ף ;wg*J^z5zh,G/Kd \{KMGw~/b3tDMJEVpeʼnl!r 0rL_J=ϷU- .qzoj}ūOFΑnۄn۶M{aO@l ^δ&l?ꈚzzldp(^tJJ ܿ61,t4@TA{(U|6F*W XktN82z]טۿVc4hرGx;"eK˘xN]YUT7`΢EM\d厱ɱ$q/7BHhl}\'}˖-)m۶ 7AW@@/+ď 5S3(a7w1K&$3Lzɒ%ntGp>BVcyAC:u D?4쏓z MpK`ccӳgO!):5C`+larxe˖aᚏ=z%7nlݺ3/_Ĝ'&&q#W!Ze3_&Y;X[^P0n={֯_?!0`Ai"OzOhV,ٛ`e ա} 4AtYzoV&aԜsmYP~C,Gx4%2yj^ޡڇF> S&h{ G#s!)VzF\/0n|7 UrʜyI jv]2HJJ !bر#ڵkOX">?^>umD P^əc74WHgdgsr88t|r [xAJ}9;v,W U}JQM:b&uݜH7 .ၑGT4ܣgcIƚ۞~g͚ohݿƯZ\'L?qGB9_vm9::I0il5ofy1:~8dlM޽uwj`;P;Nhajbf(.SCXi9* :V"ry#;yR&/KVC +Wԩv>ppp䀹 !!!>^Ns|Nqa%A(E.kcU;oIEɓ'31m NfnjũNQ+fKxLBUJeg m֣g &q|[l ssŋsvvW T*SSS!氘D h… 'FE>l֍7  Ҡ$ RF?(YR Kܿ]qj+W.:&F8gؐUkI2cx㧟~5jױrə|8'X b]5x*\ +kVA~fsc#:zcMFngggh#,Cs,J#߹sٳg=T >T)izƼ)Δ,ɂ}g̝ i>[H+lB;/W0h۶-& Fv͚5|܎i"U`R3fiӦ LpڵNNN\Ѽ=tL#0Iw^F\|ѭQ (ej,-=2΄8[oPҺo#mī'hyYԔ< M q}PF}H iVô䋚 .=~im(e:{}iNflllhh('BaZjS'QsmLec)bB"jޛcX3pȑkrSDٳ'ciyta1KK@SP1_l^`vL[k!bKQq7C}xM&%%qeʔ)|]a{!^cvEՙ)yv>h ؋۷oㅏa~0 ؀7E^z\_hpd ]Y*dnܸ CF╬cjZ_(*'+/XnބGZ9 SPSRy^I0ZjG)n:P Y`;JJ<[Oo;/^gZ7oCEll3y= 1U_OڂyޟKPJ*QE'Ҳ%UL:?[R&efh-eΓmgyձcݻwـ@ .g&~p˾KL| 9EM1¬]6&MO+OC80&<6qjWަ7ޞb|KyvhlT|򕠐3=e%BZݫݼmذA#fwYY5hŋHŊ!(jrΎXZe~إA弰# 6؞/ DM6ONxsKr 1vuElv~(СC=vm,&[^0^Stw(C=z;|^:BT\huZV:7H%'넌4Xv???ypXqƲ, ?~%Sbdd`" /³9.v6ޣq^6?&n:oo| J]JzF1So=ɫi\mN4I۽|--Xj2e4S* wwwr#$eZ@Ɂ< Pi mʕ;v%wvЩ8ٳ)j\o#عsgz}3xTlTX٥\z7nܨQ#87n1t `/k˰MԄ֔;F͜a`5yNU+uv={;o"$L2b!ZS.jMLݖbik.N "dI0i榝 g{o:O@/},q.e`[jd0`иVxcBٴjjl4/5AfP#WMO(JLyt^@_Ε+Uehh)xH Eo,ԕ*͂QM:u߾} ;]~=>4^s=0B"fn?>9XZYadW8y@HTçz(H)U۴aET,*r>Vh߾ܥGTojZ>Ͼ|Gh@:Dl},͵27dQ3"".A̿l\ hoo߻wիW>|CpvA&қa[TxPȠ Zxr x/ nۯjYDnLMĥF/ 'lv1k[[]s!ׂ6 77ݻ/[֭[xXsQ=ȧ|B!avMRk7)-Ԅ9w :@`]|.4[h\"8{lAq$B2U^=q:Pjvj5W2<wQQ<>E1t AcΜ9.\`7\!=[w@"oFؚ@bq*#h' ScZ6d4\|7 2>}ںu YWԃY7նm[227_3lؔtf3O0o2q70D[tAM^rςW,\Ԟ)D5/ ;yƿP{c * |GTכՕNݿLNN&? ǘT.Tlv]ڰ w0@-Zŀ:u ЂYٳŋ_dմxk&.S X *C dF'KO_&gR"9 8`s:ѝjG}CM!H`kkK܈ Ԕv܌6ҹ3o\zPT,BOgsչyxGcq]{ưӧk.zXjɓ4h'5w\:U P`d#ho~ t]tUZw)zА2 Xkzg|(|70C<sɴ((HgbԹ ʼgxS 0Gk֬9rH!)F5W_֭<0%bv&-9z| `E~Ȅ1WA-ի2aH P~2ϢbR?;j SF>ү6@H`Q櫘Ĉda16:Wdmfbci}*E u1X*RTc3(^ EرWmlܸȑ# j'NWjMyӟf unD~o>^7aS݅  `;8rAi;^.f&e, 5 g˕*ȚJ.WLc"222>| nР Y(ΔHuu+ŋ j!U5Bz#ؼ nِdd$r*9;<KD9>͝בs֨Rz'Nd2<^`I5{4G!̫VZv-7LMm*WO޼-+ԟB|0Tlhge^ѶK*z9hzS` y7z{nZP{US=z4Mɮkp'9# xW39gY}:8p  Xl*.|eODk&k2Dy0ܯT_ aeqal,La]=C›f\٪Frlv:Ԕ. "](v>|{ ϙ1c6nHJ𝀙;4K`Ob~t*Aqr8cǎEwl]x2?>pr /b^3-=jʴI)z\0q5^MAvPTbyyQ0ǽQ~ P!X.\-y'\NRFxG6 G K^ŋ$4{MfpWQD&ޭye^) ~vP`` `IUN264Lr/ClR +~Ͽ4tŻAo>RY57fe[. V?SFVܧtd6%S}[+ m'ЩS@,CGLIjWv?;88L>}ԩg+N\ /7`g3A]:T$xRZ$&W; N/|Vf.8pD{ȡV_xNF :RJK zJ`{7ҭaMb6eBjL8۴i3~xf3so>u_`U),ѣGE;($ìD{=~q\Jwێ^0Dl#Se.[lȐ!\JCh-ȖN8GkGpO?x-> ;vkxl0F>1!PoJr/Y 0mi-i^9ՠNBSRP PKpC,YzĉPx0k׮>|{\ѿr,h:K7Q}sN[HƐz' ml~\@ܲeK? p$wC˕ɕDHkR&OC I8حA#z9ۗ9x7@x 0 [F]!cڴi+V駟ՇA_r )ۮퟚ-0BcTjT׿l1Ko9l>-&HY 3x'={wp aF/,)㨄b igaaQ-5Gll,)B,J ~YTlB47m6Çy[ۉ3:^^^gϞ_GDKJÅYo\bc6KU^h8EosYO>WQBX} ILH3 <_WP-]S_HvvڨCW\aE;29$G4G]t bY+ٍ7NOO\{I_:FőpBՌ3%9k4Fs.#5ڸq# @״iS*+bBBN\sBi[bZLiO:Q01_ѣG:uL}_eF):P|L.(qKg!CtX*K΢>e58gVN!J+|_ӡ&@h.h Zgri';w"qи84΂ 2WBڵ+Ė8uAfXtoG.{W,+A%ulY\9+WmΜ90 5j =F%C|4ܤz!27VMquubrqd``߱cG_( xč_V Fm,@q?aPΌg&c$Yԣ14{l>ԘH❋/,ѺQU(~\SqTTԲe֬Y?r1>r22iR+CJ]ю݃Ƀ\J;w.kҥسI>g̘1DlB.oMrVz׿Q_~E,+|۷qVvӭJOڒzK\2305?,\ؾwr\uBx5]ؓ"rJ eVzppDMN5" 39JwгgOev tEsZP6 [&""qWZ4~]" Ϗ4X?5+OCƬDs 9swB#n߾}Hڵ9ȸY"K#1\q'tsiXpaA1c ]M ex? A˵7u;0N݀(HANJ4p>kA=lR$9ٽ.ޓ s- 4c~dnR%KS C٪UC=~O,;~ѡ )0;E&f,E֧O L@&`ccCD@M^~Jf6:%A9s.usULNIfrÒ/p޾};uHP; :-S"L5C!ϟ?U>>>'N$͇$9Я<݃2gڀygD5kj`S{zPM6!Yx8}=zgK!' 2Bx՚URdwoR>uiې7RdrLHH&r1ZY(MdNUxpB2597AR8i*663O˼\UŊ5?-::M4RBQ3L{r)V~BJ*jсs|r(kӐ!CqD:g R)$PP;<F~^ bC:aɱ+ ,M/ڞyժ|bބ5+}+K(b 3$pD=}pɩd52 +G)OdSVxY_= Kn.,#l3,q඿N6|%8Edݺug͚E^eǮħf0‚M6y*0aH=3*)E lnca&JcqQ$^AkD5\# !+h/q$2E= FҿJ\>TMmm{յu6tPEADEb{7jƒDMSh{LkFc]"*(JQze`f׽;p)痏̝SZk]0r.v"%,f%Avd+MMK`+~zջz*]wo6欬,l'!7`ɡ,[S;ySbgk78HQj^Rhs!4% /}TD}(ηVPTtՂ`f"sTc H@L"Q.1WGNပn]tmCUN999Q7Y۳gDa9:v`~O>-ڷoB 8FP)}Y2N_K,|m?7 ݺuxya/k']xڋf@r֭:BXe: D(6FtrZRFvRCkMT"/RՀ^]i5k!dOIX붻F )NG3As c뒗tsF[_aYi.BM\VBOJ`y>BapS᪉CFu5 .D%qž۬ XM%ٜ5;,>j%+! SW))):)a$&&Tb&/cҲsX&94-a@ W .{Qh.!qXd8̢i)6%gǵ Y@t[+ _N]Ѽ!nҥp4z1 xiH}Gxf]?pԯ̜oQT?Ϡ n-FO Sedh|ܠ6?h1֘\}5O=CK"޽ ڍ+͐`QJ:x{{+9sf֭t`jj 0( :pEYIa__O}f,?~8f:d_j$yRh#Xߎ<[S8Lg2"##IӀQzX\aV}VODt; JLrڮwĀB!b6m"&>ﯣN-:HŠ#f ^{)ce5Q<{C "QSUtu"MYXU3wɮ D5aɪ0%I5|ٹ{6ñgϮ]J(ZER/:ZfXX&^&ީ \ 333QIƥy$)-X/[>;8e@& P¤͛o޼ɼV_5ȡqI%nE~~ZuY[ZfAv_Eqa6l@qd ϟ?ѣG!}>o޼+Wlәf!.0]3MyC{BX 87_Mv D Q Z|ڤ/O?|6*!-SPUy rNv[u£SEi|/fAʬ]?`=$Utpp0<o,❠i0XPL# \崀9DohTbώ nR,eWY9~Hƅe˖7}rLb 9CU֮{\׬YFhlROZ>dݿu]O,CS.냾w~D}uT*DdꋡA\j:آHG0"$23sb4vGr 8;;k1`"9+a \6 AD:~I3D3r>dnɵ`-a\kT636UC?"b駟jF2Vq:ူ~ }wSK m,-Ԫj?#:FAeU5xP=&K ' Xza Tn؜,3 ӦMZǀԩSYOţ+t#Ȥ{BzlԺuk~v[CYNE}0~eOMJM{l~;x:8f;4^v C!! )WI#Ttr*Tv| S3]j0i.{W҈pDoj~`ESOCT9U]#v~~~8:uTx}C1Mt(GkCC=Uhz9<2SD,BPtK\H.7G{1rr')=ˮ`rCq:"霜()Ipxrf6[w!bEsuuWz_ܔ%|4QU _ҁi˽ p׽9سi٨KMفz6{ˡ>[}kTL>X>PI+&2'43sq@{N-ڵ ʕ+uկ ! 懻; O9ZdeeEDDܽ{WM0`?gϞ=y$ >Q ַo_@\O@_~%54062*C!.Fڸ v+ {Z=l8T*TWZhkW5y \F&3NaNLo0e\8{@ X ׀J[0$`=gb{L߿/[lr^E'WϮ5RP&jjmsnjfrJiuЈ,uTsu#"Y3Č|qJqm::$ T*mQ騏ӫ'jzxx`G1 NII)$$PAhM΍ŦѾ}{W) Z A%%%Q2sδ^CKqu]K=h Ǭy$|g߻~[+yիW!.IkԨ〯.Ya4v_>hS B,Gm>Y< e ݻK,+?~x>}`Y࠘plRUeXvmg`_pɓ'P@8uf[iHGCqoV^\;U,Ϳ{JdgϞͪ[h;;;HpʾɔL,V>aqQ*Wauo׭[zrPlYJVvx<@)mIHw{dDٳiG'5}4Z#أol"6Jiq4/uXMt}=q@wRUvd"OkaUV `FY2݅W;e`ԥ:ѝlgL_X},W"4uiboMgmj8/^e՘;C;??۾"~ror]㻌=Z_|yq/+WvZG ?Je%ׯ_/:-FQ޲e "<|EgE#u(l soV3͛߿FnVF~vgVř<ő۾{nSiƌD, }SNB(29#;Kgn{IOV"@&$Y;vu*3q(͝7~:nΝ^ PA.,Ii|2QΓ|`.\Ǐ5tYL]h ԬN#7`[Ls,W|e*1W{"gҳDk3zi &I'luٲex~؈]C%E Whe _VL@h588tA@~aRD.kҌ@8~1 #bR *L#%ᣩ)Kj_HdHlT'jBw֜d}v||<:.WftiT8$.i=ݚhߢEv֖o I͙/^Ec[ Qas.ٗ%9lݺu֬YZ :Tw&矗,Y"Iѳgύ7jX;w]Ec[j5`h8ϳ|YEߗOC#ӲsxOU9Sj_ Q/q/cر6lpÅ]B/b+[DDǥel>{sAVb ѾkAMCUkIe~QTYdؾ};E$Mx@qPmלcӦM4h5Q1 O9bx%3&tE}L ԄJN Mv~3Jo2O$+jҠVaɈ@#).>nBGɋT;}_V2 Ka̠>=a4X4i}IS(5TW/C§{b(Wu% :е=3t @ CA};:wZ^__~^?u50xyAA.=}UcJ&& i)I`"C7{?νP,ZV`C@ϛ7OpU V6͛.\ws0mڴѣ@E'e4$")#WNnΠnv 'f^G(ʕ+gϞ-P]Ptl8-nnn8cgZ;X-9;a0Q'O<|ȑ#?s;T*O?_2)k؂b5YV7 3@ K>egeOu_h|q ~ϜU|ȑ9shD^L4?]Y TggD_1$|S,G6_tlSF/glY㽏;WhքcqӳePKQY7 s7͌xbP(*9-8&QmGoooV\ E߿~ #W\{Z@7 jU-ZLYǨK_ƳܹsY, _%bJv +%x*Po(T&9-` tO{zk' b~Aݼ飨}ӧa2[WIoDfjrZk7norfybg Fz,kCMy Ż(|rd7^-+)p '"cv\136)Z6S.>}%* =qzPSR PsǎmMF^? (ƕwi-4C} 79-Sc'Ļ֨ „K.8}4 q?Y`ib_X|Р}:S{u޼0(4##c8*U@eqqd?ΝjDzrL;()NDF?Juwx66L7Rp2<7c+PYʕWկn(/^|tÇSC JVVVtS)kct%MHuū^m2Ö-[))J0l!n,I# EI.,ܡCݻwLL'!!珱bJwecRFF `kNe2R8t - R+[s&DT躷 iz _A ݨTzz`/ kq=AٳjUƒV)|Q*V bҚЍgnĤ[y<=gJJFM/Ԭ@*GJ#qBB]|АmloV0#y{SSS5l+c(77wɁ|9v_B͙3'~?ttNcwk߾}a[=ڹ=x/WJ*PW} ѢE,A\+9z(4߇јf(;Yb: IR<رcn +rS3}Co |-W>trq|[!rͰK,:TٺuŢ&&N:dOa޾} .U\*,zmGN.Zeɓ'n2dYE@PoD\!S܆怶&m|XxXz6k ɥ3Qa7hR ً &/7<ҋh..CjmP I-IK889,S)cMy؊j7.s E/φt}tKaEYlokmijӦ ux{csceAiz-0U%&SǦ?xwF}, c}H6oL!Pi_#{YrUk.820 b_L _?N-;>==rsO,uz\@l-̿f+V2|G0u MbmbЏ;zzqi`Ұ"GI*Q F*Y6s٭[)tƍ7V\#)PW,9r_>p.@>>> "!` 66xL77= VPYUJ&I&6}iƦ|5X Y._ Jx!e/W! )20o߾UVQ|G˖-Ʃ?x&+W!>ϠhXmġmx|^RgG-4/a(|Y|}uZ zBL& O^5- *nnܸQGPcwjhbתNK߿? /dX>.lodR;W+[dΑ5ݻw Wm=,X إ/SGĥy024ln\s5[\j5U\]xZY7^J$ud2۷o?|P=eZG^ `R2A0N=3YΝ^&oda+ō>lWެ4"θqF=rUGM![/C>zhѠ'˖-d]v1/Ǯ3r˅@xPl=p1E'((QZ,ݟ ѤI;À3gҧtf +_iY߀+oV:B`-Le ->}³ly^\QbI.WUmPR즵 Sdr9_vi̡fvv̄ (|_Rr$ky7?腘3n3f c{dž422)d`W,+Š(ft-OFT[sy7znX"Ss?ٻ( Wʧ*)ejX]JC555CKLLLʖq ؘ;SNjDz;n=£>xΝ;ДqC0IhM Gc }s M)h`+=ԜѷSV4Og_ӣ5s`>==F;+^ N7r.\EZ<#]NYYg~9#Ɗ0o"+#mppKtsa7pWZzxBFNv5}aek׮M2Eφ-6\iHLBZFtrȀCXvzN)ͤdCM} Εf6vԭ[J—,CsL`XtNP!00 #720Խf&R(+ ㏼8"!W`{9ڇ&X v7N6lطo_v! ǣRPl5(:n=+LN: gre/^ת2oE@d^{ 毷F%Eت'7Xzzzj㓟F x]Ku1}07r1W!n|28Kt̙3q굶~&wWBJ؂85s8$Ġ>+3 6Linj2~xX4$JJKKT) #$LyyנD0_ؑ#G03lllȡ/YrDBW*=2P_lV_I&@3k ggg҆!mi}A(]\b,G*F;iXX+rp0U҄ "1Šgއ xb%rdmJG%rEm LT J:"UX)uEv];P0[OnMS˭=S#;XRFo>!.!'uJ7tBLK~&N C&xU=w޽j*6{OZqb3SJl2yA1tn={8ǚFe.i޼9%x!;##HmT3aEUx9#""g!?3qրmڴi߾=\3n..%?,뷏SdXUJ#^ve~G{쓡<<WXޫѭ[˗eDMl֭[3˅S{oފ p~䟜qUm@|޽߲80L pK$j rriiP۷/^bU)UKϒzw2,Z@ybW=cW=zNSh(~2DL0Ѡq酧/6chր-+)jxx888㻷$ZoCRU]4v%44rcĈM_hVsJf#_ f0rӧO޻wɓ'1 m۶mҤLL|ᯣ38o!?0<^6tؽ-ؖ{(Q%KA}@Ao2s޽{702CVMp!0_po iC;5Mc-++ |Ϟ=ӧOP+U\.C'Ayh+^k]@oܸrG{ ԩS'V{%-KvW&Kœ8PU>ews_Gjj* ի?f0m4;p@M1Y`&e\4n@me5938hd PMf??;S%7P3*.O"n,q @^jB/hPP5mϦU& GX7&yLg^6/ϿnM/(Xm۶QN[X\Ғg JxF&0ؙ_}k&|KRFiiD#+[W~ofP3'|kfmmҫ&L5 "hU.`i111Xz`$u),ŌLRKc͆6zCLV#^S}bRSl0kkKؓk׮+1= Z|L&jl/j1ԁ]3'۸$*%і݆VMJ!cmkeNmY6|/"+WݲSRdf JF."(fxl"EEE ]K'f*Jtm@6+C/_*+n^ĮryݚҥK? jժ㻵5&aߚxg}ŧħgYK5w Nhn<6 zd3N[gVWSk_꧱nBfA+.[L=xrҥ֭+.GDv:u!CgFpp09`"6aaAC5 <7$7$"(:g<ԮΎ㻶ݜ=ۧ;qvs[A\Bzڵ}ի~*A>la6l~r70jeY &&! "_tTնQmr2.'Nݰal T1X ɑ+bRDeuPA2H kQ%8j%K*bW]k,|9r.߰0VGT5*)އƀ m۔4o޻Â}'%MH'.שaA#gl>44ҲjxOgumv3Ǐ߲ìm 䉩Ӌx5Tqs4_źÆ =sL D 040 YL///ƍ jvaHL$$, CRJrgiB[5f#bA-ZԦMɿ(V5`0Ԅݹs'WCz_W> Xm;Fvh1gG;w}"|ܹsG ? =aJ~{harꡥTBfΜ$AeB#?}?($. ʴ5/&T{n`wXZ4]{D,D1R1] {.ӱ^ErVy@LQI 3w>LŋW*N{rr2&KjMTհ)!КzG0-zC%@svPq J\n"O+ lBZ9VظWR( eO\J۷;@Ǧf</֔tĚ>|?lڴ K8kw+1ښ\E^>v!*%8z[scHQ}L#*a :\E.\q_gȍ |4& K4H 53uWX\M0c`Ź.> 5,m?PFu' VQ>QQQ0kX'O`3: YfY!+]oheX|JZ sM+540ǽ)+`:Nݣ;KjB_bEJԗ'po߾Q,jBka@>-Znh`8 z1[ְ i28r/^|)yn E0?Y85;9drNvM=4ʕ+YMxaiM q@r% w:]= 4/fnzڵ͛7wM!#([#6%=+W.f^~k^'S‡㳦t7 h[Y귀, Ecƌ*xg#-(E1NDYy ?k8-Wծjkef*Qke 3OPo@ܶ9ʕ+8$@8z,؈ F)9*9OpJeS>>>c "۩a[VSం`lժUdOϟO .ƒ9V̥JqWrf>k۶-|`ngY$i$Eͣ"b`v@y/:$`w8o߾c㺶~ڬd?>7aw^3ERdfR,Tw @]?ȑ#ꊪgϞ`o]D3=G17Zשnl_E]~~БE8pXfh؈&M‰ƒ8NMMUcSW.eOL&HM bq4hA hD$Lp,yg3Y"b]J%^+amڦ{"믧Nc]]],'NZ2? 51p\W^͊M;|U*/uba ;.Ywj\nckkk (✉̋eId $U9d3oC4HܦSB.cfm;廟үUuyƌTNaҥ7a,0D9i"a=]k͓ SP/e`kMA< 6a!pǗsgGCCR9\uaf4v/ K}{QfYbc91`=F`wf͚y2"ּx>W\ʂcC~66mP/FC'`%dsUݭV}c`آ/_im nZ3 RFL $;L' lPdG/(&ŻD$g x׫W 3(VFubgϞFhm9 ?3%o@Mov7.{w{|b'0Wz56S `;jUN̈́)9pNB419 J̟+֤脴̳l,֥&J$~wߍP ŋP̷ A@ YQ:SE^X4͹`)X www \1x>d ֗y~GдHP<[\̜R&f"fu@ƨ #7pwaG:D?ŵVU'ބʀ"4+]d57/Ȱ"YYo[[j M($*>lmm_arsVjz K 1`S<cBSHɓ5 s ^zׯ_GDD:f0SA$ *U]|wQ"bSR2ɿ䢸-׽VQZkJH~P-[!̙3RJ*5%|֭[YLfk/c01.ۍ"/hT<5ѓY[8Wk\FsW'wRC_ŠM0A}m>P -*K-?2^h&ԮW__֏r< z7x#WXCV>00yԩmJ.MJϤ}2.ժ*5zg`ʰ'Ri+sr ]3`ʙ{}321\ꈱZdrR|{S̃H`t|Q08B*6dP )q%X=p sz.]-o$\8gǵM|@_g_1L·gsw'9sko۶M󽊊= @>Ih22P/^ODzLLLhh(UXwW2NdF5kdM]5MTw/#b&&9>=E*1z9<#tnճ;kرEkמ1chV_ 8.\zj S3F ^5)3Ĩ\ >(.-32)=fuڻ~ɫVU[T>}X(|! TuƵ< 471³sȀ>0r ahd e9lY,7;WmǸd(6:oqЈ675ه6lݯ$%%BCnݚ%ge I^a-{R9Zlw*5(pF9`: M-[Fek2E&cUs "O<+5*[5*a~,&L Qe S[[/,9tXw64Лݯ0Sek[uЦirf'гN=.TةoGҲ25e݇w VV\(~Z|ٳ6pp&}`$7o9 ׇlڴiÆ ,Z}`F%E#_דUhn(uy2[K^?Ҋ);w۷DW lYf.ReYfM-F5]y|-4EoqO n  !B׸I))&dϟ??sL|M3F|ۨSU-6M0L,>1=]\қ7I9 B!10045>nU*ɓ^~)0ɵ&Z*h$p,'>4̄O#5>|HQ{ƗC4/.E^~Ϭ\muUğ{A-j86f:xUQkccӝ _z2438qX>++ =Aq<ӽfs'TnܸUS-c`%4y=zڵ;ߕ Fi:V 40 A/A+NN`2:SeT=/|eǎSp֭ lذaP}݀*P8vAJօ<<$$ 1L<0RJ8NϖE&G##'ŧeGäƠ]jزWcjG'䕲Dvg}+W@M;q`Yˋtmlq@R`m\'{O6YKM7nT<}8Tƍ ?~)@"..q>ss9 [ooo  {vIG&\{xsߐ\ET=Lb?Ykg[l|qeJyuy6aEaba35!>'K}ױU,͍ 鎳]TӐ[%i&JBOپ3殙5x'Nmboힿao>z2yzJOţkԘ:|:PP$Yċׯ_An:FSCu xYT|(]IH 5 $`Auօ)*% iao_GE'D$e˹Kn{g,Ibmf9c;yuhTYCt4ܺ3gGȿ )"HxLR_ Y\8X &ґ.frVZE79998o߾7lmmk׮ K|jM;ݽ͇d@4֓o. MnoXo^zN*G/sqjW9ͪ1Ǝd?o*H촴4 o۶0Qč) QVʜ=={&ԄaV,{#""I&v,Mdu@DR2|ۙٹ Mbl F`̚ ݻ߿KͿw~|7*9KTȥ^q7Wku+I矱oKAt`p6U@ԊsJwj.!cG\ςF #Ad5f0c Q^=PRsioDǽMIIK͒Q0H8L2s!Wpm#:p>M`yaA|pbhطoٳgۍQ N3c HγgQoaej2{̜Rn?̍߼nHI9eG=l@JG90OxZp` \gZ|<;?ϼ8?tדM+WM,(FmXӧ% \1:t!t߽V] .|7 "ƪepL -{:,R)uV54CYZ3ʼzeLxadRjߥCLv"zyw? ;8bz7n8tP]pyrs(!:0oXy|3wRݻw0 LSNlqbCI(H *W}Ԅ9Jh4j? P;Zuҭ`S\`!?j0&pF(B7ÇRy' Q?P{멩{TC?Fff@MխOCtRWyMj;6u)L`ϖ刧k k6 c, J7G{QZ`+Aahz<j\x-?KUK,!? @~R."6 A6Ul5{@Ov ƜMMW W67uj^FSUޫׯ7︸l߾싡a}~5:R#5#ia 4hXHfdE%BJ$OKH˖y)Ʌ`~@g,[tWQ[vlTߠڀ;v B|`ʘ1cL:Y8Aիs٣GǏcz֭}by;]{5=ЯTEhx MYǘXսy`_|ѣ;? rj)9Vf&{=hUyh;O@9s诌!3\΅ߛA6ItwBM5"y"une>Co!QiFy37nT$&PoO/HJ~~~X;pu~eҤI6lXhѐ!C`m'l׶av来Iy_5w顳\T>pHV/h*k ӚR 0-䑄~&+֚VDzK.wZ7y&7Y3C0TΞ cK/eiCkѦARD9u7m>YFʃEzN?~zl~ 5E2[n]pAZZ}TT *I}nDFFoZHG&]]]!k#@CbəTxw+cg_q+)rPcCNoѷe#G%ϝ;'2PgϞӦM~322eWƍ7o/cccAqgΜm \8<,ڂ/PaIRP܄BHSse"aWbqdffFꍶAٳgCjZ  G I腍3>Rݻw'Ԥ+aE^> )UU-m,ɣ([ɠ1 ],a1WXAásǬ}[;ڽ\303uy$*Qg4"Ԅey&W F<->*[k-9[pˠ5X5nO~`Y!3~@P1u9.VH 5f±~"1253;652&R2,!9&%=iGH%/Gs %Pw=pwgff*9;;<ի[YYI\.!`kB?\Ե8q,uƌcj,]7uDkO^Rg6kn JRxg&lPVT\+Pk/Rf߾}I]z'VLlNqϱuV2\ oBNoJL$sRN(Gjoo 6χȳu'7*Ul!bDDĨQ4Ipbɒ%Ž PO/ 6^?ʰOoN-Wnr`$իWt`eeRzc'pq==-& YkHǏ:uٳ5,&5y p3iyznCN?um:K,UJU`keI? = hh7| Or> 4Of}Pzm<{S` 劼ujiXVV$]&t;d ]=¸ G-dA\OwuU00aL>A`q.9dOO3f?fxxf1;Dׇ/P4Fi)>>X{ }U1ǽ?ݹs' ֐ݵkqML>5+GleVdW:6߹s͛C`/[ )Z.egg ru Ƌr6 Ne+)eUj|eb9Sl$t:UX!$]Z〓5888**J3:0 g^ZS'$OX|rli?WT i1'UϠq{jD nPS—{?~ olCQ̞-<5oxW= 51 |> YW, )`UΏፙYO:s?`y׭[jA8p8 FJN=Pu.P 88 G|ӈ/TWܔUR y4i?}b<aBzao ҵ҃g{C:@}~@{if[!(Pj 惡&~)10eڈ,7bTyJwes}:͆aL 0(t <  sU85k6}t^T;IHH z`$fv GWWW;adժUciY䴰"}0RWr c#CNK@{͝;wv}IA-qƍ+mщf)СCkժY e Api6uӛ/f@pZvPGAulfN6^<[~\jҽ^^^K.xoϞ=;m4jżrQqQeIܼW~Pr$0%y@S3,Rcǎ )WfBSG9n 'gBU x{}\k- p۶mrH.vcXr%`rȾvohDwLl:zޮE=rPMEH۷OA-f888 .`8ǰknTKM(RYsJdjZ[<*>sNuvnjժW =;'6%HNx-҉K_հ6͆lӠR)E}||Ŀ7%M_Ԕǀa ۫>|ܹs[n ҹGǟyoxjj<@R_!wj 8 -f$$H-|PԤ9Ճb? rϞ=`f&& vU)S%],cxO3RƆR X*19 +C,2.3U_\LmUkѝ ģGRFD|"_InA|q׊)ǫV_aeڹA\J:m%&0Հ7-[xzzm`#;;PC^\\*,lѢD$Z(U d(7F%[E@\`dxBJhlw|td\QXj}\k9U4z8ҦـMjؾuȑHoFXzX5IgzX~Z;;v?ׯ̙3wRס!?/4@W!A1 \&ΎswܝN XC,OMLZS'|޽r-[ֶm^z&ھ575"M͐l-Q M4@ 899HX2{N՛"THII Nlw]sQwwwJtpp? 3 "CEFDp HDκg[먵ZZ*-n,Qlٛ'3 Cz򿼼q߽;)63Ye5*Kˁ#AMVԔ54L5#Kyő#@94X4pf'tС\HT{4ihЗYruVj+{"0`hBCC}||WV.E*bcc` O>#t zJAZ6hiim޼ `Æ K,VW}ձKjJҰijj;5T4F}lL(DӦMn\cg:8[|>PtP-/Li9S,޽{R__(mPQ$_rXmX"W^5bN |P.R&k}MՙAOh\\ ȭ761Y~v]ߊT*xek׮򘘘V002YN2E72d%%%fffjH^CPaKʟ :$Ukjfr^5'bɁC^%,b~H'TWE=z!z4vaÆ!O5xbc^#7k֬_~E:|`d8>\dNV;@໠dଡ଼)SZQ|(oweY$VナK"On_+’'N-y.bODDȑ#vK3cMvA"յ% !RSXͨl}8R`gϞM{eddܻwǥK'= f):id5lvYY T1*k *q߱ctuu###1i~h$)aԑ~ɓuݢZZS219mUB4y(.\pڵJ4ڮّK;ds~Y9܎ښ?)7>c . s p] o'''+&'fX[[ Rդ/j Bef $+^NzS|R]ߡP/>V*Bn߾u)I"&O> .Ab~~2̚м ܺukϞ=ΝC`j6#u,ZWP*aM; ;Ȃx"^lBYhW:Z1֝ :VG}Q_hqС{ Wx 6f&˽i/; " ޔҏCLrPA^]i<8k'bt XnԩS?& r>~;1)J7me`\n-ctޝRy@tt҄en^z!<  |d%@AG:88Y›jkNu}ca`Hi PDZAo+pz*Iȥ@\ t21 0/[! 3f./5Ix*ўHDU`gh Ք^z">"Immq#EcFJtAJp4Fv|RPkB!38Mg)Cd-F ʫ֟z06ABL1+ewΜ9nnn^.qOsv_PV2A>v 7;":6mZvv+tG7s#VVUR&LoyR)b|NNV] UǏ2E*ܳHIIKV6{QeTVW5n}iYu I~R0rl$".޽epb5v`1Y0>oDk2:XI;;;qƮ]}eN{“k*+MĐ}L6l@ .\PY`?>sG} ע1Q_Cu՘ "!%!EҩS'6...;vlll6భ[M p@ALLL (ihh*s.]ZG+(|>'J62Tԁh( rvn@0YW\Ye$_)k ÌԆ\iniHGR~jBMM`kDȋ.Qx)-[kד }7ہ_3>L_j$5kϛ7]:wr!"/ID#bsylWM]H|#G^v Յ D?'hXnuavv6$4ӧOElFБ$Mj\^iumniEzaizAIfQYAEue}#&RsY4nK'OlȥfPPرc}||deeq!clm-0"YLm.*e")Z )#S )ϓ6kTRo(ڡeHǣ+ѥ%2f KyH~6?'7弨hb/MIGY9?"55u̘1&466ޱcxW={A8ZlQQ:->xH???[ Ovr\|CYj, BA ά 7T ?B3q@5kր.4%<]LG= 0nVDL3glkj SFDDXZZe:M4ŋm&&2ƪU;v믿XBSS͛7ʼ+vhĤ,K+J/*)/!h J^NBɃk"uTOE\9i~nӦ3U}wϷU888 ʆ ;9ϖהkjتEB">HI&elh\tWϞ=w32mĴ-t ~ǎb~U^^nݻwwrrсWr޽V6mڂ K/gʕ7n%p/^VCQ̔􉉉-s(nݺMíZXX\N&^>/N%dS3[@rT$^.%,ŢS=lQ:4~VGĈ1cݛB0kI. aVH#x|Zy\$R'š$kiiNu\Zbb 9ɓd򋍍vy,Li|bK3z6P={p1&hS|m]D'P;w㳳Sra;;;*  fN<с)[o!,58yѳ5 ;)(be烵'c;N .-Hkע_nG9l0rfw 2'ɚMLtsx|2]AUI`ZƿA|u y|ePK0˩P8j~~>Ν355amqq:  "TZO+>/SL<E*((gff^lْ%KfIԤ i 0jtAY Ȁ֣G$(QsMryOK*+Yĝ_plp,x:pVn#G>|8==]\b|9Hz2Hl=% (6-UebbQQȷ$'jAO J'j H]]_SsoSSS޽vPS9bTO3rsK+]-:ڛko߾ z~?[033믿 >Lj͍%$/l[NP*4) 9eiu]$;Jk,6s]l=_pA\l[ #""°Ĭ=>6he&-l 0*hIk ?y6|||d;ϊʨil%I^^ހ͛: J+K9A]tiݺub A R(۷~wѣG.~9HIɻw殮 30R`<<<@\&]Ԝ]R[80yYeMc37*Ń|-@I59t J8Trr= >6*38oRf{2EBbCfIɚ|4"VF4[`͓TKEV!KV\ |ahᶶ(p>z@⻤9BNmsqnܸM-ѡCO:=-y߹sD0Sv ZFI,0;qNAF!(F hR=jf(ܹs:I{F2dHk00kG5${hPhf뛥Ś"zM$4ђ[i 6G\ yw"dIIxhvvvݺuٳgLvݻ'DE^~rಐ!ɎSOOO1Nr )))½]tqwwGn^dU%f'fVT F(TU.(P`'~gg)90ڦfp@MByt<+wrrww07!E^; ,ѣG7:::1k~L466h%ո& q`#8Io[2h\`` l$#7yq~yu}3TYIx8W-.MlWGC56M ۷ܹs~ױcǐq]]]q!c$P[[T*bA])z,A*MtIrssEAk.ߢѸc/M@N"_\_ddd~DPPК5ktW^Y ☌<,RQ#S&d>WTPQ ~8U %zZjloffgo#??hu\[sԻwoP( ̚ ȅ`IA QW#5[.ڵ}ҪK+ք5ur;j  Hy;tp+VlڴI[@K@c P' ccc_pã[YYM۷{"DKJ+d=4qN*Д)1+. 6hz:taeAfƍQQQ.\U 6,<<%5?%@hngy]t5hѬToQԏkע磡JԔf&;Vp!(HP"xEEEcǎݷocܸq#l'M>1?[kGt!=*r8y˖-ѣ:fo34""2𺙙(ASd95nzֽ酥u tT d%^IѪ4ehOf ˗/ݻU@2fd;2]IMt5eeduX䦦do3߿_9g̘-Y^^~9Xwʋz$T#5˹Ď;&2[Jol` tUWj[|ٳgA  ;]\vY̇5L6J}3z0Z2Xl%K!~P/!Vn5?#\,\^SS+PD®SN .===m@[kN.8wXǏw<5kPW^q+5p2:5@ƃfoڴL5oh?>}3g`@vpp`&ƤdM~Z F8vmUu Ko*KS[%4."a|YRR6P +ol*.b'Ƞ :֭[d1ggpXFBll>@˗//\6—-[͋NddnK$9qDKU #G#Ly)rGPPP@@5u +'?˪!*1ċKeE ɩ=z4''GsB=ztY5?Hk9RY(~ TʬIf.(kjbj̖-[Ef̘qZD<z200ku+O/x!$ `%ZbJJJo඼Ç_~,A\e~Y>~^R]%Pb|~'A.6 .Q .|cUaٳ' Pd=XѣGwFӻw* LK;ʬnlmp&41XNOHa8VN0yرgϢc:4|phK褐#WVV?~GcnܗZf52C n߾'pLMM탠ϟ' ˡC::: [Z~#壜x@tC6*4_.@Lڜ8qB$mx1M? fBmD8s˗=l(6*((tЕ"ilhfn6b ek*61zu=[y9Ǐs;>|>3߿?dȐ('O92WDQ8gbOSiTjiiH#99wG 744bow9_T^KJ(Qv~UC5<!; t t3{r|ܨ(p6F@s񉌌 6`|Z3::zĉ(eo~ 1 $c/..F=-(iTAy5J{;큗jɐrGEbLZ]8u͛7Ò ۲xҤI544@ݢu %e7h"֙I#x\]p;s Xɻ͐K7f<5AG;( 괰Gϟ`0.]q._,V60%LpM s\ p:s?G/>w~~>UǏ(?3g 7tƨ̒r=PMNSM^x$\l777onh%NP( 7nKᥖʣ!ܺu+Rf3f@ #oKjAMe̙6l #n޼yܹ(*=>Ji(#w%rr`yF]I*tڶoۛlbbr- u8C0dӦM\-b֬Y[3)L(r43}|(A<2H ENiyZwvppv8p2*p@{/vqqMMM.\~:3j(Ν;X4Uc(HSДBsε<K졬GId(A9n%^MzZR]  v5,.1$\n3BW2ŴX;qDĥ%tPEuKeM mDKN}WV/BHqqqodͪ\"QҚ KۙLj[ &=y?=z@WW۳ )BPY='ң9@`9s@E:u;Ai %jՓHNN>ycDz={D^T,;x|BJRf[SYqD|]~~~يŋ7l@f1cFPP9-BB[sl"ŐԋqɁVi56DX\9s\[nI>r%HL2z 'ʚ7@ɭ8\P3*I$\ݻwx挌 $0;ᶗ5Yl aӑnܸ!'' l`lZ&H=r H&FO wҊ  PEZ566 ZxsssyyyQQ=RUU588T8ɗ`1m<{TfVʨHee;ֹ`j44$^!ܙ~}!*zq${IyH\*Pp\ W"yhO.}{zq4p.!A_ 5!]&C LEKN-$6~W#=4BaĠ R&dQ奈ae.C,ƿsl^b7=_;doJfӇJs;wn7oDQuQd!!! LQ.>ydF&K_Cu1]-VsdnܸQBrJ?644ty9Q.?~ZM(~,.1$2 6\]ü\Ţ5{Vɓ8 Aؿv-Ȭ $D$V1r~2˥P~@ TTT NHҋ h𷬦.-DjJt/k8bff&yŁ|'=J_'"?˷Ҳ ú|0[n%$$,--6>JAA>PH,$&})1mʾ%53weIpʄ"jugܸq`YxB(UV'?@%kw_sI6Q)Hk8(& .t5^@}=ڮ5666#G #'``|NINlp,LO֖Xa999:oL}}֭~,i@ʻv6LH7n Cbۻw/ Ang0{deg } > XeeeAU$c0V} )=~_ 'yM6LD V)֮] tO7D/>Hy6=H%*z7Kݱ= `ޮ=,xϟqyuv$?P9%\퍁)&"6hhZJb e OF8FCCC]]\y"C,cǎuÆ C$:1U)3lJBEi5`.k'iܰH7%թ{I9sji*L ʅ)fe|F PzP`ff[8| x>cƌ.X <*Ǐ駟o/'o-f AHښiii8|0'!lmm>0͚EEE(Z"=SZ` Cދ "KQ@X,2DG.`,IͤspVHE.}!1}t'''+++7_G^uFQAA$0 7(jnmU0Ůmh*}^^O]R]:pf_ܼyyHLy1"̅ 8qBX??Θ1 Vpq7>q1$a8Z*J@\YR)dLttݻ/_,yK,.1@LIIihh@6JjTyY`<ҥKW"Dpi$BCCyc0Y_K= ݭ0jjj0r<`˨t % !eP"/&SGr]<__Kq_ܹuM6-])QF-[e=pp`}!c`Qt1CZ}!p{-!A0ŚOD2l;Wy@݌: z)ݻ֑ Ž; SW]NT74Iy@g@JhķKL￯f,Yxk\:(d:6Xb<їHL5]mü\B=z4**-Ut5^s&9 XNSہ`-Zta2h'"Q"jjj 1/w˅ Xg" u۶m◂]p!$$\CCcg>9 (Ƿ?گ{˱Ƙ ŵ[MMMVshWVVΛ7eA۸q*.={2rz xL6"bqK}׍ݻ{9uTEE'TPP@ @b>_kdrA('%Gb477_bܹsW'O>pypVVք rrr`{(nsTiY^#Eprr>.^55:u|T)Ⱦ.KM.em1aI3 ֭[&MBSNSG5 MkO^{\jχJ^=;̙͡3wnWtu33Ç5/WĚ6h9@?-مN?s}9sAwu..( JMMMr r柹ca?R{mVVVGCBBdZ֓,)"u^>[YVXXhgg.|mRRxqMr'Y{LɨoQ)Rws%x,p<VO+,n#GҥK/^򫦦&x T^^^vBkdǢAC䗸I`T&KN2˭Xtt4T' 0,,lђ &)nlӧO(uTQmj/kiR8˨ 7ӻw? QVVѣ4*yh`C3cC1i]'W !")CdDA _-˗-[=;ÿKiג=)(odsy_Be x@|>BQW4H'p׮]{ܧo˯GSLY~=ƐW3T4Ym 5}X/W{3cJuС}ʔ . "I>] /\kB3f ^hʕ+a#tֻOs[%623}DJedd gL|Ӿ o*]]]EVõ&2_@EA9;v #0ۻw>y$ ( }_ Gڵ5p" Ke9&5%kQtfFQق]pw IA ?G$~e,^”)Cdر[8;;968YQX`5XS)J4k׮A>22rFFFx1֔QHH>}Dؠ_6ȴ`**[4WSsuɹ73d72165/<}`Y$tttoߞ;wnb+**rѣ<Y(62)).23~~[kF @WWԩS~߇/=LŽX&]&fZЖ8 Lh 0aB~5EpE >)z絔gb4`nS@S)lipriTt|)+ȟ` r/^prrjIVZpa['7}t2YXXڶ277>TVVہFL9NSSbyy98YYYpb0IMt1gϞ6lhk3lҟRp.L׭29^Mի;v&WB5ja``֔YrO?nݺG٭DsS};$Kk5qf4aaaǏ9 x"qZb͚5˗/o5ó{ /Wvr\ 0'Kb {ʼt~?ϨZ.R|l3رcK^Q H00kJ7oQfx{{%"~y-piC'[ǭ[,kyѣGM%9!B@}_~cǎ8pcr&$$ 0 (˗/;88A&II)+ S`n7={|^ŋ)))p->} V|X--N::;;Y6^ѣG|&@]a_B7H!hG궝 ]:w7567T{'1*j3fM+*4ʇKu ^vpuB$cG+qѣĉCBBPy ̚у ޳pUVVg2gm;|^; &dwc| 8NvvvXXX)_4/$ZZZZ\\\YY +((ijjhutxYkkkmll@…XƯs11M]o,l6렪 LmʼAdπ\] ORUU *YWW>\슺g=M,ƃm%Sx{t}ևfm۶+WH𕔔޽{x_ ԩS{>ChϿb,\X…ciL0zOA766vԨQ:h7nEj Kx@P\a+-e=q[,ܺtl |ia䴴L% ,,,@O;88Lho8qr,MA}]sh&w4C8\UUաCvؑ$LLLEFF(5 P(޹|rbpѓG.>~GD3L4) p~#1 nʹ_ti/TIN .X ɚ c111"ɺ̢MAYT)4X[DRUмى'رC$E^g7}{sb:~탕f=z4Ѝ\XYHݵ͊p]"|¼]u_zBMMM'Nزe+/mmm'M_L@c ǚ޽{J]]}Ĉ`+ۻV:55ɓNOOs"c~/{^^q3tÜ:׮A޽l_Ox޽{<(y212FtKva.ܮŋQ.3@QE/{ /P!&s47#AU}m߾^g۷)SL| O5eaÆٳIԙL&Ju֭;w$''lڵݻ_w?o8}uѾ3߸O ح[9sF q ̬sΦ;v]F&"X.QSSRQQQnnnبrAAAV" \5MJ_qP>#"'l\_ρdu(;v3$ar%%ogtYP[[;qĖYGD //ohhvY___SS`. &\i¶&ǶĨQ6le~tE9::ĄRҲ'FH/^rrJNLodеk.]%K!'!5h#IE.$#Nu߯[A'tah\`̘1Ǐ+I00>DxbI`իWeOyyyVVVTUUsrr TR_Yƍ¢@GMKKsX޽m֥Kخih)dY |T8[_=o޼yS$ z2y䈈wUBBB̙3[ tN0bĈ+VH‘#G6!&&fʕKcc!Cxyy)))}۷?JV3$$`}gt[NtŗʊnvvfxEGGoܸJx*xӧOqr ϓ5?K͏!+ v޼y_ɛ3gtҾ}VTTܹS(4O>F4hG . .Ԇ @zLbժU .DkN\Z}JDlH7*r,e F8<X{TTԁ>|6133 9rzwfHێ;JC_mKEgN_]u4Z !ROCuhO}{/g4|S:;;c놁E&\=11ƍwIJJ˓W=zeꪬ: p>+'''0Cȑ#?SVVV˯BCC} N^x |G|i2˼7۲e_zzzcǎ8qbΝ]bYSMMMO>333^С#J/>>>g>>224zߺ,ZA쐙 |(ر drMRU}.ԗ˲;vof͚>[աrɒ%/_AoS]]z,:^Z]]]DDą Dϟ?հQRUւ:? kd6a@O-[$ 3`K\100kbH挌Gikk;;;~bMk.۷o4ilN/O̰DQUuB_غu$|)''rΜ9޸c``ӧիBvhUOn!?od25z:Om*xeeV%J }a```x3@q111>l_\B'3 ڷRU{8L*^y7DIIiĈ3gtpp&FK$eeeKjn*k艬=S|xxuuuw޸q$G={nݺYC477^~]xڵk͛g}(51Yr}G[޳g333xUU3fXZZ7Yŋ^zedd{߿okk u^Gd\eo:thՒ;vYpp ̚ III//7o?/ Eͧeq8L6xf72q8q߃$˙3gh510%@EDDٲeԩSac:a! t NWxpʕ+$MǏ&Zmm픔}}&fVi}= L} ձ}=' TyV:{Ϡ4f<&Ƈ@ccp(AКGON吊"}\ZK#DSy`?h?իrşAAArܹN6 ̚_>nܸѧO%&&r_$f(* Tf?GyC:Y%%%ׯߺukCCɅNNNa``И4iΝ;ɏhAńqU(l.v27͎Ի۶m[v-0dȐ{xxෆY㠬 eyy9)ݹ<^вs ^n!L}"zS~:rJIoѢE}a`|EBWWwɒ%rr{qhb<=..9Q;w#""H,Ϝ9seLXkb`|`2NNN#BydCViY.ݻyyy+Vسgz#GÎ)֚ h4ҥKɏ? Ѕ܀{B_SWY;>Ɉ2@źڵK*++zQ]gzCԛML6U^.mސzh3g,Y"3gΜ5k~)Xkb`|XN$?666ݻ649YJr LJ"|iXXXppxʤP(&Mz)kM Ol6955}m:= T%( @IqЦko[?SFc``ـJΙ3}pjfkb ft643:[[2}`?D/^ѣpxtpp8qą 0eb``0|pccc#ꤥ۱ح73Ք7~;,]`gAAAddd@@@rrk[....$$<fM ***@xK. W;ue:# 1̡Nvܹ=**JUL8w\EEE\510>Wkh/7449s6::wd8SW]e[g2քiii&M*--s~??ح[yK.#?=zm pp2lBbz:_:#rG=_1g611%zuOOO\510;ܾw^vv6l瑩XPVf2?~ܧO狙CPf͚0a\51(m3̋/†NWg>Ǎ^^^111bܼysƍl100dzM/ӦMۺu+I.]ΆzT