├── .travis.yml ├── LICENSE ├── README.md ├── buntdb.go ├── buntdb_test.go └── logo.png /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Josh Baker 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | BuntDB 5 |
6 | Build Status 7 | Code Coverage 8 | Go Report Card 9 | GoDoc 10 |

11 | 12 | BuntDB is a low-level, in-memory, key/value store in pure Go. 13 | It persists to disk, is ACID compliant, and uses locking for multiple 14 | readers and a single writer. It supports custom indexes and geospatial 15 | data. It's ideal for projects that need a dependable database and favor 16 | speed over data size. 17 | 18 | Features 19 | ======== 20 | 21 | - In-memory database for [fast reads and writes](#performance) 22 | - Embeddable with a [simple API](https://godoc.org/github.com/tidwall/buntdb) 23 | - [Spatial indexing](#spatial-indexes) for up to 20 dimensions; Useful for Geospatial data 24 | - Index fields inside [JSON](#json-indexes) documents 25 | - [Collate i18n Indexes](#collate-i18n-indexes) using the optional [collate package](https://github.com/tidwall/collate) 26 | - Create [custom indexes](#custom-indexes) for any data type 27 | - Support for [multi value indexes](#multi-value-index); Similar to a SQL multi column index 28 | - [Built-in types](#built-in-types) that are easy to get up & running; String, Uint, Int, Float 29 | - Flexible [iteration](#iterating) of data; ascending, descending, and ranges 30 | - [Durable append-only file](#append-only-file) format for persistence 31 | - Option to evict old items with an [expiration](#data-expiration) TTL 32 | - Tight codebase, under 2K loc using the `cloc` command 33 | - ACID semantics with locking [transactions](#transactions) that support rollbacks 34 | 35 | 36 | Getting Started 37 | =============== 38 | 39 | ## Installing 40 | 41 | To start using BuntDB, install Go and run `go get`: 42 | 43 | ```sh 44 | $ go get -u github.com/tidwall/buntdb 45 | ``` 46 | 47 | This will retrieve the library. 48 | 49 | 50 | ## Opening a database 51 | 52 | The primary object in BuntDB is a `DB`. To open or create your 53 | database, use the `buntdb.Open()` function: 54 | 55 | ```go 56 | package main 57 | 58 | import ( 59 | "log" 60 | 61 | "github.com/tidwall/buntdb" 62 | ) 63 | 64 | func main() { 65 | // Open the data.db file. It will be created if it doesn't exist. 66 | db, err := buntdb.Open("data.db") 67 | if err != nil { 68 | log.Fatal(err) 69 | } 70 | defer db.Close() 71 | 72 | ... 73 | } 74 | ``` 75 | 76 | It's also possible to open a database that does not persist to disk by using `:memory:` as the path of the file. 77 | 78 | ```go 79 | buntdb.Open(":memory:") // Open a file that does not persist to disk. 80 | ``` 81 | 82 | ## Transactions 83 | 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. 84 | 85 | 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. 86 | 87 | 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. 88 | 89 | ### Read-only Transactions 90 | 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. 91 | 92 | ```go 93 | err := db.View(func(tx *buntdb.Tx) error { 94 | ... 95 | return nil 96 | }) 97 | ``` 98 | 99 | ### Read/write Transactions 100 | 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. 101 | 102 | ```go 103 | err := db.Update(func(tx *buntdb.Tx) error { 104 | ... 105 | return nil 106 | }) 107 | ``` 108 | 109 | ## Setting and getting key/values 110 | 111 | To set a value you must open a read/write transaction: 112 | 113 | ```go 114 | err := db.Update(func(tx *buntdb.Tx) error { 115 | _, _, err := tx.Set("mykey", "myvalue", nil) 116 | return err 117 | }) 118 | ``` 119 | 120 | 121 | To get the value: 122 | 123 | ```go 124 | err := db.View(func(tx *buntdb.Tx) error { 125 | val, err := tx.Get("mykey") 126 | if err != nil{ 127 | return err 128 | } 129 | fmt.Printf("value is %s\n", val) 130 | return nil 131 | }) 132 | ``` 133 | 134 | Getting non-existent values will cause an `ErrNotFound` error. 135 | 136 | ### Iterating 137 | All keys/value pairs are ordered in the database by the key. To iterate over the keys: 138 | 139 | ```go 140 | err := db.View(func(tx *buntdb.Tx) error { 141 | err := tx.Ascend("", func(key, value string) bool { 142 | fmt.Printf("key: %s, value: %s\n", key, value) 143 | }) 144 | return err 145 | }) 146 | ``` 147 | 148 | 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. 149 | 150 | 151 | ## Custom Indexes 152 | 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). 153 | 154 | 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. 155 | 156 | For example, let's say you want to create an index for ordering names: 157 | 158 | ```go 159 | db.CreateIndex("names", "*", buntdb.IndexString) 160 | ``` 161 | 162 | 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 163 | 164 | Now you can add various names: 165 | 166 | ```go 167 | db.Update(func(tx *buntdb.Tx) error { 168 | tx.Set("user:0:name", "tom", nil) 169 | tx.Set("user:1:name", "Randi", nil) 170 | tx.Set("user:2:name", "jane", nil) 171 | tx.Set("user:4:name", "Janet", nil) 172 | tx.Set("user:5:name", "Paula", nil) 173 | tx.Set("user:6:name", "peter", nil) 174 | tx.Set("user:7:name", "Terri", nil) 175 | return nil 176 | }) 177 | ``` 178 | 179 | Finally you can iterate over the index: 180 | 181 | ```go 182 | db.View(func(tx *buntdb.Tx) error { 183 | tx.Ascend("names", func(key, val string) bool { 184 | fmt.Printf(buf, "%s %s\n", key, val) 185 | return true 186 | }) 187 | return nil 188 | }) 189 | ``` 190 | The output should be: 191 | ``` 192 | user:2:name jane 193 | user:4:name Janet 194 | user:5:name Paula 195 | user:6:name peter 196 | user:1:name Randi 197 | user:7:name Terri 198 | user:0:name tom 199 | ``` 200 | 201 | The pattern parameter can be used to filter on keys like this: 202 | 203 | ```go 204 | db.CreateIndex("names", "user:*", buntdb.IndexString) 205 | ``` 206 | 207 | Now only items with keys that have the prefix `user:` will be added to the `names` index. 208 | 209 | 210 | ### Built-in types 211 | Along with `IndexString`, there is also `IndexInt`, `IndexUint`, and `IndexFloat`. 212 | These are built-in types for indexing. You can choose to use these or create your own. 213 | 214 | So to create an index that is numerically ordered on an age key, we could use: 215 | 216 | ```go 217 | db.CreateIndex("ages", "user:*:age", buntdb.IndexInt) 218 | ``` 219 | 220 | And then add values: 221 | 222 | ```go 223 | db.Update(func(tx *buntdb.Tx) error { 224 | tx.Set("user:0:age", "35", nil) 225 | tx.Set("user:1:age", "49", nil) 226 | tx.Set("user:2:age", "13", nil) 227 | tx.Set("user:4:age", "63", nil) 228 | tx.Set("user:5:age", "8", nil) 229 | tx.Set("user:6:age", "3", nil) 230 | tx.Set("user:7:age", "16", nil) 231 | return nil 232 | }) 233 | ``` 234 | 235 | ```go 236 | db.View(func(tx *buntdb.Tx) error { 237 | tx.Ascend("ages", func(key, val string) bool { 238 | fmt.Printf(buf, "%s %s\n", key, val) 239 | return true 240 | }) 241 | return nil 242 | }) 243 | ``` 244 | 245 | The output should be: 246 | ``` 247 | user:6:age 3 248 | user:5:age 8 249 | user:2:age 13 250 | user:7:age 16 251 | user:0:age 35 252 | user:1:age 49 253 | user:4:age 63 254 | ``` 255 | 256 | ## Spatial Indexes 257 | 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. 258 | 259 | To create a spatial index use the `CreateSpatialIndex` function: 260 | 261 | ```go 262 | db.CreateSpatialIndex("fleet", "fleet:*:pos", buntdb.IndexRect) 263 | ``` 264 | 265 | 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/). 266 | 267 | To add some lon,lat points to the `fleet` index: 268 | 269 | ```go 270 | db.Update(func(tx *buntdb.Tx) error { 271 | tx.Set("fleet:0:pos", "[-115.567 33.532]", nil) 272 | tx.Set("fleet:1:pos", "[-116.671 35.735]", nil) 273 | tx.Set("fleet:2:pos", "[-113.902 31.234]", nil) 274 | return nil 275 | }) 276 | ``` 277 | 278 | And then you can run the `Intersects` function on the index: 279 | 280 | ```go 281 | db.View(func(tx *buntdb.Tx) error { 282 | tx.Intersects("fleet", "[-117 30],[-112 36]", func(key, val string) bool { 283 | ... 284 | return true 285 | }) 286 | return nil 287 | }) 288 | ``` 289 | 290 | This will get all three positions. 291 | 292 | ### k-Nearest Neighbors 293 | 294 | Use the `Nearby` function to get all the positions in order of nearest to farthest : 295 | 296 | ```go 297 | db.View(func(tx *buntdb.Tx) error { 298 | tx.Nearby("fleet", "[-113 33]", func(key, val string, dist float64) bool { 299 | ... 300 | return true 301 | }) 302 | return nil 303 | }) 304 | ``` 305 | 306 | ### Spatial bracket syntax 307 | 308 | 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`. 309 | 310 | - **2D rectangle:** `[10 15],[20 25]` 311 | *Min XY: "10x15", Max XY: "20x25"* 312 | 313 | - **3D rectangle:** `[10 15 12],[20 25 18]` 314 | *Min XYZ: "10x15x12", Max XYZ: "20x25x18"* 315 | 316 | - **2D point:** `[10 15]` 317 | *XY: "10x15"* 318 | 319 | - **LonLat point:** `[-112.2693 33.5123]` 320 | *LatLon: "33.5123 -112.2693"* 321 | 322 | - **LonLat bounding box:** `[-112.26 33.51],[-112.18 33.67]` 323 | *Min LatLon: "33.51 -112.26", Max LatLon: "33.67 -112.18"* 324 | 325 | **Notice:** The longitude is the Y axis and is on the left, and latitude is the X axis and is on the right. 326 | 327 | You can also represent `Infinity` by using `-inf` and `+inf`. 328 | For example, you might have the following points (`[X Y M]` where XY is a point and M is a timestamp): 329 | ``` 330 | [3 9 1] 331 | [3 8 2] 332 | [4 8 3] 333 | [4 7 4] 334 | [5 7 5] 335 | [5 6 6] 336 | ``` 337 | 338 | You can then do a search for all points with `M` between 2-4 by calling `Intersects`. 339 | 340 | ```go 341 | tx.Intersects("points", "[-inf -inf 2],[+inf +inf 4]", func(key, val string) bool { 342 | println(val) 343 | return true 344 | }) 345 | ``` 346 | 347 | Which will return: 348 | 349 | ``` 350 | [3 8 2] 351 | [4 8 3] 352 | [4 7 4] 353 | ``` 354 | 355 | ## JSON Indexes 356 | Indexes can be created on individual fields inside JSON documents. BuntDB uses [GJSON](https://github.com/tidwall/gjson) under the hood. 357 | 358 | For example: 359 | 360 | ```go 361 | package main 362 | 363 | import ( 364 | "fmt" 365 | 366 | "github.com/tidwall/buntdb" 367 | ) 368 | 369 | func main() { 370 | db, _ := buntdb.Open(":memory:") 371 | db.CreateIndex("last_name", "*", buntdb.IndexJSON("name.last")) 372 | db.CreateIndex("age", "*", buntdb.IndexJSON("age")) 373 | db.Update(func(tx *buntdb.Tx) error { 374 | tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":38}`, nil) 375 | tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":47}`, nil) 376 | tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":52}`, nil) 377 | tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":28}`, nil) 378 | return nil 379 | }) 380 | db.View(func(tx *buntdb.Tx) error { 381 | fmt.Println("Order by last name") 382 | tx.Ascend("last_name", func(key, value string) bool { 383 | fmt.Printf("%s: %s\n", key, value) 384 | return true 385 | }) 386 | fmt.Println("Order by age") 387 | tx.Ascend("age", func(key, value string) bool { 388 | fmt.Printf("%s: %s\n", key, value) 389 | return true 390 | }) 391 | fmt.Println("Order by age range 30-50") 392 | tx.AscendRange("age", `{"age":30}`, `{"age":50}`, func(key, value string) bool { 393 | fmt.Printf("%s: %s\n", key, value) 394 | return true 395 | }) 396 | return nil 397 | }) 398 | } 399 | ``` 400 | 401 | Results: 402 | 403 | ``` 404 | Order by last name 405 | 3: {"name":{"first":"Carol","last":"Anderson"},"age":52} 406 | 4: {"name":{"first":"Alan","last":"Cooper"},"age":28} 407 | 1: {"name":{"first":"Tom","last":"Johnson"},"age":38} 408 | 2: {"name":{"first":"Janet","last":"Prichard"},"age":47} 409 | 410 | Order by age 411 | 4: {"name":{"first":"Alan","last":"Cooper"},"age":28} 412 | 1: {"name":{"first":"Tom","last":"Johnson"},"age":38} 413 | 2: {"name":{"first":"Janet","last":"Prichard"},"age":47} 414 | 3: {"name":{"first":"Carol","last":"Anderson"},"age":52} 415 | 416 | Order by age range 30-50 417 | 1: {"name":{"first":"Tom","last":"Johnson"},"age":38} 418 | 2: {"name":{"first":"Janet","last":"Prichard"},"age":47} 419 | ``` 420 | 421 | ## Multi Value Index 422 | With BuntDB it's possible to join multiple values on a single index. 423 | 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. 424 | 425 | In this example we are creating a multi value index on "name.last" and "age": 426 | 427 | ```go 428 | db, _ := buntdb.Open(":memory:") 429 | db.CreateIndex("last_name_age", "*", buntdb.IndexJSON("name.last"), buntdb.IndexJSON("age")) 430 | db.Update(func(tx *buntdb.Tx) error { 431 | tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":38}`, nil) 432 | tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":47}`, nil) 433 | tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":52}`, nil) 434 | tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":28}`, nil) 435 | tx.Set("5", `{"name":{"first":"Sam","last":"Anderson"},"age":51}`, nil) 436 | tx.Set("6", `{"name":{"first":"Melinda","last":"Prichard"},"age":44}`, nil) 437 | return nil 438 | }) 439 | db.View(func(tx *buntdb.Tx) error { 440 | tx.Ascend("last_name_age", func(key, value string) bool { 441 | fmt.Printf("%s: %s\n", key, value) 442 | return true 443 | }) 444 | return nil 445 | }) 446 | 447 | // Output: 448 | // 5: {"name":{"first":"Sam","last":"Anderson"},"age":51} 449 | // 3: {"name":{"first":"Carol","last":"Anderson"},"age":52} 450 | // 4: {"name":{"first":"Alan","last":"Cooper"},"age":28} 451 | // 1: {"name":{"first":"Tom","last":"Johnson"},"age":38} 452 | // 6: {"name":{"first":"Melinda","last":"Prichard"},"age":44} 453 | // 2: {"name":{"first":"Janet","last":"Prichard"},"age":47} 454 | ``` 455 | 456 | ## Descending Ordered Index 457 | Any index can be put in descending order by wrapping it's less function with `buntdb.Desc`. 458 | 459 | ```go 460 | db.CreateIndex("last_name_age", "*", 461 | buntdb.IndexJSON("name.last"), 462 | buntdb.Desc(buntdb.IndexJSON("age"))) 463 | ``` 464 | 465 | This will create a multi value index where the last name is ascending and the age is descending. 466 | 467 | ## Collate i18n Indexes 468 | 469 | Using the external [collate package](https://github.com/tidwall/collate) it's possible to create 470 | 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. 471 | 472 | To install: 473 | 474 | ``` 475 | go get -u github.com/tidwall/collate 476 | ``` 477 | 478 | For example: 479 | 480 | ```go 481 | import "github.com/tidwall/collate" 482 | 483 | // To sort case-insensitive in French. 484 | db.CreateIndex("name", "*", collate.IndexString("FRENCH_CI")) 485 | 486 | // To specify that numbers should sort numerically ("2" < "12") 487 | // and use a comma to represent a decimal point. 488 | db.CreateIndex("amount", "*", collate.IndexString("FRENCH_NUM")) 489 | ``` 490 | 491 | There's also support for Collation on JSON indexes: 492 | 493 | ```go 494 | db.CreateIndex("last_name", "*", collate.IndexJSON("CHINESE_CI", "name.last")) 495 | ``` 496 | 497 | Check out the [collate project](https://github.com/tidwall/collate) for more information. 498 | 499 | ## Data Expiration 500 | Items can be automatically evicted by using the `SetOptions` object in the `Set` function to set a `TTL`. 501 | 502 | ```go 503 | db.Update(func(tx *buntdb.Tx) error { 504 | tx.Set("mykey", "myval", &buntdb.SetOptions{Expires:true, TTL:time.Second}) 505 | return nil 506 | }) 507 | ``` 508 | 509 | 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. 510 | 511 | ## Delete while iterating 512 | BuntDB does not currently support deleting a key while in the process of iterating. 513 | As a workaround you'll need to delete keys following the completion of the iterator. 514 | 515 | ```go 516 | var delkeys []string 517 | tx.AscendKeys("object:*", func(k, v string) bool { 518 | if someCondition(k) == true { 519 | delkeys = append(delkeys, k) 520 | } 521 | return true // continue 522 | }) 523 | for _, k := range delkeys { 524 | if _, err = tx.Delete(k); err != nil { 525 | return err 526 | } 527 | } 528 | ``` 529 | 530 | ## Append-only File 531 | 532 | BuntDB uses an AOF (append-only file) which is a log of all database changes that occur from operations like `Set()` and `Delete()`. 533 | 534 | The format of this file looks like: 535 | ``` 536 | set key:1 value1 537 | set key:2 value2 538 | set key:1 value3 539 | del key:2 540 | ... 541 | ``` 542 | 543 | When the database opens again, it will read back the aof file and process each command in exact order. 544 | This read process happens one time when the database opens. 545 | From there on the file is only appended. 546 | 547 | As you may guess this log file can grow large over time. 548 | There's a background routine that automatically shrinks the log file when it gets too large. 549 | There is also a `Shrink()` function which will rewrite the aof file so that it contains only the items in the database. 550 | The shrink operation does not lock up the database so read and write transactions can continue while shrinking is in process. 551 | 552 | ### Durability and fsync 553 | 554 | 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`. 555 | 556 | The `Config.SyncPolicy` has the following options: 557 | 558 | - `Never` - fsync is managed by the operating system, less safe 559 | - `EverySecond` - fsync every second, fast and safer, this is the default 560 | - `Always` - fsync after every write, very durable, slower 561 | 562 | ## Config 563 | 564 | Here are some configuration options that can be use to change various behaviors of the database. 565 | 566 | - **SyncPolicy** adjusts how often the data is synced to disk. This value can be Never, EverySecond, or Always. Default is EverySecond. 567 | - **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. 568 | - **AutoShrinkMinSize** defines the minimum size of the aof file before an automatic shrink can occur. Default is 32MB. 569 | - **AutoShrinkDisabled** turns off automatic background shrinking. Default is false. 570 | 571 | To update the configuration you should call `ReadConfig` followed by `SetConfig`. For example: 572 | 573 | ```go 574 | 575 | var config buntdb.Config 576 | if err := db.ReadConfig(&config); err != nil{ 577 | log.Fatal(err) 578 | } 579 | if err := db.WriteConfig(config); err != nil{ 580 | log.Fatal(err) 581 | } 582 | ``` 583 | 584 | ## Performance 585 | 586 | How fast is BuntDB? 587 | 588 | Here are some example [benchmarks](https://github.com/tidwall/raft-buntdb#raftstore-performance-comparison) when using BuntDB in a Raft Store implementation. 589 | 590 | You can also run the standard Go benchmark tool from the project root directory: 591 | 592 | ``` 593 | go test --bench=. 594 | ``` 595 | 596 | ### BuntDB-Benchmark 597 | 598 | There's a [custom utility](https://github.com/tidwall/buntdb-benchmark) that was created specifically for benchmarking BuntDB. 599 | 600 | *These are the results from running the benchmarks on a MacBook Pro 15" 2.8 GHz Intel Core i7:* 601 | 602 | ``` 603 | $ buntdb-benchmark -q 604 | GET: 4609604.74 operations per second 605 | SET: 248500.33 operations per second 606 | ASCEND_100: 2268998.79 operations per second 607 | ASCEND_200: 1178388.14 operations per second 608 | ASCEND_400: 679134.20 operations per second 609 | ASCEND_800: 348445.55 operations per second 610 | DESCEND_100: 2313821.69 operations per second 611 | DESCEND_200: 1292738.38 operations per second 612 | DESCEND_400: 675258.76 operations per second 613 | DESCEND_800: 337481.67 operations per second 614 | SPATIAL_SET: 134824.60 operations per second 615 | SPATIAL_INTERSECTS_100: 939491.47 operations per second 616 | SPATIAL_INTERSECTS_200: 561590.40 operations per second 617 | SPATIAL_INTERSECTS_400: 306951.15 operations per second 618 | SPATIAL_INTERSECTS_800: 159673.91 operations per second 619 | ``` 620 | 621 | To install this utility: 622 | 623 | ``` 624 | go get github.com/tidwall/buntdb-benchmark 625 | ``` 626 | 627 | 628 | 629 | ## Contact 630 | Josh Baker [@tidwall](http://twitter.com/tidwall) 631 | 632 | ## License 633 | 634 | BuntDB source code is available under the MIT [License](/LICENSE). 635 | -------------------------------------------------------------------------------- /buntdb.go: -------------------------------------------------------------------------------- 1 | // Package buntdb implements a low-level in-memory key/value store in pure Go. 2 | // It persists to disk, is ACID compliant, and uses locking for multiple 3 | // readers and a single writer. Bunt is ideal for projects that need 4 | // a dependable database, and favor speed over data size. 5 | package buntdb 6 | 7 | import ( 8 | "bufio" 9 | "errors" 10 | "io" 11 | "os" 12 | "sort" 13 | "strconv" 14 | "strings" 15 | "sync" 16 | "time" 17 | 18 | "github.com/tidwall/btree" 19 | "github.com/tidwall/gjson" 20 | "github.com/tidwall/grect" 21 | "github.com/tidwall/match" 22 | "github.com/tidwall/rtree" 23 | ) 24 | 25 | var ( 26 | // ErrTxNotWritable is returned when performing a write operation on a 27 | // read-only transaction. 28 | ErrTxNotWritable = errors.New("tx not writable") 29 | 30 | // ErrTxClosed is returned when committing or rolling back a transaction 31 | // that has already been committed or rolled back. 32 | ErrTxClosed = errors.New("tx closed") 33 | 34 | // ErrNotFound is returned when an item or index is not in the database. 35 | ErrNotFound = errors.New("not found") 36 | 37 | // ErrInvalid is returned when the database file is an invalid format. 38 | ErrInvalid = errors.New("invalid database") 39 | 40 | // ErrDatabaseClosed is returned when the database is closed. 41 | ErrDatabaseClosed = errors.New("database closed") 42 | 43 | // ErrIndexExists is returned when an index already exists in the database. 44 | ErrIndexExists = errors.New("index exists") 45 | 46 | // ErrInvalidOperation is returned when an operation cannot be completed. 47 | ErrInvalidOperation = errors.New("invalid operation") 48 | 49 | // ErrInvalidSyncPolicy is returned for an invalid SyncPolicy value. 50 | ErrInvalidSyncPolicy = errors.New("invalid sync policy") 51 | 52 | // ErrShrinkInProcess is returned when a shrink operation is in-process. 53 | ErrShrinkInProcess = errors.New("shrink is in-process") 54 | 55 | // ErrPersistenceActive is returned when post-loading data from an database 56 | // not opened with Open(":memory:"). 57 | ErrPersistenceActive = errors.New("persistence active") 58 | 59 | // ErrTxIterating is returned when Set or Delete are called while iterating. 60 | ErrTxIterating = errors.New("tx is iterating") 61 | ) 62 | 63 | // DB represents a collection of key-value pairs that persist on disk. 64 | // Transactions are used for all forms of data access to the DB. 65 | type DB struct { 66 | mu sync.RWMutex // the gatekeeper for all fields 67 | file *os.File // the underlying file 68 | buf []byte // a buffer to write to 69 | keys *btree.BTree // a tree of all item ordered by key 70 | exps *btree.BTree // a tree of items ordered by expiration 71 | idxs map[string]*index // the index trees. 72 | exmgr bool // indicates that expires manager is running. 73 | flushes int // a count of the number of disk flushes 74 | closed bool // set when the database has been closed 75 | config Config // the database configuration 76 | persist bool // do we write to disk 77 | shrinking bool // when an aof shrink is in-process. 78 | lastaofsz int // the size of the last shrink aof size 79 | } 80 | 81 | // SyncPolicy represents how often data is synced to disk. 82 | type SyncPolicy int 83 | 84 | const ( 85 | // Never is used to disable syncing data to disk. 86 | // The faster and less safe method. 87 | Never SyncPolicy = 0 88 | // EverySecond is used to sync data to disk every second. 89 | // It's pretty fast and you can lose 1 second of data if there 90 | // is a disaster. 91 | // This is the recommended setting. 92 | EverySecond = 1 93 | // Always is used to sync data after every write to disk. 94 | // Slow. Very safe. 95 | Always = 2 96 | ) 97 | 98 | // Config represents database configuration options. These 99 | // options are used to change various behaviors of the database. 100 | type Config struct { 101 | // SyncPolicy adjusts how often the data is synced to disk. 102 | // This value can be Never, EverySecond, or Always. 103 | // The default is EverySecond. 104 | SyncPolicy SyncPolicy 105 | 106 | // AutoShrinkPercentage is used by the background process to trigger 107 | // a shrink of the aof file when the size of the file is larger than the 108 | // percentage of the result of the previous shrunk file. 109 | // For example, if this value is 100, and the last shrink process 110 | // resulted in a 100mb file, then the new aof file must be 200mb before 111 | // a shrink is triggered. 112 | AutoShrinkPercentage int 113 | 114 | // AutoShrinkMinSize defines the minimum size of the aof file before 115 | // an automatic shrink can occur. 116 | AutoShrinkMinSize int 117 | 118 | // AutoShrinkDisabled turns off automatic background shrinking 119 | AutoShrinkDisabled bool 120 | 121 | // OnExpired is used to custom handle the deletion option when a key 122 | // has been expired. 123 | OnExpired func(keys []string) 124 | 125 | // OnExpiredSync will be called inside the same transaction that is performing 126 | // the deletion of expired items. If OnExpired is present then this callback 127 | // will not be called. If this callback is present, then the deletion of the 128 | // timeed-out item is the explicit responsibility of this callback. 129 | OnExpiredSync func(key, value string, tx *Tx) error 130 | } 131 | 132 | // exctx is a simple b-tree context for ordering by expiration. 133 | type exctx struct { 134 | db *DB 135 | } 136 | 137 | // Default number of btree degrees 138 | const btreeDegrees = 64 139 | 140 | // Open opens a database at the provided path. 141 | // If the file does not exist then it will be created automatically. 142 | func Open(path string) (*DB, error) { 143 | db := &DB{} 144 | // initialize trees and indexes 145 | db.keys = btree.New(btreeDegrees, nil) 146 | db.exps = btree.New(btreeDegrees, &exctx{db}) 147 | db.idxs = make(map[string]*index) 148 | // initialize default configuration 149 | db.config = Config{ 150 | SyncPolicy: EverySecond, 151 | AutoShrinkPercentage: 100, 152 | AutoShrinkMinSize: 32 * 1024 * 1024, 153 | } 154 | // turn off persistence for pure in-memory 155 | db.persist = path != ":memory:" 156 | if db.persist { 157 | var err error 158 | // hardcoding 0666 as the default mode. 159 | db.file, err = os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0666) 160 | if err != nil { 161 | return nil, err 162 | } 163 | // load the database from disk 164 | if err := db.load(); err != nil { 165 | // close on error, ignore close error 166 | _ = db.file.Close() 167 | return nil, err 168 | } 169 | } 170 | // start the background manager. 171 | go db.backgroundManager() 172 | return db, nil 173 | } 174 | 175 | // Close releases all database resources. 176 | // All transactions must be closed before closing the database. 177 | func (db *DB) Close() error { 178 | db.mu.Lock() 179 | defer db.mu.Unlock() 180 | if db.closed { 181 | return ErrDatabaseClosed 182 | } 183 | db.closed = true 184 | if db.persist { 185 | db.file.Sync() // do a sync but ignore the error 186 | if err := db.file.Close(); err != nil { 187 | return err 188 | } 189 | } 190 | // Let's release all references to nil. This will help both with debugging 191 | // late usage panics and it provides a hint to the garbage collector 192 | db.keys, db.exps, db.idxs, db.file = nil, nil, nil, nil 193 | return nil 194 | } 195 | 196 | // Save writes a snapshot of the database to a writer. This operation blocks all 197 | // writes, but not reads. This can be used for snapshots and backups for pure 198 | // in-memory databases using the ":memory:". Database that persist to disk 199 | // can be snapshotted by simply copying the database file. 200 | func (db *DB) Save(wr io.Writer) error { 201 | var err error 202 | db.mu.RLock() 203 | defer db.mu.RUnlock() 204 | // use a buffered writer and flush every 4MB 205 | var buf []byte 206 | // iterated through every item in the database and write to the buffer 207 | db.keys.Ascend(func(item btree.Item) bool { 208 | dbi := item.(*dbItem) 209 | buf = dbi.writeSetTo(buf) 210 | if len(buf) > 1024*1024*4 { 211 | // flush when buffer is over 4MB 212 | _, err = wr.Write(buf) 213 | if err != nil { 214 | return false 215 | } 216 | buf = buf[:0] 217 | } 218 | return true 219 | }) 220 | if err != nil { 221 | return err 222 | } 223 | // one final flush 224 | if len(buf) > 0 { 225 | _, err = wr.Write(buf) 226 | if err != nil { 227 | return err 228 | } 229 | } 230 | return nil 231 | } 232 | 233 | // Load loads commands from reader. This operation blocks all reads and writes. 234 | // Note that this can only work for fully in-memory databases opened with 235 | // Open(":memory:"). 236 | func (db *DB) Load(rd io.Reader) error { 237 | db.mu.Lock() 238 | defer db.mu.Unlock() 239 | if db.persist { 240 | // cannot load into databases that persist to disk 241 | return ErrPersistenceActive 242 | } 243 | return db.readLoad(rd, time.Now()) 244 | } 245 | 246 | // index represents a b-tree or r-tree index and also acts as the 247 | // b-tree/r-tree context for itself. 248 | type index struct { 249 | btr *btree.BTree // contains the items 250 | rtr *rtree.RTree // contains the items 251 | name string // name of the index 252 | pattern string // a required key pattern 253 | less func(a, b string) bool // less comparison function 254 | rect func(item string) (min, max []float64) // rect from string function 255 | db *DB // the origin database 256 | opts IndexOptions // index options 257 | } 258 | 259 | // match matches the pattern to the key 260 | func (idx *index) match(key string) bool { 261 | if idx.pattern == "*" { 262 | return true 263 | } 264 | if idx.opts.CaseInsensitiveKeyMatching { 265 | for i := 0; i < len(key); i++ { 266 | if key[i] >= 'A' && key[i] <= 'Z' { 267 | key = strings.ToLower(key) 268 | break 269 | } 270 | } 271 | } 272 | return match.Match(key, idx.pattern) 273 | } 274 | 275 | // clearCopy creates a copy of the index, but with an empty dataset. 276 | func (idx *index) clearCopy() *index { 277 | // copy the index meta information 278 | nidx := &index{ 279 | name: idx.name, 280 | pattern: idx.pattern, 281 | db: idx.db, 282 | less: idx.less, 283 | rect: idx.rect, 284 | opts: idx.opts, 285 | } 286 | // initialize with empty trees 287 | if nidx.less != nil { 288 | nidx.btr = btree.New(btreeDegrees, nidx) 289 | } 290 | if nidx.rect != nil { 291 | nidx.rtr = rtree.New(nidx) 292 | } 293 | return nidx 294 | } 295 | 296 | // rebuild rebuilds the index 297 | func (idx *index) rebuild() { 298 | // initialize trees 299 | if idx.less != nil { 300 | idx.btr = btree.New(btreeDegrees, idx) 301 | } 302 | if idx.rect != nil { 303 | idx.rtr = rtree.New(idx) 304 | } 305 | // iterate through all keys and fill the index 306 | idx.db.keys.Ascend(func(item btree.Item) bool { 307 | dbi := item.(*dbItem) 308 | if !idx.match(dbi.key) { 309 | // does not match the pattern, conintue 310 | return true 311 | } 312 | if idx.less != nil { 313 | idx.btr.ReplaceOrInsert(dbi) 314 | } 315 | if idx.rect != nil { 316 | idx.rtr.Insert(dbi) 317 | } 318 | return true 319 | }) 320 | } 321 | 322 | // CreateIndex builds a new index and populates it with items. 323 | // The items are ordered in an b-tree and can be retrieved using the 324 | // Ascend* and Descend* methods. 325 | // An error will occur if an index with the same name already exists. 326 | // 327 | // When a pattern is provided, the index will be populated with 328 | // keys that match the specified pattern. This is a very simple pattern 329 | // match where '*' matches on any number characters and '?' matches on 330 | // any one character. 331 | // The less function compares if string 'a' is less than string 'b'. 332 | // It allows for indexes to create custom ordering. It's possible 333 | // that the strings may be textual or binary. It's up to the provided 334 | // less function to handle the content format and comparison. 335 | // There are some default less function that can be used such as 336 | // IndexString, IndexBinary, etc. 337 | // 338 | // Deprecated: Use Transactions 339 | func (db *DB) CreateIndex(name, pattern string, 340 | less ...func(a, b string) bool) error { 341 | return db.Update(func(tx *Tx) error { 342 | return tx.CreateIndex(name, pattern, less...) 343 | }) 344 | } 345 | 346 | // ReplaceIndex builds a new index and populates it with items. 347 | // The items are ordered in an b-tree and can be retrieved using the 348 | // Ascend* and Descend* methods. 349 | // If a previous index with the same name exists, that index will be deleted. 350 | // 351 | // Deprecated: Use Transactions 352 | func (db *DB) ReplaceIndex(name, pattern string, 353 | less ...func(a, b string) bool) error { 354 | return db.Update(func(tx *Tx) error { 355 | err := tx.CreateIndex(name, pattern, less...) 356 | if err != nil { 357 | if err == ErrIndexExists { 358 | err := tx.DropIndex(name) 359 | if err != nil { 360 | return err 361 | } 362 | return tx.CreateIndex(name, pattern, less...) 363 | } 364 | return err 365 | } 366 | return nil 367 | }) 368 | } 369 | 370 | // CreateSpatialIndex builds a new index and populates it with items. 371 | // The items are organized in an r-tree and can be retrieved using the 372 | // Intersects method. 373 | // An error will occur if an index with the same name already exists. 374 | // 375 | // The rect function converts a string to a rectangle. The rectangle is 376 | // represented by two arrays, min and max. Both arrays may have a length 377 | // between 1 and 20, and both arrays must match in length. A length of 1 is a 378 | // one dimensional rectangle, and a length of 4 is a four dimension rectangle. 379 | // There is support for up to 20 dimensions. 380 | // The values of min must be less than the values of max at the same dimension. 381 | // Thus min[0] must be less-than-or-equal-to max[0]. 382 | // The IndexRect is a default function that can be used for the rect 383 | // parameter. 384 | // 385 | // Deprecated: Use Transactions 386 | func (db *DB) CreateSpatialIndex(name, pattern string, 387 | rect func(item string) (min, max []float64)) error { 388 | return db.Update(func(tx *Tx) error { 389 | return tx.CreateSpatialIndex(name, pattern, rect) 390 | }) 391 | } 392 | 393 | // ReplaceSpatialIndex builds a new index and populates it with items. 394 | // The items are organized in an r-tree and can be retrieved using the 395 | // Intersects method. 396 | // If a previous index with the same name exists, that index will be deleted. 397 | // 398 | // Deprecated: Use Transactions 399 | func (db *DB) ReplaceSpatialIndex(name, pattern string, 400 | rect func(item string) (min, max []float64)) error { 401 | return db.Update(func(tx *Tx) error { 402 | err := tx.CreateSpatialIndex(name, pattern, rect) 403 | if err != nil { 404 | if err == ErrIndexExists { 405 | err := tx.DropIndex(name) 406 | if err != nil { 407 | return err 408 | } 409 | return tx.CreateSpatialIndex(name, pattern, rect) 410 | } 411 | return err 412 | } 413 | return nil 414 | }) 415 | } 416 | 417 | // DropIndex removes an index. 418 | // 419 | // Deprecated: Use Transactions 420 | func (db *DB) DropIndex(name string) error { 421 | return db.Update(func(tx *Tx) error { 422 | return tx.DropIndex(name) 423 | }) 424 | } 425 | 426 | // Indexes returns a list of index names. 427 | // 428 | // Deprecated: Use Transactions 429 | func (db *DB) Indexes() ([]string, error) { 430 | var names []string 431 | var err = db.View(func(tx *Tx) error { 432 | var err error 433 | names, err = tx.Indexes() 434 | return err 435 | }) 436 | return names, err 437 | } 438 | 439 | // ReadConfig returns the database configuration. 440 | func (db *DB) ReadConfig(config *Config) error { 441 | db.mu.RLock() 442 | defer db.mu.RUnlock() 443 | if db.closed { 444 | return ErrDatabaseClosed 445 | } 446 | *config = db.config 447 | return nil 448 | } 449 | 450 | // SetConfig updates the database configuration. 451 | func (db *DB) SetConfig(config Config) error { 452 | db.mu.Lock() 453 | defer db.mu.Unlock() 454 | if db.closed { 455 | return ErrDatabaseClosed 456 | } 457 | switch config.SyncPolicy { 458 | default: 459 | return ErrInvalidSyncPolicy 460 | case Never, EverySecond, Always: 461 | } 462 | db.config = config 463 | return nil 464 | } 465 | 466 | // insertIntoDatabase performs inserts an item in to the database and updates 467 | // all indexes. If a previous item with the same key already exists, that item 468 | // will be replaced with the new one, and return the previous item. 469 | func (db *DB) insertIntoDatabase(item *dbItem) *dbItem { 470 | var pdbi *dbItem 471 | prev := db.keys.ReplaceOrInsert(item) 472 | if prev != nil { 473 | // A previous item was removed from the keys tree. Let's 474 | // fully delete this item from all indexes. 475 | pdbi = prev.(*dbItem) 476 | if pdbi.opts != nil && pdbi.opts.ex { 477 | // Remove it from the exipres tree. 478 | db.exps.Delete(pdbi) 479 | } 480 | for _, idx := range db.idxs { 481 | if idx.btr != nil { 482 | // Remove it from the btree index. 483 | idx.btr.Delete(pdbi) 484 | } 485 | if idx.rtr != nil { 486 | // Remove it from the rtree index. 487 | idx.rtr.Remove(pdbi) 488 | } 489 | } 490 | } 491 | if item.opts != nil && item.opts.ex { 492 | // The new item has eviction options. Add it to the 493 | // expires tree 494 | db.exps.ReplaceOrInsert(item) 495 | } 496 | for _, idx := range db.idxs { 497 | if !idx.match(item.key) { 498 | continue 499 | } 500 | if idx.btr != nil { 501 | // Add new item to btree index. 502 | idx.btr.ReplaceOrInsert(item) 503 | } 504 | if idx.rtr != nil { 505 | // Add new item to rtree index. 506 | idx.rtr.Insert(item) 507 | } 508 | } 509 | // we must return the previous item to the caller. 510 | return pdbi 511 | } 512 | 513 | // deleteFromDatabase removes and item from the database and indexes. The input 514 | // item must only have the key field specified thus "&dbItem{key: key}" is all 515 | // that is needed to fully remove the item with the matching key. If an item 516 | // with the matching key was found in the database, it will be removed and 517 | // returned to the caller. A nil return value means that the item was not 518 | // found in the database 519 | func (db *DB) deleteFromDatabase(item *dbItem) *dbItem { 520 | var pdbi *dbItem 521 | prev := db.keys.Delete(item) 522 | if prev != nil { 523 | pdbi = prev.(*dbItem) 524 | if pdbi.opts != nil && pdbi.opts.ex { 525 | // Remove it from the exipres tree. 526 | db.exps.Delete(pdbi) 527 | } 528 | for _, idx := range db.idxs { 529 | if idx.btr != nil { 530 | // Remove it from the btree index. 531 | idx.btr.Delete(pdbi) 532 | } 533 | if idx.rtr != nil { 534 | // Remove it from the rtree index. 535 | idx.rtr.Remove(pdbi) 536 | } 537 | } 538 | } 539 | return pdbi 540 | } 541 | 542 | // backgroundManager runs continuously in the background and performs various 543 | // operations such as removing expired items and syncing to disk. 544 | func (db *DB) backgroundManager() { 545 | flushes := 0 546 | t := time.NewTicker(time.Second) 547 | defer t.Stop() 548 | for range t.C { 549 | var shrink bool 550 | // Open a standard view. This will take a full lock of the 551 | // database thus allowing for access to anything we need. 552 | var onExpired func([]string) 553 | var expired []*dbItem 554 | var onExpiredSync func(key, value string, tx *Tx) error 555 | err := db.Update(func(tx *Tx) error { 556 | onExpired = db.config.OnExpired 557 | if onExpired == nil { 558 | onExpiredSync = db.config.OnExpiredSync 559 | } 560 | if db.persist && !db.config.AutoShrinkDisabled { 561 | pos, err := db.file.Seek(0, 1) 562 | if err != nil { 563 | return err 564 | } 565 | aofsz := int(pos) 566 | if aofsz > db.config.AutoShrinkMinSize { 567 | prc := float64(db.config.AutoShrinkPercentage) / 100.0 568 | shrink = aofsz > db.lastaofsz+int(float64(db.lastaofsz)*prc) 569 | } 570 | } 571 | // produce a list of expired items that need removing 572 | db.exps.AscendLessThan(&dbItem{ 573 | opts: &dbItemOpts{ex: true, exat: time.Now()}, 574 | }, func(item btree.Item) bool { 575 | expired = append(expired, item.(*dbItem)) 576 | return true 577 | }) 578 | if onExpired == nil && onExpiredSync == nil { 579 | for _, itm := range expired { 580 | if _, err := tx.Delete(itm.key); err != nil { 581 | // it's ok to get a "not found" because the 582 | // 'Delete' method reports "not found" for 583 | // expired items. 584 | if err != ErrNotFound { 585 | return err 586 | } 587 | } 588 | } 589 | } else if onExpiredSync != nil { 590 | for _, itm := range expired { 591 | if err := onExpiredSync(itm.key, itm.val, tx); err != nil { 592 | return err 593 | } 594 | } 595 | } 596 | return nil 597 | }) 598 | if err == ErrDatabaseClosed { 599 | break 600 | } 601 | 602 | // send expired event, if needed 603 | if onExpired != nil && len(expired) > 0 { 604 | keys := make([]string, 0, 32) 605 | for _, itm := range expired { 606 | keys = append(keys, itm.key) 607 | } 608 | onExpired(keys) 609 | } 610 | 611 | // execute a disk sync, if needed 612 | func() { 613 | db.mu.Lock() 614 | defer db.mu.Unlock() 615 | if db.persist && db.config.SyncPolicy == EverySecond && 616 | flushes != db.flushes { 617 | _ = db.file.Sync() 618 | flushes = db.flushes 619 | } 620 | }() 621 | if shrink { 622 | if err = db.Shrink(); err != nil { 623 | if err == ErrDatabaseClosed { 624 | break 625 | } 626 | } 627 | } 628 | } 629 | } 630 | 631 | // Shrink will make the database file smaller by removing redundant 632 | // log entries. This operation does not block the database. 633 | func (db *DB) Shrink() error { 634 | db.mu.Lock() 635 | if db.closed { 636 | db.mu.Unlock() 637 | return ErrDatabaseClosed 638 | } 639 | if !db.persist { 640 | // The database was opened with ":memory:" as the path. 641 | // There is no persistence, and no need to do anything here. 642 | db.mu.Unlock() 643 | return nil 644 | } 645 | if db.shrinking { 646 | // The database is already in the process of shrinking. 647 | db.mu.Unlock() 648 | return ErrShrinkInProcess 649 | } 650 | db.shrinking = true 651 | defer func() { 652 | db.mu.Lock() 653 | db.shrinking = false 654 | db.mu.Unlock() 655 | }() 656 | fname := db.file.Name() 657 | tmpname := fname + ".tmp" 658 | // the endpos is used to return to the end of the file when we are 659 | // finished writing all of the current items. 660 | endpos, err := db.file.Seek(0, 2) 661 | if err != nil { 662 | return err 663 | } 664 | db.mu.Unlock() 665 | time.Sleep(time.Second / 4) // wait just a bit before starting 666 | f, err := os.Create(tmpname) 667 | if err != nil { 668 | return err 669 | } 670 | defer func() { 671 | _ = f.Close() 672 | _ = os.RemoveAll(tmpname) 673 | }() 674 | 675 | // we are going to read items in as chunks as to not hold up the database 676 | // for too long. 677 | var buf []byte 678 | pivot := "" 679 | done := false 680 | for !done { 681 | err := func() error { 682 | db.mu.RLock() 683 | defer db.mu.RUnlock() 684 | if db.closed { 685 | return ErrDatabaseClosed 686 | } 687 | done = true 688 | var n int 689 | db.keys.AscendGreaterOrEqual(&dbItem{key: pivot}, 690 | func(item btree.Item) bool { 691 | dbi := item.(*dbItem) 692 | // 1000 items or 64MB buffer 693 | if n > 1000 || len(buf) > 64*1024*1024 { 694 | pivot = dbi.key 695 | done = false 696 | return false 697 | } 698 | buf = dbi.writeSetTo(buf) 699 | n++ 700 | return true 701 | }, 702 | ) 703 | if len(buf) > 0 { 704 | if _, err := f.Write(buf); err != nil { 705 | return err 706 | } 707 | buf = buf[:0] 708 | } 709 | return nil 710 | }() 711 | if err != nil { 712 | return err 713 | } 714 | } 715 | // We reached this far so all of the items have been written to a new tmp 716 | // There's some more work to do by appending the new line from the aof 717 | // to the tmp file and finally swap the files out. 718 | return func() error { 719 | // We're wrapping this in a function to get the benefit of a defered 720 | // lock/unlock. 721 | db.mu.Lock() 722 | defer db.mu.Unlock() 723 | if db.closed { 724 | return ErrDatabaseClosed 725 | } 726 | // We are going to open a new version of the aof file so that we do 727 | // not change the seek position of the previous. This may cause a 728 | // problem in the future if we choose to use syscall file locking. 729 | aof, err := os.Open(fname) 730 | if err != nil { 731 | return err 732 | } 733 | defer func() { _ = aof.Close() }() 734 | if _, err := aof.Seek(endpos, 0); err != nil { 735 | return err 736 | } 737 | // Just copy all of the new commands that have occurred since we 738 | // started the shrink process. 739 | if _, err := io.Copy(f, aof); err != nil { 740 | return err 741 | } 742 | // Close all files 743 | if err := aof.Close(); err != nil { 744 | return err 745 | } 746 | if err := f.Close(); err != nil { 747 | return err 748 | } 749 | if err := db.file.Close(); err != nil { 750 | return err 751 | } 752 | // Any failures below here is really bad. So just panic. 753 | if err := os.Rename(tmpname, fname); err != nil { 754 | panic(err) 755 | } 756 | db.file, err = os.OpenFile(fname, os.O_CREATE|os.O_RDWR, 0666) 757 | if err != nil { 758 | panic(err) 759 | } 760 | pos, err := db.file.Seek(0, 2) 761 | if err != nil { 762 | return err 763 | } 764 | db.lastaofsz = int(pos) 765 | return nil 766 | }() 767 | } 768 | 769 | var errValidEOF = errors.New("valid eof") 770 | 771 | // readLoad reads from the reader and loads commands into the database. 772 | // modTime is the modified time of the reader, should be no greater than 773 | // the current time.Now(). 774 | func (db *DB) readLoad(rd io.Reader, modTime time.Time) error { 775 | data := make([]byte, 4096) 776 | parts := make([]string, 0, 8) 777 | r := bufio.NewReader(rd) 778 | for { 779 | // read a single command. 780 | // first we should read the number of parts that the of the command 781 | line, err := r.ReadBytes('\n') 782 | if err != nil { 783 | if len(line) > 0 { 784 | // got an eof but also data. this should be an unexpected eof. 785 | return io.ErrUnexpectedEOF 786 | } 787 | if err == io.EOF { 788 | break 789 | } 790 | return err 791 | } 792 | if line[0] != '*' { 793 | return ErrInvalid 794 | } 795 | // convert the string number to and int 796 | var n int 797 | if len(line) == 4 && line[len(line)-2] == '\r' { 798 | if line[1] < '0' || line[1] > '9' { 799 | return ErrInvalid 800 | } 801 | n = int(line[1] - '0') 802 | } else { 803 | if len(line) < 5 || line[len(line)-2] != '\r' { 804 | return ErrInvalid 805 | } 806 | for i := 1; i < len(line)-2; i++ { 807 | if line[i] < '0' || line[i] > '9' { 808 | return ErrInvalid 809 | } 810 | n = n*10 + int(line[i]-'0') 811 | } 812 | } 813 | // read each part of the command. 814 | parts = parts[:0] 815 | for i := 0; i < n; i++ { 816 | // read the number of bytes of the part. 817 | line, err := r.ReadBytes('\n') 818 | if err != nil { 819 | return err 820 | } 821 | if line[0] != '$' { 822 | return ErrInvalid 823 | } 824 | // convert the string number to and int 825 | var n int 826 | if len(line) == 4 && line[len(line)-2] == '\r' { 827 | if line[1] < '0' || line[1] > '9' { 828 | return ErrInvalid 829 | } 830 | n = int(line[1] - '0') 831 | } else { 832 | if len(line) < 5 || line[len(line)-2] != '\r' { 833 | return ErrInvalid 834 | } 835 | for i := 1; i < len(line)-2; i++ { 836 | if line[i] < '0' || line[i] > '9' { 837 | return ErrInvalid 838 | } 839 | n = n*10 + int(line[i]-'0') 840 | } 841 | } 842 | // resize the read buffer 843 | if len(data) < n+2 { 844 | dataln := len(data) 845 | for dataln < n+2 { 846 | dataln *= 2 847 | } 848 | data = make([]byte, dataln) 849 | } 850 | if _, err = io.ReadFull(r, data[:n+2]); err != nil { 851 | return err 852 | } 853 | if data[n] != '\r' || data[n+1] != '\n' { 854 | return ErrInvalid 855 | } 856 | // copy string 857 | parts = append(parts, string(data[:n])) 858 | } 859 | // finished reading the command 860 | 861 | if len(parts) == 0 { 862 | continue 863 | } 864 | if (parts[0][0] == 's' || parts[0][1] == 'S') && 865 | (parts[0][1] == 'e' || parts[0][1] == 'E') && 866 | (parts[0][2] == 't' || parts[0][2] == 'T') { 867 | // SET 868 | if len(parts) < 3 || len(parts) == 4 || len(parts) > 5 { 869 | return ErrInvalid 870 | } 871 | if len(parts) == 5 { 872 | if strings.ToLower(parts[3]) != "ex" { 873 | return ErrInvalid 874 | } 875 | ex, err := strconv.ParseInt(parts[4], 10, 64) 876 | if err != nil { 877 | return err 878 | } 879 | now := time.Now() 880 | dur := (time.Duration(ex) * time.Second) - now.Sub(modTime) 881 | if dur > 0 { 882 | db.insertIntoDatabase(&dbItem{ 883 | key: parts[1], 884 | val: parts[2], 885 | opts: &dbItemOpts{ 886 | ex: true, 887 | exat: now.Add(dur), 888 | }, 889 | }) 890 | } 891 | } else { 892 | db.insertIntoDatabase(&dbItem{key: parts[1], val: parts[2]}) 893 | } 894 | } else if (parts[0][0] == 'd' || parts[0][1] == 'D') && 895 | (parts[0][1] == 'e' || parts[0][1] == 'E') && 896 | (parts[0][2] == 'l' || parts[0][2] == 'L') { 897 | // DEL 898 | if len(parts) != 2 { 899 | return ErrInvalid 900 | } 901 | db.deleteFromDatabase(&dbItem{key: parts[1]}) 902 | } else if (parts[0][0] == 'f' || parts[0][1] == 'F') && 903 | strings.ToLower(parts[0]) == "flushdb" { 904 | db.keys = btree.New(btreeDegrees, nil) 905 | db.exps = btree.New(btreeDegrees, &exctx{db}) 906 | db.idxs = make(map[string]*index) 907 | } else { 908 | return ErrInvalid 909 | } 910 | } 911 | return nil 912 | } 913 | 914 | // load reads entries from the append only database file and fills the database. 915 | // The file format uses the Redis append only file format, which is and a series 916 | // of RESP commands. For more information on RESP please read 917 | // http://redis.io/topics/protocol. The only supported RESP commands are DEL and 918 | // SET. 919 | func (db *DB) load() error { 920 | fi, err := db.file.Stat() 921 | if err != nil { 922 | return err 923 | } 924 | if err := db.readLoad(db.file, fi.ModTime()); err != nil { 925 | return err 926 | } 927 | pos, err := db.file.Seek(0, 2) 928 | if err != nil { 929 | return err 930 | } 931 | db.lastaofsz = int(pos) 932 | return nil 933 | } 934 | 935 | // managed calls a block of code that is fully contained in a transaction. 936 | // This method is intended to be wrapped by Update and View 937 | func (db *DB) managed(writable bool, fn func(tx *Tx) error) (err error) { 938 | var tx *Tx 939 | tx, err = db.Begin(writable) 940 | if err != nil { 941 | return 942 | } 943 | defer func() { 944 | if err != nil { 945 | // The caller returned an error. We must rollback. 946 | _ = tx.Rollback() 947 | return 948 | } 949 | if writable { 950 | // Everything went well. Lets Commit() 951 | err = tx.Commit() 952 | } else { 953 | // read-only transaction can only roll back. 954 | err = tx.Rollback() 955 | } 956 | }() 957 | tx.funcd = true 958 | defer func() { 959 | tx.funcd = false 960 | }() 961 | err = fn(tx) 962 | return 963 | } 964 | 965 | // View executes a function within a managed read-only transaction. 966 | // When a non-nil error is returned from the function that error will be return 967 | // to the caller of View(). 968 | // 969 | // Executing a manual commit or rollback from inside the function will result 970 | // in a panic. 971 | func (db *DB) View(fn func(tx *Tx) error) error { 972 | return db.managed(false, fn) 973 | } 974 | 975 | // Update executes a function within a managed read/write transaction. 976 | // The transaction has been committed when no error is returned. 977 | // In the event that an error is returned, the transaction will be rolled back. 978 | // When a non-nil error is returned from the function, the transaction will be 979 | // rolled back and the that error will be return to the caller of Update(). 980 | // 981 | // Executing a manual commit or rollback from inside the function will result 982 | // in a panic. 983 | func (db *DB) Update(fn func(tx *Tx) error) error { 984 | return db.managed(true, fn) 985 | } 986 | 987 | // get return an item or nil if not found. 988 | func (db *DB) get(key string) *dbItem { 989 | item := db.keys.Get(&dbItem{key: key}) 990 | if item != nil { 991 | return item.(*dbItem) 992 | } 993 | return nil 994 | } 995 | 996 | // Tx represents a transaction on the database. This transaction can either be 997 | // read-only or read/write. Read-only transactions can be used for retrieving 998 | // values for keys and iterating through keys and values. Read/write 999 | // transactions can set and delete keys. 1000 | // 1001 | // All transactions must be committed or rolled-back when done. 1002 | type Tx struct { 1003 | db *DB // the underlying database. 1004 | writable bool // when false mutable operations fail. 1005 | funcd bool // when true Commit and Rollback panic. 1006 | wc *txWriteContext // context for writable transactions. 1007 | } 1008 | 1009 | type txWriteContext struct { 1010 | // rollback when deleteAll is called 1011 | rbkeys *btree.BTree // a tree of all item ordered by key 1012 | rbexps *btree.BTree // a tree of items ordered by expiration 1013 | rbidxs map[string]*index // the index trees. 1014 | 1015 | rollbackItems map[string]*dbItem // details for rolling back tx. 1016 | commitItems map[string]*dbItem // details for committing tx. 1017 | itercount int // stack of iterators 1018 | rollbackIndexes map[string]*index // details for dropped indexes. 1019 | } 1020 | 1021 | // DeleteAll deletes all items from the database. 1022 | func (tx *Tx) DeleteAll() error { 1023 | if tx.db == nil { 1024 | return ErrTxClosed 1025 | } else if !tx.writable { 1026 | return ErrTxNotWritable 1027 | } else if tx.wc.itercount > 0 { 1028 | return ErrTxIterating 1029 | } 1030 | 1031 | // check to see if we've already deleted everything 1032 | if tx.wc.rbkeys == nil { 1033 | // we need to backup the live data in case of a rollback. 1034 | tx.wc.rbkeys = tx.db.keys 1035 | tx.wc.rbexps = tx.db.exps 1036 | tx.wc.rbidxs = tx.db.idxs 1037 | } 1038 | 1039 | // now reset the live database trees 1040 | tx.db.keys = btree.New(btreeDegrees, nil) 1041 | tx.db.exps = btree.New(btreeDegrees, &exctx{tx.db}) 1042 | tx.db.idxs = make(map[string]*index) 1043 | 1044 | // finally re-create the indexes 1045 | for name, idx := range tx.wc.rbidxs { 1046 | tx.db.idxs[name] = idx.clearCopy() 1047 | } 1048 | 1049 | // always clear out the commits 1050 | tx.wc.commitItems = make(map[string]*dbItem) 1051 | 1052 | return nil 1053 | } 1054 | 1055 | // Begin opens a new transaction. 1056 | // Multiple read-only transactions can be opened at the same time but there can 1057 | // only be one read/write transaction at a time. Attempting to open a read/write 1058 | // transactions while another one is in progress will result in blocking until 1059 | // the current read/write transaction is completed. 1060 | // 1061 | // All transactions must be closed by calling Commit() or Rollback() when done. 1062 | func (db *DB) Begin(writable bool) (*Tx, error) { 1063 | tx := &Tx{ 1064 | db: db, 1065 | writable: writable, 1066 | } 1067 | tx.lock() 1068 | if db.closed { 1069 | tx.unlock() 1070 | return nil, ErrDatabaseClosed 1071 | } 1072 | if writable { 1073 | // writable transactions have a writeContext object that 1074 | // contains information about changes to the database. 1075 | tx.wc = &txWriteContext{} 1076 | tx.wc.rollbackItems = make(map[string]*dbItem) 1077 | tx.wc.rollbackIndexes = make(map[string]*index) 1078 | if db.persist { 1079 | tx.wc.commitItems = make(map[string]*dbItem) 1080 | } 1081 | } 1082 | return tx, nil 1083 | } 1084 | 1085 | // lock locks the database based on the transaction type. 1086 | func (tx *Tx) lock() { 1087 | if tx.writable { 1088 | tx.db.mu.Lock() 1089 | } else { 1090 | tx.db.mu.RLock() 1091 | } 1092 | } 1093 | 1094 | // unlock unlocks the database based on the transaction type. 1095 | func (tx *Tx) unlock() { 1096 | if tx.writable { 1097 | tx.db.mu.Unlock() 1098 | } else { 1099 | tx.db.mu.RUnlock() 1100 | } 1101 | } 1102 | 1103 | // rollbackInner handles the underlying rollback logic. 1104 | // Intended to be called from Commit() and Rollback(). 1105 | func (tx *Tx) rollbackInner() { 1106 | // rollback the deleteAll if needed 1107 | if tx.wc.rbkeys != nil { 1108 | tx.db.keys = tx.wc.rbkeys 1109 | tx.db.idxs = tx.wc.rbidxs 1110 | tx.db.exps = tx.wc.rbexps 1111 | } 1112 | for key, item := range tx.wc.rollbackItems { 1113 | tx.db.deleteFromDatabase(&dbItem{key: key}) 1114 | if item != nil { 1115 | // When an item is not nil, we will need to reinsert that item 1116 | // into the database overwriting the current one. 1117 | tx.db.insertIntoDatabase(item) 1118 | } 1119 | } 1120 | for name, idx := range tx.wc.rollbackIndexes { 1121 | delete(tx.db.idxs, name) 1122 | if idx != nil { 1123 | // When an index is not nil, we will need to rebuilt that index 1124 | // this could be an expensive process if the database has many 1125 | // items or the index is complex. 1126 | tx.db.idxs[name] = idx 1127 | idx.rebuild() 1128 | } 1129 | } 1130 | } 1131 | 1132 | // Commit writes all changes to disk. 1133 | // An error is returned when a write error occurs, or when a Commit() is called 1134 | // from a read-only transaction. 1135 | func (tx *Tx) Commit() error { 1136 | if tx.funcd { 1137 | panic("managed tx commit not allowed") 1138 | } 1139 | if tx.db == nil { 1140 | return ErrTxClosed 1141 | } else if !tx.writable { 1142 | return ErrTxNotWritable 1143 | } 1144 | var err error 1145 | if tx.db.persist && (len(tx.wc.commitItems) > 0 || tx.wc.rbkeys != nil) { 1146 | tx.db.buf = tx.db.buf[:0] 1147 | // write a flushdb if a deleteAll was called. 1148 | if tx.wc.rbkeys != nil { 1149 | tx.db.buf = append(tx.db.buf, "*1\r\n$7\r\nflushdb\r\n"...) 1150 | } 1151 | // Each committed record is written to disk 1152 | for key, item := range tx.wc.commitItems { 1153 | if item == nil { 1154 | tx.db.buf = (&dbItem{key: key}).writeDeleteTo(tx.db.buf) 1155 | } else { 1156 | tx.db.buf = item.writeSetTo(tx.db.buf) 1157 | } 1158 | } 1159 | // Flushing the buffer only once per transaction. 1160 | // If this operation fails then the write did failed and we must 1161 | // rollback. 1162 | if _, err = tx.db.file.Write(tx.db.buf); err != nil { 1163 | tx.rollbackInner() 1164 | } 1165 | if tx.db.config.SyncPolicy == Always { 1166 | _ = tx.db.file.Sync() 1167 | } 1168 | // Increment the number of flushes. The background syncing uses this. 1169 | tx.db.flushes++ 1170 | } 1171 | // Unlock the database and allow for another writable transaction. 1172 | tx.unlock() 1173 | // Clear the db field to disable this transaction from future use. 1174 | tx.db = nil 1175 | return err 1176 | } 1177 | 1178 | // Rollback closes the transaction and reverts all mutable operations that 1179 | // were performed on the transaction such as Set() and Delete(). 1180 | // 1181 | // Read-only transactions can only be rolled back, not committed. 1182 | func (tx *Tx) Rollback() error { 1183 | if tx.funcd { 1184 | panic("managed tx rollback not allowed") 1185 | } 1186 | if tx.db == nil { 1187 | return ErrTxClosed 1188 | } 1189 | // The rollback func does the heavy lifting. 1190 | if tx.writable { 1191 | tx.rollbackInner() 1192 | } 1193 | // unlock the database for more transactions. 1194 | tx.unlock() 1195 | // Clear the db field to disable this transaction from future use. 1196 | tx.db = nil 1197 | return nil 1198 | } 1199 | 1200 | // dbItemOpts holds various meta information about an item. 1201 | type dbItemOpts struct { 1202 | ex bool // does this item expire? 1203 | exat time.Time // when does this item expire? 1204 | } 1205 | type dbItem struct { 1206 | key, val string // the binary key and value 1207 | opts *dbItemOpts // optional meta information 1208 | keyless bool // keyless item for scanning 1209 | } 1210 | 1211 | func appendArray(buf []byte, count int) []byte { 1212 | buf = append(buf, '*') 1213 | buf = append(buf, strconv.FormatInt(int64(count), 10)...) 1214 | buf = append(buf, '\r', '\n') 1215 | return buf 1216 | } 1217 | 1218 | func appendBulkString(buf []byte, s string) []byte { 1219 | buf = append(buf, '$') 1220 | buf = append(buf, strconv.FormatInt(int64(len(s)), 10)...) 1221 | buf = append(buf, '\r', '\n') 1222 | buf = append(buf, s...) 1223 | buf = append(buf, '\r', '\n') 1224 | return buf 1225 | } 1226 | 1227 | // writeSetTo writes an item as a single SET record to the a bufio Writer. 1228 | func (dbi *dbItem) writeSetTo(buf []byte) []byte { 1229 | if dbi.opts != nil && dbi.opts.ex { 1230 | ex := dbi.opts.exat.Sub(time.Now()) / time.Second 1231 | buf = appendArray(buf, 5) 1232 | buf = appendBulkString(buf, "set") 1233 | buf = appendBulkString(buf, dbi.key) 1234 | buf = appendBulkString(buf, dbi.val) 1235 | buf = appendBulkString(buf, "ex") 1236 | buf = appendBulkString(buf, strconv.FormatUint(uint64(ex), 10)) 1237 | } else { 1238 | buf = appendArray(buf, 3) 1239 | buf = appendBulkString(buf, "set") 1240 | buf = appendBulkString(buf, dbi.key) 1241 | buf = appendBulkString(buf, dbi.val) 1242 | } 1243 | return buf 1244 | } 1245 | 1246 | // writeSetTo writes an item as a single DEL record to the a bufio Writer. 1247 | func (dbi *dbItem) writeDeleteTo(buf []byte) []byte { 1248 | buf = appendArray(buf, 2) 1249 | buf = appendBulkString(buf, "del") 1250 | buf = appendBulkString(buf, dbi.key) 1251 | return buf 1252 | } 1253 | 1254 | // expired evaluates id the item has expired. This will always return false when 1255 | // the item does not have `opts.ex` set to true. 1256 | func (dbi *dbItem) expired() bool { 1257 | return dbi.opts != nil && dbi.opts.ex && time.Now().After(dbi.opts.exat) 1258 | } 1259 | 1260 | // MaxTime from http://stackoverflow.com/questions/25065055#32620397 1261 | // This is a long time in the future. It's an imaginary number that is 1262 | // used for b-tree ordering. 1263 | var maxTime = time.Unix(1<<63-62135596801, 999999999) 1264 | 1265 | // expiresAt will return the time when the item will expire. When an item does 1266 | // not expire `maxTime` is used. 1267 | func (dbi *dbItem) expiresAt() time.Time { 1268 | if dbi.opts == nil || !dbi.opts.ex { 1269 | return maxTime 1270 | } 1271 | return dbi.opts.exat 1272 | } 1273 | 1274 | // Less determines if a b-tree item is less than another. This is required 1275 | // for ordering, inserting, and deleting items from a b-tree. It's important 1276 | // to note that the ctx parameter is used to help with determine which 1277 | // formula to use on an item. Each b-tree should use a different ctx when 1278 | // sharing the same item. 1279 | func (dbi *dbItem) Less(item btree.Item, ctx interface{}) bool { 1280 | dbi2 := item.(*dbItem) 1281 | switch ctx := ctx.(type) { 1282 | case *exctx: 1283 | // The expires b-tree formula 1284 | if dbi2.expiresAt().After(dbi.expiresAt()) { 1285 | return true 1286 | } 1287 | if dbi.expiresAt().After(dbi2.expiresAt()) { 1288 | return false 1289 | } 1290 | case *index: 1291 | if ctx.less != nil { 1292 | // Using an index 1293 | if ctx.less(dbi.val, dbi2.val) { 1294 | return true 1295 | } 1296 | if ctx.less(dbi2.val, dbi.val) { 1297 | return false 1298 | } 1299 | } 1300 | } 1301 | // Always fall back to the key comparison. This creates absolute uniqueness. 1302 | if dbi.keyless { 1303 | return false 1304 | } else if dbi2.keyless { 1305 | return true 1306 | } 1307 | return dbi.key < dbi2.key 1308 | } 1309 | 1310 | // Rect converts a string to a rectangle. 1311 | // An invalid rectangle will cause a panic. 1312 | func (dbi *dbItem) Rect(ctx interface{}) (min, max []float64) { 1313 | switch ctx := ctx.(type) { 1314 | case *index: 1315 | return ctx.rect(dbi.val) 1316 | } 1317 | return nil, nil 1318 | } 1319 | 1320 | // SetOptions represents options that may be included with the Set() command. 1321 | type SetOptions struct { 1322 | // Expires indicates that the Set() key-value will expire 1323 | Expires bool 1324 | // TTL is how much time the key-value will exist in the database 1325 | // before being evicted. The Expires field must also be set to true. 1326 | // TTL stands for Time-To-Live. 1327 | TTL time.Duration 1328 | } 1329 | 1330 | // GetLess returns the less function for an index. This is handy for 1331 | // doing ad-hoc compares inside a transaction. 1332 | // Returns ErrNotFound if the index is not found or there is no less 1333 | // function bound to the index 1334 | func (tx *Tx) GetLess(index string) (func(a, b string) bool, error) { 1335 | if tx.db == nil { 1336 | return nil, ErrTxClosed 1337 | } 1338 | idx, ok := tx.db.idxs[index] 1339 | if !ok || idx.less == nil { 1340 | return nil, ErrNotFound 1341 | } 1342 | return idx.less, nil 1343 | } 1344 | 1345 | // GetRect returns the rect function for an index. This is handy for 1346 | // doing ad-hoc searches inside a transaction. 1347 | // Returns ErrNotFound if the index is not found or there is no rect 1348 | // function bound to the index 1349 | func (tx *Tx) GetRect(index string) (func(s string) (min, max []float64), 1350 | error) { 1351 | if tx.db == nil { 1352 | return nil, ErrTxClosed 1353 | } 1354 | idx, ok := tx.db.idxs[index] 1355 | if !ok || idx.rect == nil { 1356 | return nil, ErrNotFound 1357 | } 1358 | return idx.rect, nil 1359 | } 1360 | 1361 | // Set inserts or replaces an item in the database based on the key. 1362 | // The opt params may be used for additional functionality such as forcing 1363 | // the item to be evicted at a specified time. When the return value 1364 | // for err is nil the operation succeeded. When the return value of 1365 | // replaced is true, then the operaton replaced an existing item whose 1366 | // value will be returned through the previousValue variable. 1367 | // The results of this operation will not be available to other 1368 | // transactions until the current transaction has successfully committed. 1369 | // 1370 | // Only a writable transaction can be used with this operation. 1371 | // This operation is not allowed during iterations such as Ascend* & Descend*. 1372 | func (tx *Tx) Set(key, value string, opts *SetOptions) (previousValue string, 1373 | replaced bool, err error) { 1374 | if tx.db == nil { 1375 | return "", false, ErrTxClosed 1376 | } else if !tx.writable { 1377 | return "", false, ErrTxNotWritable 1378 | } else if tx.wc.itercount > 0 { 1379 | return "", false, ErrTxIterating 1380 | } 1381 | item := &dbItem{key: key, val: value} 1382 | if opts != nil { 1383 | if opts.Expires { 1384 | // The caller is requesting that this item expires. Convert the 1385 | // TTL to an absolute time and bind it to the item. 1386 | item.opts = &dbItemOpts{ex: true, exat: time.Now().Add(opts.TTL)} 1387 | } 1388 | } 1389 | // Insert the item into the keys tree. 1390 | prev := tx.db.insertIntoDatabase(item) 1391 | 1392 | // insert into the rollback map if there has not been a deleteAll. 1393 | if tx.wc.rbkeys == nil { 1394 | if prev == nil { 1395 | // An item with the same key did not previously exist. Let's 1396 | // create a rollback entry with a nil value. A nil value indicates 1397 | // that the entry should be deleted on rollback. When the value is 1398 | // *not* nil, that means the entry should be reverted. 1399 | tx.wc.rollbackItems[key] = nil 1400 | } else { 1401 | // A previous item already exists in the database. Let's create a 1402 | // rollback entry with the item as the value. We need to check the 1403 | // map to see if there isn't already an item that matches the 1404 | // same key. 1405 | if _, ok := tx.wc.rollbackItems[key]; !ok { 1406 | tx.wc.rollbackItems[key] = prev 1407 | } 1408 | if !prev.expired() { 1409 | previousValue, replaced = prev.val, true 1410 | } 1411 | } 1412 | } 1413 | // For commits we simply assign the item to the map. We use this map to 1414 | // write the entry to disk. 1415 | if tx.db.persist { 1416 | tx.wc.commitItems[key] = item 1417 | } 1418 | return previousValue, replaced, nil 1419 | } 1420 | 1421 | // Get returns a value for a key. If the item does not exist or if the item 1422 | // has expired then ErrNotFound is returned. If ignoreExpired is true, then 1423 | // the found value will be returned even if it is expired. 1424 | func (tx *Tx) Get(key string, ignoreExpired ...bool) (val string, err error) { 1425 | if tx.db == nil { 1426 | return "", ErrTxClosed 1427 | } 1428 | var ignore bool 1429 | if len(ignoreExpired) != 0 { 1430 | ignore = ignoreExpired[0] 1431 | } 1432 | item := tx.db.get(key) 1433 | if item == nil || (item.expired() && !ignore) { 1434 | // The item does not exists or has expired. Let's assume that 1435 | // the caller is only interested in items that have not expired. 1436 | return "", ErrNotFound 1437 | } 1438 | return item.val, nil 1439 | } 1440 | 1441 | // Delete removes an item from the database based on the item's key. If the item 1442 | // does not exist or if the item has expired then ErrNotFound is returned. 1443 | // 1444 | // Only a writable transaction can be used for this operation. 1445 | // This operation is not allowed during iterations such as Ascend* & Descend*. 1446 | func (tx *Tx) Delete(key string) (val string, err error) { 1447 | if tx.db == nil { 1448 | return "", ErrTxClosed 1449 | } else if !tx.writable { 1450 | return "", ErrTxNotWritable 1451 | } else if tx.wc.itercount > 0 { 1452 | return "", ErrTxIterating 1453 | } 1454 | item := tx.db.deleteFromDatabase(&dbItem{key: key}) 1455 | if item == nil { 1456 | return "", ErrNotFound 1457 | } 1458 | // create a rollback entry if there has not been a deleteAll call. 1459 | if tx.wc.rbkeys == nil { 1460 | if _, ok := tx.wc.rollbackItems[key]; !ok { 1461 | tx.wc.rollbackItems[key] = item 1462 | } 1463 | } 1464 | if tx.db.persist { 1465 | tx.wc.commitItems[key] = nil 1466 | } 1467 | // Even though the item has been deleted, we still want to check 1468 | // if it has expired. An expired item should not be returned. 1469 | if item.expired() { 1470 | // The item exists in the tree, but has expired. Let's assume that 1471 | // the caller is only interested in items that have not expired. 1472 | return "", ErrNotFound 1473 | } 1474 | return item.val, nil 1475 | } 1476 | 1477 | // TTL returns the remaining time-to-live for an item. 1478 | // A negative duration will be returned for items that do not have an 1479 | // expiration. 1480 | func (tx *Tx) TTL(key string) (time.Duration, error) { 1481 | if tx.db == nil { 1482 | return 0, ErrTxClosed 1483 | } 1484 | item := tx.db.get(key) 1485 | if item == nil { 1486 | return 0, ErrNotFound 1487 | } else if item.opts == nil || !item.opts.ex { 1488 | return -1, nil 1489 | } 1490 | dur := item.opts.exat.Sub(time.Now()) 1491 | if dur < 0 { 1492 | return 0, ErrNotFound 1493 | } 1494 | return dur, nil 1495 | } 1496 | 1497 | // scan iterates through a specified index and calls user-defined iterator 1498 | // function for each item encountered. 1499 | // The desc param indicates that the iterator should descend. 1500 | // The gt param indicates that there is a greaterThan limit. 1501 | // The lt param indicates that there is a lessThan limit. 1502 | // The index param tells the scanner to use the specified index tree. An 1503 | // empty string for the index means to scan the keys, not the values. 1504 | // The start and stop params are the greaterThan, lessThan limits. For 1505 | // descending order, these will be lessThan, greaterThan. 1506 | // An error will be returned if the tx is closed or the index is not found. 1507 | func (tx *Tx) scan(desc, gt, lt bool, index, start, stop string, 1508 | iterator func(key, value string) bool) error { 1509 | if tx.db == nil { 1510 | return ErrTxClosed 1511 | } 1512 | // wrap a btree specific iterator around the user-defined iterator. 1513 | iter := func(item btree.Item) bool { 1514 | dbi := item.(*dbItem) 1515 | return iterator(dbi.key, dbi.val) 1516 | } 1517 | var tr *btree.BTree 1518 | if index == "" { 1519 | // empty index means we will use the keys tree. 1520 | tr = tx.db.keys 1521 | } else { 1522 | idx := tx.db.idxs[index] 1523 | if idx == nil { 1524 | // index was not found. return error 1525 | return ErrNotFound 1526 | } 1527 | tr = idx.btr 1528 | if tr == nil { 1529 | return nil 1530 | } 1531 | } 1532 | // create some limit items 1533 | var itemA, itemB *dbItem 1534 | if gt || lt { 1535 | if index == "" { 1536 | itemA = &dbItem{key: start} 1537 | itemB = &dbItem{key: stop} 1538 | } else { 1539 | itemA = &dbItem{val: start} 1540 | itemB = &dbItem{val: stop} 1541 | if desc { 1542 | itemA.keyless = true 1543 | itemB.keyless = true 1544 | } 1545 | } 1546 | } 1547 | // execute the scan on the underlying tree. 1548 | if tx.wc != nil { 1549 | tx.wc.itercount++ 1550 | defer func() { 1551 | tx.wc.itercount-- 1552 | }() 1553 | } 1554 | if desc { 1555 | if gt { 1556 | if lt { 1557 | tr.DescendRange(itemA, itemB, iter) 1558 | } else { 1559 | tr.DescendGreaterThan(itemA, iter) 1560 | } 1561 | } else if lt { 1562 | tr.DescendLessOrEqual(itemA, iter) 1563 | } else { 1564 | tr.Descend(iter) 1565 | } 1566 | } else { 1567 | if gt { 1568 | if lt { 1569 | tr.AscendRange(itemA, itemB, iter) 1570 | } else { 1571 | tr.AscendGreaterOrEqual(itemA, iter) 1572 | } 1573 | } else if lt { 1574 | tr.AscendLessThan(itemA, iter) 1575 | } else { 1576 | tr.Ascend(iter) 1577 | } 1578 | } 1579 | return nil 1580 | } 1581 | 1582 | // Match returns true if the specified key matches the pattern. This is a very 1583 | // simple pattern matcher where '*' matches on any number characters and '?' 1584 | // matches on any one character. 1585 | func Match(key, pattern string) bool { 1586 | return match.Match(key, pattern) 1587 | } 1588 | 1589 | // AscendKeys allows for iterating through keys based on the specified pattern. 1590 | func (tx *Tx) AscendKeys(pattern string, 1591 | iterator func(key, value string) bool) error { 1592 | if pattern == "" { 1593 | return nil 1594 | } 1595 | if pattern[0] == '*' { 1596 | if pattern == "*" { 1597 | return tx.Ascend("", iterator) 1598 | } 1599 | return tx.Ascend("", func(key, value string) bool { 1600 | if match.Match(key, pattern) { 1601 | if !iterator(key, value) { 1602 | return false 1603 | } 1604 | } 1605 | return true 1606 | }) 1607 | } 1608 | min, max := match.Allowable(pattern) 1609 | return tx.AscendGreaterOrEqual("", min, func(key, value string) bool { 1610 | if key > max { 1611 | return false 1612 | } 1613 | if match.Match(key, pattern) { 1614 | if !iterator(key, value) { 1615 | return false 1616 | } 1617 | } 1618 | return true 1619 | }) 1620 | } 1621 | 1622 | // DescendKeys allows for iterating through keys based on the specified pattern. 1623 | func (tx *Tx) DescendKeys(pattern string, 1624 | iterator func(key, value string) bool) error { 1625 | if pattern == "" { 1626 | return nil 1627 | } 1628 | if pattern[0] == '*' { 1629 | if pattern == "*" { 1630 | return tx.Descend("", iterator) 1631 | } 1632 | return tx.Descend("", func(key, value string) bool { 1633 | if match.Match(key, pattern) { 1634 | if !iterator(key, value) { 1635 | return false 1636 | } 1637 | } 1638 | return true 1639 | }) 1640 | } 1641 | min, max := match.Allowable(pattern) 1642 | return tx.DescendLessOrEqual("", max, func(key, value string) bool { 1643 | if key < min { 1644 | return false 1645 | } 1646 | if match.Match(key, pattern) { 1647 | if !iterator(key, value) { 1648 | return false 1649 | } 1650 | } 1651 | return true 1652 | }) 1653 | } 1654 | 1655 | // Ascend calls the iterator for every item in the database within the range 1656 | // [first, last], until iterator returns false. 1657 | // When an index is provided, the results will be ordered by the item values 1658 | // as specified by the less() function of the defined index. 1659 | // When an index is not provided, the results will be ordered by the item key. 1660 | // An invalid index will return an error. 1661 | func (tx *Tx) Ascend(index string, 1662 | iterator func(key, value string) bool) error { 1663 | return tx.scan(false, false, false, index, "", "", iterator) 1664 | } 1665 | 1666 | // AscendGreaterOrEqual calls the iterator for every item in the database within 1667 | // the range [pivot, last], until iterator returns false. 1668 | // When an index is provided, the results will be ordered by the item values 1669 | // as specified by the less() function of the defined index. 1670 | // When an index is not provided, the results will be ordered by the item key. 1671 | // An invalid index will return an error. 1672 | func (tx *Tx) AscendGreaterOrEqual(index, pivot string, 1673 | iterator func(key, value string) bool) error { 1674 | return tx.scan(false, true, false, index, pivot, "", iterator) 1675 | } 1676 | 1677 | // AscendLessThan calls the iterator for every item in the database within the 1678 | // range [first, pivot), until iterator returns false. 1679 | // When an index is provided, the results will be ordered by the item values 1680 | // as specified by the less() function of the defined index. 1681 | // When an index is not provided, the results will be ordered by the item key. 1682 | // An invalid index will return an error. 1683 | func (tx *Tx) AscendLessThan(index, pivot string, 1684 | iterator func(key, value string) bool) error { 1685 | return tx.scan(false, false, true, index, pivot, "", iterator) 1686 | } 1687 | 1688 | // AscendRange calls the iterator for every item in the database within 1689 | // the range [greaterOrEqual, lessThan), until iterator returns false. 1690 | // When an index is provided, the results will be ordered by the item values 1691 | // as specified by the less() function of the defined index. 1692 | // When an index is not provided, the results will be ordered by the item key. 1693 | // An invalid index will return an error. 1694 | func (tx *Tx) AscendRange(index, greaterOrEqual, lessThan string, 1695 | iterator func(key, value string) bool) error { 1696 | return tx.scan( 1697 | false, true, true, index, greaterOrEqual, lessThan, iterator, 1698 | ) 1699 | } 1700 | 1701 | // Descend calls the iterator for every item in the database within the range 1702 | // [last, first], until iterator returns false. 1703 | // When an index is provided, the results will be ordered by the item values 1704 | // as specified by the less() function of the defined index. 1705 | // When an index is not provided, the results will be ordered by the item key. 1706 | // An invalid index will return an error. 1707 | func (tx *Tx) Descend(index string, 1708 | iterator func(key, value string) bool) error { 1709 | return tx.scan(true, false, false, index, "", "", iterator) 1710 | } 1711 | 1712 | // DescendGreaterThan calls the iterator for every item in the database within 1713 | // the range [last, pivot), until iterator returns false. 1714 | // When an index is provided, the results will be ordered by the item values 1715 | // as specified by the less() function of the defined index. 1716 | // When an index is not provided, the results will be ordered by the item key. 1717 | // An invalid index will return an error. 1718 | func (tx *Tx) DescendGreaterThan(index, pivot string, 1719 | iterator func(key, value string) bool) error { 1720 | return tx.scan(true, true, false, index, pivot, "", iterator) 1721 | } 1722 | 1723 | // DescendLessOrEqual calls the iterator for every item in the database within 1724 | // the range [pivot, first], until iterator returns false. 1725 | // When an index is provided, the results will be ordered by the item values 1726 | // as specified by the less() function of the defined index. 1727 | // When an index is not provided, the results will be ordered by the item key. 1728 | // An invalid index will return an error. 1729 | func (tx *Tx) DescendLessOrEqual(index, pivot string, 1730 | iterator func(key, value string) bool) error { 1731 | return tx.scan(true, false, true, index, pivot, "", iterator) 1732 | } 1733 | 1734 | // DescendRange calls the iterator for every item in the database within 1735 | // the range [lessOrEqual, greaterThan), until iterator returns false. 1736 | // When an index is provided, the results will be ordered by the item values 1737 | // as specified by the less() function of the defined index. 1738 | // When an index is not provided, the results will be ordered by the item key. 1739 | // An invalid index will return an error. 1740 | func (tx *Tx) DescendRange(index, lessOrEqual, greaterThan string, 1741 | iterator func(key, value string) bool) error { 1742 | return tx.scan( 1743 | true, true, true, index, lessOrEqual, greaterThan, iterator, 1744 | ) 1745 | } 1746 | 1747 | // AscendEqual calls the iterator for every item in the database that equals 1748 | // pivot, until iterator returns false. 1749 | // When an index is provided, the results will be ordered by the item values 1750 | // as specified by the less() function of the defined index. 1751 | // When an index is not provided, the results will be ordered by the item key. 1752 | // An invalid index will return an error. 1753 | func (tx *Tx) AscendEqual(index, pivot string, 1754 | iterator func(key, value string) bool) error { 1755 | var err error 1756 | var less func(a, b string) bool 1757 | if index != "" { 1758 | less, err = tx.GetLess(index) 1759 | if err != nil { 1760 | return err 1761 | } 1762 | } 1763 | return tx.AscendGreaterOrEqual(index, pivot, func(key, value string) bool { 1764 | if less == nil { 1765 | if key != pivot { 1766 | return false 1767 | } 1768 | } else if less(pivot, value) { 1769 | return false 1770 | } 1771 | return iterator(key, value) 1772 | }) 1773 | } 1774 | 1775 | // DescendEqual calls the iterator for every item in the database that equals 1776 | // pivot, until iterator returns false. 1777 | // When an index is provided, the results will be ordered by the item values 1778 | // as specified by the less() function of the defined index. 1779 | // When an index is not provided, the results will be ordered by the item key. 1780 | // An invalid index will return an error. 1781 | func (tx *Tx) DescendEqual(index, pivot string, 1782 | iterator func(key, value string) bool) error { 1783 | var err error 1784 | var less func(a, b string) bool 1785 | if index != "" { 1786 | less, err = tx.GetLess(index) 1787 | if err != nil { 1788 | return err 1789 | } 1790 | } 1791 | return tx.DescendLessOrEqual(index, pivot, func(key, value string) bool { 1792 | if less == nil { 1793 | if key != pivot { 1794 | return false 1795 | } 1796 | } else if less(value, pivot) { 1797 | return false 1798 | } 1799 | return iterator(key, value) 1800 | }) 1801 | } 1802 | 1803 | // rect is used by Intersects and Nearby 1804 | type rect struct { 1805 | min, max []float64 1806 | } 1807 | 1808 | func (r *rect) Rect(ctx interface{}) (min, max []float64) { 1809 | return r.min, r.max 1810 | } 1811 | 1812 | // Nearby searches for rectangle items that are nearby a target rect. 1813 | // All items belonging to the specified index will be returned in order of 1814 | // nearest to farthest. 1815 | // The specified index must have been created by AddIndex() and the target 1816 | // is represented by the rect string. This string will be processed by the 1817 | // same bounds function that was passed to the CreateSpatialIndex() function. 1818 | // An invalid index will return an error. 1819 | // The dist param is the distance of the bounding boxes. In the case of 1820 | // simple 2D points, it's the distance of the two 2D points squared. 1821 | func (tx *Tx) Nearby(index, bounds string, 1822 | iterator func(key, value string, dist float64) bool) error { 1823 | if tx.db == nil { 1824 | return ErrTxClosed 1825 | } 1826 | if index == "" { 1827 | // cannot search on keys tree. just return nil. 1828 | return nil 1829 | } 1830 | // // wrap a rtree specific iterator around the user-defined iterator. 1831 | iter := func(item rtree.Item, dist float64) bool { 1832 | dbi := item.(*dbItem) 1833 | return iterator(dbi.key, dbi.val, dist) 1834 | } 1835 | idx := tx.db.idxs[index] 1836 | if idx == nil { 1837 | // index was not found. return error 1838 | return ErrNotFound 1839 | } 1840 | if idx.rtr == nil { 1841 | // not an r-tree index. just return nil 1842 | return nil 1843 | } 1844 | // execute the nearby search 1845 | var min, max []float64 1846 | if idx.rect != nil { 1847 | min, max = idx.rect(bounds) 1848 | } 1849 | // set the center param to false, which uses the box dist calc. 1850 | idx.rtr.KNN(&rect{min, max}, false, iter) 1851 | return nil 1852 | } 1853 | 1854 | // Intersects searches for rectangle items that intersect a target rect. 1855 | // The specified index must have been created by AddIndex() and the target 1856 | // is represented by the rect string. This string will be processed by the 1857 | // same bounds function that was passed to the CreateSpatialIndex() function. 1858 | // An invalid index will return an error. 1859 | func (tx *Tx) Intersects(index, bounds string, 1860 | iterator func(key, value string) bool) error { 1861 | if tx.db == nil { 1862 | return ErrTxClosed 1863 | } 1864 | if index == "" { 1865 | // cannot search on keys tree. just return nil. 1866 | return nil 1867 | } 1868 | // wrap a rtree specific iterator around the user-defined iterator. 1869 | iter := func(item rtree.Item) bool { 1870 | dbi := item.(*dbItem) 1871 | return iterator(dbi.key, dbi.val) 1872 | } 1873 | idx := tx.db.idxs[index] 1874 | if idx == nil { 1875 | // index was not found. return error 1876 | return ErrNotFound 1877 | } 1878 | if idx.rtr == nil { 1879 | // not an r-tree index. just return nil 1880 | return nil 1881 | } 1882 | // execute the search 1883 | var min, max []float64 1884 | if idx.rect != nil { 1885 | min, max = idx.rect(bounds) 1886 | } 1887 | idx.rtr.Search(&rect{min, max}, iter) 1888 | return nil 1889 | } 1890 | 1891 | // Len returns the number of items in the database 1892 | func (tx *Tx) Len() (int, error) { 1893 | if tx.db == nil { 1894 | return 0, ErrTxClosed 1895 | } 1896 | return tx.db.keys.Len(), nil 1897 | } 1898 | 1899 | // IndexOptions provides an index with additional features or 1900 | // alternate functionality. 1901 | type IndexOptions struct { 1902 | // CaseInsensitiveKeyMatching allow for case-insensitive 1903 | // matching on keys when setting key/values. 1904 | CaseInsensitiveKeyMatching bool 1905 | } 1906 | 1907 | // CreateIndex builds a new index and populates it with items. 1908 | // The items are ordered in an b-tree and can be retrieved using the 1909 | // Ascend* and Descend* methods. 1910 | // An error will occur if an index with the same name already exists. 1911 | // 1912 | // When a pattern is provided, the index will be populated with 1913 | // keys that match the specified pattern. This is a very simple pattern 1914 | // match where '*' matches on any number characters and '?' matches on 1915 | // any one character. 1916 | // The less function compares if string 'a' is less than string 'b'. 1917 | // It allows for indexes to create custom ordering. It's possible 1918 | // that the strings may be textual or binary. It's up to the provided 1919 | // less function to handle the content format and comparison. 1920 | // There are some default less function that can be used such as 1921 | // IndexString, IndexBinary, etc. 1922 | func (tx *Tx) CreateIndex(name, pattern string, 1923 | less ...func(a, b string) bool) error { 1924 | return tx.createIndex(name, pattern, less, nil, nil) 1925 | } 1926 | 1927 | // CreateIndexOptions is the same as CreateIndex except that it allows 1928 | // for additional options. 1929 | func (tx *Tx) CreateIndexOptions(name, pattern string, 1930 | opts *IndexOptions, 1931 | less ...func(a, b string) bool) error { 1932 | return tx.createIndex(name, pattern, less, nil, opts) 1933 | } 1934 | 1935 | // CreateSpatialIndex builds a new index and populates it with items. 1936 | // The items are organized in an r-tree and can be retrieved using the 1937 | // Intersects method. 1938 | // An error will occur if an index with the same name already exists. 1939 | // 1940 | // The rect function converts a string to a rectangle. The rectangle is 1941 | // represented by two arrays, min and max. Both arrays may have a length 1942 | // between 1 and 20, and both arrays must match in length. A length of 1 is a 1943 | // one dimensional rectangle, and a length of 4 is a four dimension rectangle. 1944 | // There is support for up to 20 dimensions. 1945 | // The values of min must be less than the values of max at the same dimension. 1946 | // Thus min[0] must be less-than-or-equal-to max[0]. 1947 | // The IndexRect is a default function that can be used for the rect 1948 | // parameter. 1949 | func (tx *Tx) CreateSpatialIndex(name, pattern string, 1950 | rect func(item string) (min, max []float64)) error { 1951 | return tx.createIndex(name, pattern, nil, rect, nil) 1952 | } 1953 | 1954 | // CreateSpatialIndexOptions is the same as CreateSpatialIndex except that 1955 | // it allows for additional options. 1956 | func (tx *Tx) CreateSpatialIndexOptions(name, pattern string, 1957 | opts *IndexOptions, 1958 | rect func(item string) (min, max []float64)) error { 1959 | return tx.createIndex(name, pattern, nil, rect, nil) 1960 | } 1961 | 1962 | // createIndex is called by CreateIndex() and CreateSpatialIndex() 1963 | func (tx *Tx) createIndex(name string, pattern string, 1964 | lessers []func(a, b string) bool, 1965 | rect func(item string) (min, max []float64), 1966 | opts *IndexOptions, 1967 | ) error { 1968 | if tx.db == nil { 1969 | return ErrTxClosed 1970 | } else if !tx.writable { 1971 | return ErrTxNotWritable 1972 | } else if tx.wc.itercount > 0 { 1973 | return ErrTxIterating 1974 | } 1975 | if name == "" { 1976 | // cannot create an index without a name. 1977 | // an empty name index is designated for the main "keys" tree. 1978 | return ErrIndexExists 1979 | } 1980 | // check if an index with that name already exists. 1981 | if _, ok := tx.db.idxs[name]; ok { 1982 | // index with name already exists. error. 1983 | return ErrIndexExists 1984 | } 1985 | // genreate a less function 1986 | var less func(a, b string) bool 1987 | switch len(lessers) { 1988 | default: 1989 | // multiple less functions specified. 1990 | // create a compound less function. 1991 | less = func(a, b string) bool { 1992 | for i := 0; i < len(lessers)-1; i++ { 1993 | if lessers[i](a, b) { 1994 | return true 1995 | } 1996 | if lessers[i](b, a) { 1997 | return false 1998 | } 1999 | } 2000 | return lessers[len(lessers)-1](a, b) 2001 | } 2002 | case 0: 2003 | // no less function 2004 | case 1: 2005 | less = lessers[0] 2006 | } 2007 | var sopts IndexOptions 2008 | if opts != nil { 2009 | sopts = *opts 2010 | } 2011 | if sopts.CaseInsensitiveKeyMatching { 2012 | pattern = strings.ToLower(pattern) 2013 | } 2014 | // intialize new index 2015 | idx := &index{ 2016 | name: name, 2017 | pattern: pattern, 2018 | less: less, 2019 | rect: rect, 2020 | db: tx.db, 2021 | opts: sopts, 2022 | } 2023 | idx.rebuild() 2024 | // save the index 2025 | tx.db.idxs[name] = idx 2026 | if tx.wc.rbkeys == nil { 2027 | // store the index in the rollback map. 2028 | if _, ok := tx.wc.rollbackIndexes[name]; !ok { 2029 | // we use nil to indicate that the index should be removed upon rollback. 2030 | tx.wc.rollbackIndexes[name] = nil 2031 | } 2032 | } 2033 | return nil 2034 | } 2035 | 2036 | // DropIndex removes an index. 2037 | func (tx *Tx) DropIndex(name string) error { 2038 | if tx.db == nil { 2039 | return ErrTxClosed 2040 | } else if !tx.writable { 2041 | return ErrTxNotWritable 2042 | } else if tx.wc.itercount > 0 { 2043 | return ErrTxIterating 2044 | } 2045 | if name == "" { 2046 | // cannot drop the default "keys" index 2047 | return ErrInvalidOperation 2048 | } 2049 | idx, ok := tx.db.idxs[name] 2050 | if !ok { 2051 | return ErrNotFound 2052 | } 2053 | // delete from the map. 2054 | // this is all that is needed to delete an index. 2055 | delete(tx.db.idxs, name) 2056 | if tx.wc.rbkeys == nil { 2057 | // store the index in the rollback map. 2058 | if _, ok := tx.wc.rollbackIndexes[name]; !ok { 2059 | // we use a non-nil copy of the index without the data to indicate that the 2060 | // index should be rebuilt upon rollback. 2061 | tx.wc.rollbackIndexes[name] = idx.clearCopy() 2062 | } 2063 | } 2064 | return nil 2065 | } 2066 | 2067 | // Indexes returns a list of index names. 2068 | func (tx *Tx) Indexes() ([]string, error) { 2069 | if tx.db == nil { 2070 | return nil, ErrTxClosed 2071 | } 2072 | names := make([]string, 0, len(tx.db.idxs)) 2073 | for name := range tx.db.idxs { 2074 | names = append(names, name) 2075 | } 2076 | sort.Strings(names) 2077 | return names, nil 2078 | } 2079 | 2080 | // Rect is helper function that returns a string representation 2081 | // of a rect. IndexRect() is the reverse function and can be used 2082 | // to generate a rect from a string. 2083 | func Rect(min, max []float64) string { 2084 | r := grect.Rect{Min: min, Max: max} 2085 | return r.String() 2086 | } 2087 | 2088 | // Point is a helper function that converts a series of float64s 2089 | // to a rectangle for a spatial index. 2090 | func Point(coords ...float64) string { 2091 | return Rect(coords, coords) 2092 | } 2093 | 2094 | // IndexRect is a helper function that converts string to a rect. 2095 | // Rect() is the reverse function and can be used to generate a string 2096 | // from a rect. 2097 | func IndexRect(a string) (min, max []float64) { 2098 | r := grect.Get(a) 2099 | return r.Min, r.Max 2100 | } 2101 | 2102 | // IndexString is a helper function that return true if 'a' is less than 'b'. 2103 | // This is a case-insensitive comparison. Use the IndexBinary() for comparing 2104 | // case-sensitive strings. 2105 | func IndexString(a, b string) bool { 2106 | for i := 0; i < len(a) && i < len(b); i++ { 2107 | if a[i] >= 'A' && a[i] <= 'Z' { 2108 | if b[i] >= 'A' && b[i] <= 'Z' { 2109 | // both are uppercase, do nothing 2110 | if a[i] < b[i] { 2111 | return true 2112 | } else if a[i] > b[i] { 2113 | return false 2114 | } 2115 | } else { 2116 | // a is uppercase, convert a to lowercase 2117 | if a[i]+32 < b[i] { 2118 | return true 2119 | } else if a[i]+32 > b[i] { 2120 | return false 2121 | } 2122 | } 2123 | } else if b[i] >= 'A' && b[i] <= 'Z' { 2124 | // b is uppercase, convert b to lowercase 2125 | if a[i] < b[i]+32 { 2126 | return true 2127 | } else if a[i] > b[i]+32 { 2128 | return false 2129 | } 2130 | } else { 2131 | // neither are uppercase 2132 | if a[i] < b[i] { 2133 | return true 2134 | } else if a[i] > b[i] { 2135 | return false 2136 | } 2137 | } 2138 | } 2139 | return len(a) < len(b) 2140 | } 2141 | 2142 | // IndexBinary is a helper function that returns true if 'a' is less than 'b'. 2143 | // This compares the raw binary of the string. 2144 | func IndexBinary(a, b string) bool { 2145 | return a < b 2146 | } 2147 | 2148 | // IndexInt is a helper function that returns true if 'a' is less than 'b'. 2149 | func IndexInt(a, b string) bool { 2150 | ia, _ := strconv.ParseInt(a, 10, 64) 2151 | ib, _ := strconv.ParseInt(b, 10, 64) 2152 | return ia < ib 2153 | } 2154 | 2155 | // IndexUint is a helper function that returns true if 'a' is less than 'b'. 2156 | // This compares uint64s that are added to the database using the 2157 | // Uint() conversion function. 2158 | func IndexUint(a, b string) bool { 2159 | ia, _ := strconv.ParseUint(a, 10, 64) 2160 | ib, _ := strconv.ParseUint(b, 10, 64) 2161 | return ia < ib 2162 | } 2163 | 2164 | // IndexFloat is a helper function that returns true if 'a' is less than 'b'. 2165 | // This compares float64s that are added to the database using the 2166 | // Float() conversion function. 2167 | func IndexFloat(a, b string) bool { 2168 | ia, _ := strconv.ParseFloat(a, 64) 2169 | ib, _ := strconv.ParseFloat(b, 64) 2170 | return ia < ib 2171 | } 2172 | 2173 | // IndexJSON provides for the ability to create an index on any JSON field. 2174 | // When the field is a string, the comparison will be case-insensitive. 2175 | // It returns a helper function used by CreateIndex. 2176 | func IndexJSON(path string) func(a, b string) bool { 2177 | return func(a, b string) bool { 2178 | return gjson.Get(a, path).Less(gjson.Get(b, path), false) 2179 | } 2180 | } 2181 | 2182 | // IndexJSONCaseSensitive provides for the ability to create an index on 2183 | // any JSON field. 2184 | // When the field is a string, the comparison will be case-sensitive. 2185 | // It returns a helper function used by CreateIndex. 2186 | func IndexJSONCaseSensitive(path string) func(a, b string) bool { 2187 | return func(a, b string) bool { 2188 | return gjson.Get(a, path).Less(gjson.Get(b, path), true) 2189 | } 2190 | } 2191 | 2192 | // Desc is a helper function that changes the order of an index. 2193 | func Desc(less func(a, b string) bool) func(a, b string) bool { 2194 | return func(a, b string) bool { return less(b, a) } 2195 | } 2196 | -------------------------------------------------------------------------------- /buntdb_test.go: -------------------------------------------------------------------------------- 1 | package buntdb 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io/ioutil" 8 | "math/rand" 9 | "os" 10 | "strconv" 11 | "strings" 12 | "sync" 13 | "testing" 14 | "time" 15 | ) 16 | 17 | func testOpen(t testing.TB) *DB { 18 | if err := os.RemoveAll("data.db"); err != nil { 19 | t.Fatal(err) 20 | } 21 | return testReOpen(t, nil) 22 | } 23 | func testReOpen(t testing.TB, db *DB) *DB { 24 | return testReOpenDelay(t, db, 0) 25 | } 26 | 27 | func testReOpenDelay(t testing.TB, db *DB, dur time.Duration) *DB { 28 | if db != nil { 29 | if err := db.Close(); err != nil { 30 | t.Fatal(err) 31 | } 32 | } 33 | time.Sleep(dur) 34 | db, err := Open("data.db") 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | return db 39 | } 40 | 41 | func testClose(db *DB) { 42 | _ = db.Close() 43 | _ = os.RemoveAll("data.db") 44 | } 45 | 46 | func TestBackgroudOperations(t *testing.T) { 47 | db := testOpen(t) 48 | defer testClose(db) 49 | for i := 0; i < 1000; i++ { 50 | if err := db.Update(func(tx *Tx) error { 51 | for j := 0; j < 200; j++ { 52 | if _, _, err := tx.Set(fmt.Sprintf("hello%d", j), "planet", nil); err != nil { 53 | return err 54 | } 55 | } 56 | if _, _, err := tx.Set("hi", "world", &SetOptions{Expires: true, TTL: time.Second / 2}); err != nil { 57 | return err 58 | } 59 | return nil 60 | }); err != nil { 61 | t.Fatal(err) 62 | } 63 | } 64 | n := 0 65 | err := db.View(func(tx *Tx) error { 66 | var err error 67 | n, err = tx.Len() 68 | return err 69 | }) 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | if n != 201 { 74 | t.Fatalf("expecting '%v', got '%v'", 201, n) 75 | } 76 | time.Sleep(time.Millisecond * 1500) 77 | db = testReOpen(t, db) 78 | defer testClose(db) 79 | n = 0 80 | err = db.View(func(tx *Tx) error { 81 | var err error 82 | n, err = tx.Len() 83 | return err 84 | }) 85 | if err != nil { 86 | t.Fatal(err) 87 | } 88 | if n != 200 { 89 | t.Fatalf("expecting '%v', got '%v'", 200, n) 90 | } 91 | } 92 | func TestSaveLoad(t *testing.T) { 93 | db, _ := Open(":memory:") 94 | defer db.Close() 95 | if err := db.Update(func(tx *Tx) error { 96 | for i := 0; i < 20; i++ { 97 | _, _, err := tx.Set(fmt.Sprintf("key:%d", i), fmt.Sprintf("planet:%d", i), nil) 98 | if err != nil { 99 | return err 100 | } 101 | } 102 | return nil 103 | }); err != nil { 104 | t.Fatal(err) 105 | } 106 | f, err := os.Create("temp.db") 107 | if err != nil { 108 | t.Fatal(err) 109 | } 110 | defer func() { 111 | f.Close() 112 | os.RemoveAll("temp.db") 113 | }() 114 | if err := db.Save(f); err != nil { 115 | t.Fatal(err) 116 | } 117 | if err := f.Close(); err != nil { 118 | t.Fatal(err) 119 | } 120 | db.Close() 121 | db, _ = Open(":memory:") 122 | defer db.Close() 123 | f, err = os.Open("temp.db") 124 | if err != nil { 125 | t.Fatal(err) 126 | } 127 | defer f.Close() 128 | if err := db.Load(f); err != nil { 129 | t.Fatal(err) 130 | } 131 | if err := db.View(func(tx *Tx) error { 132 | for i := 0; i < 20; i++ { 133 | ex := fmt.Sprintf("planet:%d", i) 134 | val, err := tx.Get(fmt.Sprintf("key:%d", i)) 135 | if err != nil { 136 | return err 137 | } 138 | if ex != val { 139 | t.Fatalf("expected %s, got %s", ex, val) 140 | } 141 | } 142 | return nil 143 | }); err != nil { 144 | t.Fatal(err) 145 | } 146 | } 147 | 148 | func TestMutatingIterator(t *testing.T) { 149 | db := testOpen(t) 150 | defer testClose(db) 151 | count := 1000 152 | if err := db.CreateIndex("ages", "user:*:age", IndexInt); err != nil { 153 | t.Fatal(err) 154 | } 155 | 156 | for i := 0; i < 10; i++ { 157 | if err := db.Update(func(tx *Tx) error { 158 | for j := 0; j < count; j++ { 159 | key := fmt.Sprintf("user:%d:age", j) 160 | val := fmt.Sprintf("%d", rand.Intn(100)) 161 | if _, _, err := tx.Set(key, val, nil); err != nil { 162 | return err 163 | } 164 | } 165 | return nil 166 | }); err != nil { 167 | t.Fatal(err) 168 | } 169 | 170 | if err := db.Update(func(tx *Tx) error { 171 | return tx.Ascend("ages", func(key, val string) bool { 172 | _, err := tx.Delete(key) 173 | if err != ErrTxIterating { 174 | t.Fatal("should not be able to call Delete while iterating.") 175 | } 176 | _, _, err = tx.Set(key, "", nil) 177 | if err != ErrTxIterating { 178 | t.Fatal("should not be able to call Set while iterating.") 179 | } 180 | return true 181 | }) 182 | }); err != nil { 183 | t.Fatal(err) 184 | } 185 | } 186 | } 187 | 188 | func TestCaseInsensitiveIndex(t *testing.T) { 189 | db := testOpen(t) 190 | defer testClose(db) 191 | count := 1000 192 | if err := db.Update(func(tx *Tx) error { 193 | opts := &IndexOptions{ 194 | CaseInsensitiveKeyMatching: true, 195 | } 196 | return tx.CreateIndexOptions("ages", "User:*:age", opts, IndexInt) 197 | }); err != nil { 198 | t.Fatal(err) 199 | } 200 | 201 | if err := db.Update(func(tx *Tx) error { 202 | for j := 0; j < count; j++ { 203 | key := fmt.Sprintf("user:%d:age", j) 204 | val := fmt.Sprintf("%d", rand.Intn(100)) 205 | if _, _, err := tx.Set(key, val, nil); err != nil { 206 | return err 207 | } 208 | } 209 | return nil 210 | }); err != nil { 211 | t.Fatal(err) 212 | } 213 | 214 | if err := db.View(func(tx *Tx) error { 215 | var vals []string 216 | err := tx.Ascend("ages", func(key, value string) bool { 217 | vals = append(vals, value) 218 | return true 219 | }) 220 | if err != nil { 221 | return err 222 | } 223 | if len(vals) != count { 224 | return fmt.Errorf("expected '%v', got '%v'", count, len(vals)) 225 | } 226 | return nil 227 | }); err != nil { 228 | t.Fatal(err) 229 | } 230 | 231 | } 232 | 233 | func TestIndexTransaction(t *testing.T) { 234 | db := testOpen(t) 235 | defer testClose(db) 236 | var errFine = errors.New("this is fine") 237 | ascend := func(tx *Tx, index string) ([]string, error) { 238 | var vals []string 239 | if err := tx.Ascend(index, func(key, val string) bool { 240 | vals = append(vals, key, val) 241 | return true 242 | }); err != nil { 243 | return nil, err 244 | } 245 | return vals, nil 246 | } 247 | ascendEqual := func(tx *Tx, index string, vals []string) error { 248 | vals2, err := ascend(tx, index) 249 | if err != nil { 250 | return err 251 | } 252 | if len(vals) != len(vals2) { 253 | return errors.New("invalid size match") 254 | } 255 | for i := 0; i < len(vals); i++ { 256 | if vals[i] != vals2[i] { 257 | return errors.New("invalid order") 258 | } 259 | } 260 | return nil 261 | } 262 | // test creating an index and adding items 263 | if err := db.Update(func(tx *Tx) error { 264 | tx.Set("1", "3", nil) 265 | tx.Set("2", "2", nil) 266 | tx.Set("3", "1", nil) 267 | if err := tx.CreateIndex("idx1", "*", IndexInt); err != nil { 268 | return err 269 | } 270 | if err := ascendEqual(tx, "idx1", []string{"3", "1", "2", "2", "1", "3"}); err != nil { 271 | return err 272 | } 273 | return nil 274 | }); err != nil { 275 | t.Fatal(err) 276 | } 277 | 278 | // test to see if the items persisted from previous transaction 279 | // test add item. 280 | // test force rollback. 281 | if err := db.Update(func(tx *Tx) error { 282 | if err := ascendEqual(tx, "idx1", []string{"3", "1", "2", "2", "1", "3"}); err != nil { 283 | return err 284 | } 285 | tx.Set("4", "0", nil) 286 | if err := ascendEqual(tx, "idx1", []string{"4", "0", "3", "1", "2", "2", "1", "3"}); err != nil { 287 | return err 288 | } 289 | return errFine 290 | }); err != errFine { 291 | t.Fatalf("expected '%v', got '%v'", errFine, err) 292 | } 293 | 294 | // test to see if the rollback happened 295 | if err := db.View(func(tx *Tx) error { 296 | if err := ascendEqual(tx, "idx1", []string{"3", "1", "2", "2", "1", "3"}); err != nil { 297 | return err 298 | } 299 | return nil 300 | }); err != nil { 301 | t.Fatalf("expected '%v', got '%v'", nil, err) 302 | } 303 | 304 | // del item, drop index, rollback 305 | if err := db.Update(func(tx *Tx) error { 306 | if err := tx.DropIndex("idx1"); err != nil { 307 | return err 308 | } 309 | return errFine 310 | }); err != errFine { 311 | t.Fatalf("expected '%v', got '%v'", errFine, err) 312 | } 313 | 314 | // test to see if the rollback happened 315 | if err := db.View(func(tx *Tx) error { 316 | if err := ascendEqual(tx, "idx1", []string{"3", "1", "2", "2", "1", "3"}); err != nil { 317 | return err 318 | } 319 | return nil 320 | }); err != nil { 321 | t.Fatalf("expected '%v', got '%v'", nil, err) 322 | } 323 | 324 | various := func(reterr error) error { 325 | // del item 3, add index 2, add item 4, test index 1 and 2. 326 | // flushdb, test index 1 and 2. 327 | // add item 1 and 2, add index 2 and 3, test index 2 and 3 328 | return db.Update(func(tx *Tx) error { 329 | tx.Delete("3") 330 | tx.CreateIndex("idx2", "*", IndexInt) 331 | tx.Set("4", "0", nil) 332 | if err := ascendEqual(tx, "idx1", []string{"4", "0", "2", "2", "1", "3"}); err != nil { 333 | return fmt.Errorf("err: %v", err) 334 | } 335 | if err := ascendEqual(tx, "idx2", []string{"4", "0", "2", "2", "1", "3"}); err != nil { 336 | return fmt.Errorf("err: %v", err) 337 | } 338 | tx.DeleteAll() 339 | if err := ascendEqual(tx, "idx1", []string{}); err != nil { 340 | return fmt.Errorf("err: %v", err) 341 | } 342 | if err := ascendEqual(tx, "idx2", []string{}); err != nil { 343 | return fmt.Errorf("err: %v", err) 344 | } 345 | tx.Set("1", "3", nil) 346 | tx.Set("2", "2", nil) 347 | tx.CreateIndex("idx1", "*", IndexInt) 348 | tx.CreateIndex("idx2", "*", IndexInt) 349 | if err := ascendEqual(tx, "idx1", []string{"2", "2", "1", "3"}); err != nil { 350 | return fmt.Errorf("err: %v", err) 351 | } 352 | if err := ascendEqual(tx, "idx2", []string{"2", "2", "1", "3"}); err != nil { 353 | return fmt.Errorf("err: %v", err) 354 | } 355 | return reterr 356 | }) 357 | } 358 | // various rollback 359 | if err := various(errFine); err != errFine { 360 | t.Fatalf("expected '%v', got '%v'", errFine, err) 361 | } 362 | // test to see if the rollback happened 363 | if err := db.View(func(tx *Tx) error { 364 | if err := ascendEqual(tx, "idx1", []string{"3", "1", "2", "2", "1", "3"}); err != nil { 365 | return fmt.Errorf("err: %v", err) 366 | } 367 | if err := ascendEqual(tx, "idx2", []string{"3", "1", "2", "2", "1", "3"}); err != ErrNotFound { 368 | return fmt.Errorf("err: %v", err) 369 | } 370 | return nil 371 | }); err != nil { 372 | t.Fatalf("expected '%v', got '%v'", nil, err) 373 | } 374 | 375 | // various commit 376 | if err := various(nil); err != nil { 377 | t.Fatalf("expected '%v', got '%v'", nil, err) 378 | } 379 | 380 | // test to see if the commit happened 381 | if err := db.View(func(tx *Tx) error { 382 | if err := ascendEqual(tx, "idx1", []string{"2", "2", "1", "3"}); err != nil { 383 | return fmt.Errorf("err: %v", err) 384 | } 385 | if err := ascendEqual(tx, "idx2", []string{"2", "2", "1", "3"}); err != nil { 386 | return fmt.Errorf("err: %v", err) 387 | } 388 | return nil 389 | }); err != nil { 390 | t.Fatalf("expected '%v', got '%v'", nil, err) 391 | } 392 | } 393 | 394 | func TestDeleteAll(t *testing.T) { 395 | db := testOpen(t) 396 | defer testClose(db) 397 | 398 | db.Update(func(tx *Tx) error { 399 | tx.Set("hello1", "planet1", nil) 400 | tx.Set("hello2", "planet2", nil) 401 | tx.Set("hello3", "planet3", nil) 402 | return nil 403 | }) 404 | db.CreateIndex("all", "*", IndexString) 405 | db.Update(func(tx *Tx) error { 406 | tx.Set("hello1", "planet1.1", nil) 407 | tx.DeleteAll() 408 | tx.Set("bb", "11", nil) 409 | tx.Set("aa", "**", nil) 410 | tx.Delete("aa") 411 | tx.Set("aa", "22", nil) 412 | return nil 413 | }) 414 | var res string 415 | var res2 string 416 | db.View(func(tx *Tx) error { 417 | tx.Ascend("", func(key, val string) bool { 418 | res += key + ":" + val + "\n" 419 | return true 420 | }) 421 | tx.Ascend("all", func(key, val string) bool { 422 | res2 += key + ":" + val + "\n" 423 | return true 424 | }) 425 | return nil 426 | }) 427 | if res != "aa:22\nbb:11\n" { 428 | t.Fatal("fail") 429 | } 430 | if res2 != "bb:11\naa:22\n" { 431 | t.Fatal("fail") 432 | } 433 | db = testReOpen(t, db) 434 | defer testClose(db) 435 | res = "" 436 | res2 = "" 437 | db.CreateIndex("all", "*", IndexString) 438 | db.View(func(tx *Tx) error { 439 | tx.Ascend("", func(key, val string) bool { 440 | res += key + ":" + val + "\n" 441 | return true 442 | }) 443 | tx.Ascend("all", func(key, val string) bool { 444 | res2 += key + ":" + val + "\n" 445 | return true 446 | }) 447 | return nil 448 | }) 449 | if res != "aa:22\nbb:11\n" { 450 | t.Fatal("fail") 451 | } 452 | if res2 != "bb:11\naa:22\n" { 453 | t.Fatal("fail") 454 | } 455 | db.Update(func(tx *Tx) error { 456 | tx.Set("1", "1", nil) 457 | tx.Set("2", "2", nil) 458 | tx.Set("3", "3", nil) 459 | tx.Set("4", "4", nil) 460 | return nil 461 | }) 462 | err := db.Update(func(tx *Tx) error { 463 | tx.Set("1", "a", nil) 464 | tx.Set("5", "5", nil) 465 | tx.Delete("2") 466 | tx.Set("6", "6", nil) 467 | tx.DeleteAll() 468 | tx.Set("7", "7", nil) 469 | tx.Set("8", "8", nil) 470 | tx.Set("6", "c", nil) 471 | return errors.New("please rollback") 472 | }) 473 | if err == nil || err.Error() != "please rollback" { 474 | t.Fatal("expecteding 'please rollback' error") 475 | } 476 | 477 | res = "" 478 | res2 = "" 479 | db.View(func(tx *Tx) error { 480 | tx.Ascend("", func(key, val string) bool { 481 | res += key + ":" + val + "\n" 482 | return true 483 | }) 484 | tx.Ascend("all", func(key, val string) bool { 485 | res2 += key + ":" + val + "\n" 486 | return true 487 | }) 488 | return nil 489 | }) 490 | if res != "1:1\n2:2\n3:3\n4:4\naa:22\nbb:11\n" { 491 | t.Fatal("fail") 492 | } 493 | if res2 != "1:1\nbb:11\n2:2\naa:22\n3:3\n4:4\n" { 494 | t.Fatal("fail") 495 | } 496 | } 497 | 498 | func TestAscendEqual(t *testing.T) { 499 | db := testOpen(t) 500 | defer testClose(db) 501 | if err := db.Update(func(tx *Tx) error { 502 | for i := 0; i < 300; i++ { 503 | _, _, err := tx.Set(fmt.Sprintf("key:%05dA", i), fmt.Sprintf("%d", i+1000), nil) 504 | if err != nil { 505 | return err 506 | } 507 | _, _, err = tx.Set(fmt.Sprintf("key:%05dB", i), fmt.Sprintf("%d", i+1000), nil) 508 | if err != nil { 509 | return err 510 | } 511 | } 512 | return tx.CreateIndex("num", "*", IndexInt) 513 | }); err != nil { 514 | t.Fatal(err) 515 | } 516 | var res []string 517 | if err := db.View(func(tx *Tx) error { 518 | return tx.AscendEqual("", "key:00055A", func(key, value string) bool { 519 | res = append(res, key) 520 | return true 521 | }) 522 | }); err != nil { 523 | t.Fatal(err) 524 | } 525 | if len(res) != 1 { 526 | t.Fatalf("expected %v, got %v", 1, len(res)) 527 | } 528 | if res[0] != "key:00055A" { 529 | t.Fatalf("expected %v, got %v", "key:00055A", res[0]) 530 | } 531 | res = nil 532 | if err := db.View(func(tx *Tx) error { 533 | return tx.AscendEqual("num", "1125", func(key, value string) bool { 534 | res = append(res, key) 535 | return true 536 | }) 537 | }); err != nil { 538 | t.Fatal(err) 539 | } 540 | if len(res) != 2 { 541 | t.Fatalf("expected %v, got %v", 2, len(res)) 542 | } 543 | if res[0] != "key:00125A" { 544 | t.Fatalf("expected %v, got %v", "key:00125A", res[0]) 545 | } 546 | if res[1] != "key:00125B" { 547 | t.Fatalf("expected %v, got %v", "key:00125B", res[1]) 548 | } 549 | } 550 | func TestDescendEqual(t *testing.T) { 551 | db := testOpen(t) 552 | defer testClose(db) 553 | if err := db.Update(func(tx *Tx) error { 554 | for i := 0; i < 300; i++ { 555 | _, _, err := tx.Set(fmt.Sprintf("key:%05dA", i), fmt.Sprintf("%d", i+1000), nil) 556 | if err != nil { 557 | return err 558 | } 559 | _, _, err = tx.Set(fmt.Sprintf("key:%05dB", i), fmt.Sprintf("%d", i+1000), nil) 560 | if err != nil { 561 | return err 562 | } 563 | } 564 | return tx.CreateIndex("num", "*", IndexInt) 565 | }); err != nil { 566 | t.Fatal(err) 567 | } 568 | var res []string 569 | if err := db.View(func(tx *Tx) error { 570 | return tx.DescendEqual("", "key:00055A", func(key, value string) bool { 571 | res = append(res, key) 572 | return true 573 | }) 574 | }); err != nil { 575 | t.Fatal(err) 576 | } 577 | if len(res) != 1 { 578 | t.Fatalf("expected %v, got %v", 1, len(res)) 579 | } 580 | if res[0] != "key:00055A" { 581 | t.Fatalf("expected %v, got %v", "key:00055A", res[0]) 582 | } 583 | res = nil 584 | if err := db.View(func(tx *Tx) error { 585 | return tx.DescendEqual("num", "1125", func(key, value string) bool { 586 | res = append(res, key) 587 | return true 588 | }) 589 | }); err != nil { 590 | t.Fatal(err) 591 | } 592 | if len(res) != 2 { 593 | t.Fatalf("expected %v, got %v", 2, len(res)) 594 | } 595 | if res[0] != "key:00125B" { 596 | t.Fatalf("expected %v, got %v", "key:00125B", res[0]) 597 | } 598 | if res[1] != "key:00125A" { 599 | t.Fatalf("expected %v, got %v", "key:00125A", res[1]) 600 | } 601 | } 602 | func TestVariousTx(t *testing.T) { 603 | db := testOpen(t) 604 | defer testClose(db) 605 | if err := db.Update(func(tx *Tx) error { 606 | _, _, err := tx.Set("hello", "planet", nil) 607 | return err 608 | }); err != nil { 609 | t.Fatal(err) 610 | } 611 | errBroken := errors.New("broken") 612 | if err := db.Update(func(tx *Tx) error { 613 | _, _, _ = tx.Set("hello", "world", nil) 614 | return errBroken 615 | }); err != errBroken { 616 | t.Fatalf("did not correctly receive the user-defined transaction error.") 617 | } 618 | var val string 619 | err := db.View(func(tx *Tx) error { 620 | var err error 621 | val, err = tx.Get("hello") 622 | return err 623 | }) 624 | if err != nil { 625 | t.Fatal(err) 626 | } 627 | if val == "world" { 628 | t.Fatal("a rollbacked transaction got through") 629 | } 630 | if val != "planet" { 631 | t.Fatalf("expecting '%v', got '%v'", "planet", val) 632 | } 633 | if err := db.Update(func(tx *Tx) error { 634 | tx.db = nil 635 | if _, _, err := tx.Set("hello", "planet", nil); err != ErrTxClosed { 636 | t.Fatal("expecting a tx closed error") 637 | } 638 | if _, err := tx.Delete("hello"); err != ErrTxClosed { 639 | t.Fatal("expecting a tx closed error") 640 | } 641 | if _, err := tx.Get("hello"); err != ErrTxClosed { 642 | t.Fatal("expecting a tx closed error") 643 | } 644 | tx.db = db 645 | tx.writable = false 646 | if _, _, err := tx.Set("hello", "planet", nil); err != ErrTxNotWritable { 647 | t.Fatal("expecting a tx not writable error") 648 | } 649 | if _, err := tx.Delete("hello"); err != ErrTxNotWritable { 650 | t.Fatal("expecting a tx not writable error") 651 | } 652 | tx.writable = true 653 | if _, err := tx.Get("something"); err != ErrNotFound { 654 | t.Fatalf("expecting not found error") 655 | } 656 | if _, err := tx.Delete("something"); err != ErrNotFound { 657 | t.Fatalf("expecting not found error") 658 | } 659 | if _, _, err := tx.Set("var", "val", &SetOptions{Expires: true, TTL: 0}); err != nil { 660 | t.Fatal(err) 661 | } 662 | if _, err := tx.Get("var"); err != ErrNotFound { 663 | t.Fatalf("expecting not found error") 664 | } 665 | if _, err := tx.Delete("var"); err != ErrNotFound { 666 | tx.unlock() 667 | t.Fatalf("expecting not found error") 668 | } 669 | return nil 670 | }); err != nil { 671 | t.Fatal(err) 672 | } 673 | 674 | // test non-managed transactions 675 | tx, err := db.Begin(true) 676 | if err != nil { 677 | t.Fatal(err) 678 | } 679 | defer tx.Rollback() 680 | _, _, err = tx.Set("howdy", "world", nil) 681 | if err != nil { 682 | t.Fatal(err) 683 | } 684 | if err := tx.Commit(); err != nil { 685 | t.Fatal(err) 686 | } 687 | tx, err = db.Begin(false) 688 | if err != nil { 689 | t.Fatal(err) 690 | } 691 | defer tx.Rollback() 692 | v, err := tx.Get("howdy") 693 | if err != nil { 694 | t.Fatal(err) 695 | } 696 | if v != "world" { 697 | t.Fatalf("expecting '%v', got '%v'", "world", v) 698 | } 699 | if err := tx.Rollback(); err != nil { 700 | t.Fatal(err) 701 | } 702 | tx, err = db.Begin(true) 703 | if err != nil { 704 | t.Fatal(err) 705 | } 706 | defer tx.Rollback() 707 | v, err = tx.Get("howdy") 708 | if err != nil { 709 | t.Fatal(err) 710 | } 711 | if v != "world" { 712 | t.Fatalf("expecting '%v', got '%v'", "world", v) 713 | } 714 | _, err = tx.Delete("howdy") 715 | if err != nil { 716 | t.Fatal(err) 717 | } 718 | if err := tx.Commit(); err != nil { 719 | t.Fatal(err) 720 | } 721 | 722 | // test for invalid commits 723 | if err := db.Update(func(tx *Tx) error { 724 | // we are going to do some hackery 725 | defer func() { 726 | if v := recover(); v != nil { 727 | if v.(string) != "managed tx commit not allowed" { 728 | t.Fatal(v.(string)) 729 | } 730 | } 731 | }() 732 | return tx.Commit() 733 | }); err != nil { 734 | t.Fatal(err) 735 | } 736 | 737 | // test for invalid commits 738 | if err := db.Update(func(tx *Tx) error { 739 | // we are going to do some hackery 740 | defer func() { 741 | if v := recover(); v != nil { 742 | if v.(string) != "managed tx rollback not allowed" { 743 | t.Fatal(v.(string)) 744 | } 745 | } 746 | }() 747 | return tx.Rollback() 748 | }); err != nil { 749 | t.Fatal(err) 750 | } 751 | 752 | // test for closed transactions 753 | if err := db.Update(func(tx *Tx) error { 754 | tx.db = nil 755 | return nil 756 | }); err != ErrTxClosed { 757 | t.Fatal("expecting tx closed error") 758 | } 759 | db.mu.Unlock() 760 | 761 | // test for invalid writes 762 | if err := db.Update(func(tx *Tx) error { 763 | tx.writable = false 764 | return nil 765 | }); err != ErrTxNotWritable { 766 | t.Fatal("expecting tx not writable error") 767 | } 768 | db.mu.Unlock() 769 | // test for closed transactions 770 | if err := db.View(func(tx *Tx) error { 771 | tx.db = nil 772 | return nil 773 | }); err != ErrTxClosed { 774 | t.Fatal("expecting tx closed error") 775 | } 776 | db.mu.RUnlock() 777 | // flush to unwritable file 778 | if err := db.Update(func(tx *Tx) error { 779 | _, _, err := tx.Set("var1", "val1", nil) 780 | if err != nil { 781 | t.Fatal(err) 782 | } 783 | return tx.db.file.Close() 784 | }); err == nil { 785 | t.Fatal("should not be able to commit when the file is closed") 786 | } 787 | db.file, err = os.OpenFile("data.db", os.O_CREATE|os.O_RDWR, 0666) 788 | if err != nil { 789 | t.Fatal(err) 790 | } 791 | if _, err := db.file.Seek(0, 2); err != nil { 792 | t.Fatal(err) 793 | } 794 | db.buf = nil 795 | if err := db.CreateIndex("blank", "*", nil); err != nil { 796 | t.Fatal(err) 797 | } 798 | if err := db.CreateIndex("real", "*", IndexInt); err != nil { 799 | t.Fatal(err) 800 | } 801 | // test scanning 802 | if err := db.Update(func(tx *Tx) error { 803 | less, err := tx.GetLess("junk") 804 | if err != ErrNotFound { 805 | t.Fatalf("expecting a not found, got %v", err) 806 | } 807 | if less != nil { 808 | t.Fatal("expecting nil, got a less function") 809 | } 810 | less, err = tx.GetLess("blank") 811 | if err != ErrNotFound { 812 | t.Fatalf("expecting a not found, got %v", err) 813 | } 814 | if less != nil { 815 | t.Fatal("expecting nil, got a less function") 816 | } 817 | less, err = tx.GetLess("real") 818 | if err != nil { 819 | return err 820 | } 821 | if less == nil { 822 | t.Fatal("expecting a less function, got nil") 823 | } 824 | _, _, err = tx.Set("nothing", "here", nil) 825 | return err 826 | }); err != nil { 827 | t.Fatal(err) 828 | } 829 | if err := db.View(func(tx *Tx) error { 830 | s := "" 831 | err := tx.Ascend("", func(key, val string) bool { 832 | s += key + ":" + val + "\n" 833 | return true 834 | }) 835 | if err != nil { 836 | return err 837 | } 838 | if s != "hello:planet\nnothing:here\n" { 839 | t.Fatal("invalid scan") 840 | } 841 | tx.db = nil 842 | err = tx.Ascend("", func(key, val string) bool { return true }) 843 | if err != ErrTxClosed { 844 | tx.unlock() 845 | t.Fatal("expecting tx closed error") 846 | } 847 | tx.db = db 848 | err = tx.Ascend("na", func(key, val string) bool { return true }) 849 | if err != ErrNotFound { 850 | t.Fatal("expecting not found error") 851 | } 852 | err = tx.Ascend("blank", func(key, val string) bool { return true }) 853 | if err != nil { 854 | t.Fatal(err) 855 | } 856 | s = "" 857 | err = tx.AscendLessThan("", "liger", func(key, val string) bool { 858 | s += key + ":" + val + "\n" 859 | return true 860 | }) 861 | if err != nil { 862 | return err 863 | } 864 | if s != "hello:planet\n" { 865 | t.Fatal("invalid scan") 866 | } 867 | 868 | s = "" 869 | err = tx.Descend("", func(key, val string) bool { 870 | s += key + ":" + val + "\n" 871 | return true 872 | }) 873 | if err != nil { 874 | return err 875 | } 876 | if s != "nothing:here\nhello:planet\n" { 877 | t.Fatal("invalid scan") 878 | } 879 | 880 | s = "" 881 | err = tx.DescendLessOrEqual("", "liger", func(key, val string) bool { 882 | s += key + ":" + val + "\n" 883 | return true 884 | }) 885 | if err != nil { 886 | return err 887 | } 888 | 889 | if s != "hello:planet\n" { 890 | t.Fatal("invalid scan") 891 | } 892 | 893 | s = "" 894 | err = tx.DescendGreaterThan("", "liger", func(key, val string) bool { 895 | s += key + ":" + val + "\n" 896 | return true 897 | }) 898 | if err != nil { 899 | return err 900 | } 901 | 902 | if s != "nothing:here\n" { 903 | t.Fatal("invalid scan") 904 | } 905 | s = "" 906 | err = tx.DescendRange("", "liger", "apple", func(key, val string) bool { 907 | s += key + ":" + val + "\n" 908 | return true 909 | }) 910 | if err != nil { 911 | return err 912 | } 913 | if s != "hello:planet\n" { 914 | t.Fatal("invalid scan") 915 | } 916 | return nil 917 | }); err != nil { 918 | t.Fatal(err) 919 | } 920 | 921 | // test some spatial stuff 922 | if err := db.CreateSpatialIndex("spat", "rect:*", IndexRect); err != nil { 923 | t.Fatal(err) 924 | } 925 | if err := db.CreateSpatialIndex("junk", "rect:*", nil); err != nil { 926 | t.Fatal(err) 927 | } 928 | err = db.Update(func(tx *Tx) error { 929 | rect, err := tx.GetRect("spat") 930 | if err != nil { 931 | return err 932 | } 933 | if rect == nil { 934 | t.Fatal("expecting a rect function, got nil") 935 | } 936 | rect, err = tx.GetRect("junk") 937 | if err != ErrNotFound { 938 | t.Fatalf("expecting a not found, got %v", err) 939 | } 940 | if rect != nil { 941 | t.Fatal("expecting nil, got a rect function") 942 | } 943 | rect, err = tx.GetRect("na") 944 | if err != ErrNotFound { 945 | t.Fatalf("expecting a not found, got %v", err) 946 | } 947 | if rect != nil { 948 | t.Fatal("expecting nil, got a rect function") 949 | } 950 | if _, _, err := tx.Set("rect:1", "[10 10],[20 20]", nil); err != nil { 951 | return err 952 | } 953 | if _, _, err := tx.Set("rect:2", "[15 15],[25 25]", nil); err != nil { 954 | return err 955 | } 956 | if _, _, err := tx.Set("shape:1", "[12 12],[25 25]", nil); err != nil { 957 | return err 958 | } 959 | s := "" 960 | err = tx.Intersects("spat", "[5 5],[13 13]", func(key, val string) bool { 961 | s += key + ":" + val + "\n" 962 | return true 963 | }) 964 | if err != nil { 965 | return err 966 | } 967 | if s != "rect:1:[10 10],[20 20]\n" { 968 | t.Fatal("invalid scan") 969 | } 970 | tx.db = nil 971 | err = tx.Intersects("spat", "[5 5],[13 13]", func(key, val string) bool { 972 | return true 973 | }) 974 | if err != ErrTxClosed { 975 | t.Fatal("expecting tx closed error") 976 | } 977 | tx.db = db 978 | err = tx.Intersects("", "[5 5],[13 13]", func(key, val string) bool { 979 | return true 980 | }) 981 | if err != nil { 982 | t.Fatal(err) 983 | } 984 | err = tx.Intersects("na", "[5 5],[13 13]", func(key, val string) bool { 985 | return true 986 | }) 987 | if err != ErrNotFound { 988 | t.Fatal("expecting not found error") 989 | } 990 | err = tx.Intersects("junk", "[5 5],[13 13]", func(key, val string) bool { 991 | return true 992 | }) 993 | if err != nil { 994 | t.Fatal(err) 995 | } 996 | n, err := tx.Len() 997 | if err != nil { 998 | t.Fatal(err) 999 | } 1000 | if n != 5 { 1001 | t.Fatalf("expecting %v, got %v", 5, n) 1002 | } 1003 | tx.db = nil 1004 | _, err = tx.Len() 1005 | if err != ErrTxClosed { 1006 | t.Fatal("expecting tx closed error") 1007 | } 1008 | tx.db = db 1009 | return nil 1010 | }) 1011 | if err != nil { 1012 | t.Fatal(err) 1013 | } 1014 | // test after closing 1015 | if err := db.Close(); err != nil { 1016 | t.Fatal(err) 1017 | } 1018 | 1019 | if err := db.Update(func(tx *Tx) error { return nil }); err != ErrDatabaseClosed { 1020 | t.Fatalf("should not be able to perform transactionso on a closed database.") 1021 | } 1022 | } 1023 | 1024 | func TestNearby(t *testing.T) { 1025 | rand.Seed(time.Now().UnixNano()) 1026 | N := 100000 1027 | db, _ := Open(":memory:") 1028 | db.CreateSpatialIndex("points", "*", IndexRect) 1029 | db.Update(func(tx *Tx) error { 1030 | for i := 0; i < N; i++ { 1031 | p := Point( 1032 | rand.Float64()*100, 1033 | rand.Float64()*100, 1034 | rand.Float64()*100, 1035 | rand.Float64()*100, 1036 | ) 1037 | tx.Set(fmt.Sprintf("p:%d", i), p, nil) 1038 | } 1039 | return nil 1040 | }) 1041 | var keys, values []string 1042 | var dists []float64 1043 | var pdist float64 1044 | var i int 1045 | db.View(func(tx *Tx) error { 1046 | tx.Nearby("points", Point(0, 0, 0, 0), func(key, value string, dist float64) bool { 1047 | if i != 0 && dist < pdist { 1048 | t.Fatal("out of order") 1049 | } 1050 | keys = append(keys, key) 1051 | values = append(values, value) 1052 | dists = append(dists, dist) 1053 | pdist = dist 1054 | i++ 1055 | return true 1056 | }) 1057 | return nil 1058 | }) 1059 | if len(keys) != N { 1060 | t.Fatalf("expected '%v', got '%v'", N, len(keys)) 1061 | } 1062 | } 1063 | 1064 | func Example_descKeys() { 1065 | db, _ := Open(":memory:") 1066 | db.CreateIndex("name", "*", IndexString) 1067 | db.Update(func(tx *Tx) error { 1068 | tx.Set("user:100:first", "Tom", nil) 1069 | tx.Set("user:100:last", "Johnson", nil) 1070 | tx.Set("user:101:first", "Janet", nil) 1071 | tx.Set("user:101:last", "Prichard", nil) 1072 | tx.Set("user:102:first", "Alan", nil) 1073 | tx.Set("user:102:last", "Cooper", nil) 1074 | return nil 1075 | }) 1076 | db.View(func(tx *Tx) error { 1077 | tx.AscendKeys("user:101:*", 1078 | func(key, value string) bool { 1079 | fmt.Printf("%s: %s\n", key, value) 1080 | return true 1081 | }) 1082 | tx.AscendKeys("user:10?:*", 1083 | func(key, value string) bool { 1084 | fmt.Printf("%s: %s\n", key, value) 1085 | return true 1086 | }) 1087 | tx.AscendKeys("*2*", 1088 | func(key, value string) bool { 1089 | fmt.Printf("%s: %s\n", key, value) 1090 | return true 1091 | }) 1092 | tx.DescendKeys("user:101:*", 1093 | func(key, value string) bool { 1094 | fmt.Printf("%s: %s\n", key, value) 1095 | return true 1096 | }) 1097 | tx.DescendKeys("*", 1098 | func(key, value string) bool { 1099 | fmt.Printf("%s: %s\n", key, value) 1100 | return true 1101 | }) 1102 | return nil 1103 | }) 1104 | // Output: 1105 | //user:101:first: Janet 1106 | //user:101:last: Prichard 1107 | //user:100:first: Tom 1108 | //user:100:last: Johnson 1109 | //user:101:first: Janet 1110 | //user:101:last: Prichard 1111 | //user:102:first: Alan 1112 | //user:102:last: Cooper 1113 | //user:102:first: Alan 1114 | //user:102:last: Cooper 1115 | //user:101:last: Prichard 1116 | //user:101:first: Janet 1117 | //user:102:last: Cooper 1118 | //user:102:first: Alan 1119 | //user:101:last: Prichard 1120 | //user:101:first: Janet 1121 | //user:100:last: Johnson 1122 | //user:100:first: Tom 1123 | } 1124 | 1125 | func ExampleDesc() { 1126 | db, _ := Open(":memory:") 1127 | db.CreateIndex("last_name_age", "*", IndexJSON("name.last"), Desc(IndexJSON("age"))) 1128 | db.Update(func(tx *Tx) error { 1129 | tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":38}`, nil) 1130 | tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":47}`, nil) 1131 | tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":52}`, nil) 1132 | tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":28}`, nil) 1133 | tx.Set("5", `{"name":{"first":"Sam","last":"Anderson"},"age":51}`, nil) 1134 | tx.Set("6", `{"name":{"first":"Melinda","last":"Prichard"},"age":44}`, nil) 1135 | return nil 1136 | }) 1137 | db.View(func(tx *Tx) error { 1138 | tx.Ascend("last_name_age", func(key, value string) bool { 1139 | fmt.Printf("%s: %s\n", key, value) 1140 | return true 1141 | }) 1142 | return nil 1143 | }) 1144 | 1145 | // Output: 1146 | //3: {"name":{"first":"Carol","last":"Anderson"},"age":52} 1147 | //5: {"name":{"first":"Sam","last":"Anderson"},"age":51} 1148 | //4: {"name":{"first":"Alan","last":"Cooper"},"age":28} 1149 | //1: {"name":{"first":"Tom","last":"Johnson"},"age":38} 1150 | //2: {"name":{"first":"Janet","last":"Prichard"},"age":47} 1151 | //6: {"name":{"first":"Melinda","last":"Prichard"},"age":44} 1152 | } 1153 | 1154 | func ExampleDB_CreateIndex_jSON() { 1155 | db, _ := Open(":memory:") 1156 | db.CreateIndex("last_name", "*", IndexJSON("name.last")) 1157 | db.CreateIndex("age", "*", IndexJSON("age")) 1158 | db.Update(func(tx *Tx) error { 1159 | tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":38}`, nil) 1160 | tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":47}`, nil) 1161 | tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":52}`, nil) 1162 | tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":28}`, nil) 1163 | return nil 1164 | }) 1165 | db.View(func(tx *Tx) error { 1166 | fmt.Println("Order by last name") 1167 | tx.Ascend("last_name", func(key, value string) bool { 1168 | fmt.Printf("%s: %s\n", key, value) 1169 | return true 1170 | }) 1171 | fmt.Println("Order by age") 1172 | tx.Ascend("age", func(key, value string) bool { 1173 | fmt.Printf("%s: %s\n", key, value) 1174 | return true 1175 | }) 1176 | fmt.Println("Order by age range 30-50") 1177 | tx.AscendRange("age", `{"age":30}`, `{"age":50}`, func(key, value string) bool { 1178 | fmt.Printf("%s: %s\n", key, value) 1179 | return true 1180 | }) 1181 | return nil 1182 | }) 1183 | 1184 | // Output: 1185 | // Order by last name 1186 | // 3: {"name":{"first":"Carol","last":"Anderson"},"age":52} 1187 | // 4: {"name":{"first":"Alan","last":"Cooper"},"age":28} 1188 | // 1: {"name":{"first":"Tom","last":"Johnson"},"age":38} 1189 | // 2: {"name":{"first":"Janet","last":"Prichard"},"age":47} 1190 | // Order by age 1191 | // 4: {"name":{"first":"Alan","last":"Cooper"},"age":28} 1192 | // 1: {"name":{"first":"Tom","last":"Johnson"},"age":38} 1193 | // 2: {"name":{"first":"Janet","last":"Prichard"},"age":47} 1194 | // 3: {"name":{"first":"Carol","last":"Anderson"},"age":52} 1195 | // Order by age range 30-50 1196 | // 1: {"name":{"first":"Tom","last":"Johnson"},"age":38} 1197 | // 2: {"name":{"first":"Janet","last":"Prichard"},"age":47} 1198 | } 1199 | 1200 | func ExampleDB_CreateIndex_strings() { 1201 | db, _ := Open(":memory:") 1202 | db.CreateIndex("name", "*", IndexString) 1203 | db.Update(func(tx *Tx) error { 1204 | tx.Set("1", "Tom", nil) 1205 | tx.Set("2", "Janet", nil) 1206 | tx.Set("3", "Carol", nil) 1207 | tx.Set("4", "Alan", nil) 1208 | tx.Set("5", "Sam", nil) 1209 | tx.Set("6", "Melinda", nil) 1210 | return nil 1211 | }) 1212 | db.View(func(tx *Tx) error { 1213 | tx.Ascend("name", func(key, value string) bool { 1214 | fmt.Printf("%s: %s\n", key, value) 1215 | return true 1216 | }) 1217 | return nil 1218 | }) 1219 | 1220 | // Output: 1221 | //4: Alan 1222 | //3: Carol 1223 | //2: Janet 1224 | //6: Melinda 1225 | //5: Sam 1226 | //1: Tom 1227 | } 1228 | 1229 | func ExampleDB_CreateIndex_ints() { 1230 | db, _ := Open(":memory:") 1231 | db.CreateIndex("age", "*", IndexInt) 1232 | db.Update(func(tx *Tx) error { 1233 | tx.Set("1", "30", nil) 1234 | tx.Set("2", "51", nil) 1235 | tx.Set("3", "16", nil) 1236 | tx.Set("4", "76", nil) 1237 | tx.Set("5", "23", nil) 1238 | tx.Set("6", "43", nil) 1239 | return nil 1240 | }) 1241 | db.View(func(tx *Tx) error { 1242 | tx.Ascend("age", func(key, value string) bool { 1243 | fmt.Printf("%s: %s\n", key, value) 1244 | return true 1245 | }) 1246 | return nil 1247 | }) 1248 | 1249 | // Output: 1250 | //3: 16 1251 | //5: 23 1252 | //1: 30 1253 | //6: 43 1254 | //2: 51 1255 | //4: 76 1256 | } 1257 | func ExampleDB_CreateIndex_multipleFields() { 1258 | db, _ := Open(":memory:") 1259 | db.CreateIndex("last_name_age", "*", IndexJSON("name.last"), IndexJSON("age")) 1260 | db.Update(func(tx *Tx) error { 1261 | tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":38}`, nil) 1262 | tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":47}`, nil) 1263 | tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":52}`, nil) 1264 | tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":28}`, nil) 1265 | tx.Set("5", `{"name":{"first":"Sam","last":"Anderson"},"age":51}`, nil) 1266 | tx.Set("6", `{"name":{"first":"Melinda","last":"Prichard"},"age":44}`, nil) 1267 | return nil 1268 | }) 1269 | db.View(func(tx *Tx) error { 1270 | tx.Ascend("last_name_age", func(key, value string) bool { 1271 | fmt.Printf("%s: %s\n", key, value) 1272 | return true 1273 | }) 1274 | return nil 1275 | }) 1276 | 1277 | // Output: 1278 | //5: {"name":{"first":"Sam","last":"Anderson"},"age":51} 1279 | //3: {"name":{"first":"Carol","last":"Anderson"},"age":52} 1280 | //4: {"name":{"first":"Alan","last":"Cooper"},"age":28} 1281 | //1: {"name":{"first":"Tom","last":"Johnson"},"age":38} 1282 | //6: {"name":{"first":"Melinda","last":"Prichard"},"age":44} 1283 | //2: {"name":{"first":"Janet","last":"Prichard"},"age":47} 1284 | } 1285 | 1286 | func TestNoExpiringItem(t *testing.T) { 1287 | item := &dbItem{key: "key", val: "val"} 1288 | if !item.expiresAt().Equal(maxTime) { 1289 | t.Fatal("item.expiresAt() != maxTime") 1290 | } 1291 | if min, max := item.Rect(nil); min != nil || max != nil { 1292 | t.Fatal("item min,max should both be nil") 1293 | } 1294 | } 1295 | func TestAutoShrink(t *testing.T) { 1296 | db := testOpen(t) 1297 | defer testClose(db) 1298 | for i := 0; i < 1000; i++ { 1299 | err := db.Update(func(tx *Tx) error { 1300 | for i := 0; i < 20; i++ { 1301 | if _, _, err := tx.Set(fmt.Sprintf("HELLO:%d", i), "WORLD", nil); err != nil { 1302 | return err 1303 | } 1304 | } 1305 | return nil 1306 | }) 1307 | if err != nil { 1308 | t.Fatal(err) 1309 | } 1310 | } 1311 | db = testReOpen(t, db) 1312 | defer testClose(db) 1313 | db.config.AutoShrinkMinSize = 64 * 1024 // 64K 1314 | for i := 0; i < 2000; i++ { 1315 | err := db.Update(func(tx *Tx) error { 1316 | for i := 0; i < 20; i++ { 1317 | if _, _, err := tx.Set(fmt.Sprintf("HELLO:%d", i), "WORLD", nil); err != nil { 1318 | return err 1319 | } 1320 | } 1321 | return nil 1322 | }) 1323 | if err != nil { 1324 | t.Fatal(err) 1325 | } 1326 | } 1327 | time.Sleep(time.Second * 3) 1328 | db = testReOpen(t, db) 1329 | defer testClose(db) 1330 | err := db.View(func(tx *Tx) error { 1331 | n, err := tx.Len() 1332 | if err != nil { 1333 | return err 1334 | } 1335 | if n != 20 { 1336 | t.Fatalf("expecting 20, got %v", n) 1337 | } 1338 | return nil 1339 | }) 1340 | if err != nil { 1341 | t.Fatal(err) 1342 | } 1343 | } 1344 | 1345 | // test database format loading 1346 | func TestDatabaseFormat(t *testing.T) { 1347 | // should succeed 1348 | func() { 1349 | resp := strings.Join([]string{ 1350 | "*3\r\n$3\r\nset\r\n$4\r\nvar1\r\n$4\r\n1234\r\n", 1351 | "*3\r\n$3\r\nset\r\n$4\r\nvar2\r\n$4\r\n1234\r\n", 1352 | "*2\r\n$3\r\ndel\r\n$4\r\nvar1\r\n", 1353 | "*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", 1354 | }, "") 1355 | if err := os.RemoveAll("data.db"); err != nil { 1356 | t.Fatal(err) 1357 | } 1358 | if err := ioutil.WriteFile("data.db", []byte(resp), 0666); err != nil { 1359 | t.Fatal(err) 1360 | } 1361 | db := testOpen(t) 1362 | defer testClose(db) 1363 | }() 1364 | testBadFormat := func(resp string) { 1365 | if err := os.RemoveAll("data.db"); err != nil { 1366 | t.Fatal(err) 1367 | } 1368 | if err := ioutil.WriteFile("data.db", []byte(resp), 0666); err != nil { 1369 | t.Fatal(err) 1370 | } 1371 | db, err := Open("data.db") 1372 | if err == nil { 1373 | if err := db.Close(); err != nil { 1374 | t.Fatal(err) 1375 | } 1376 | if err := os.RemoveAll("data.db"); err != nil { 1377 | t.Fatal(err) 1378 | } 1379 | t.Fatalf("invalid database should not be allowed") 1380 | } 1381 | } 1382 | testBadFormat("*3\r") 1383 | testBadFormat("*3\n") 1384 | testBadFormat("*a\r\n") 1385 | testBadFormat("*2\r\n") 1386 | testBadFormat("*2\r\n%3") 1387 | testBadFormat("*2\r\n$") 1388 | testBadFormat("*2\r\n$3\r\n") 1389 | testBadFormat("*2\r\n$3\r\ndel") 1390 | testBadFormat("*2\r\n$3\r\ndel\r\r") 1391 | testBadFormat("*0\r\n*2\r\n$3\r\ndel\r\r") 1392 | testBadFormat("*1\r\n$3\r\nnop\r\n") 1393 | testBadFormat("*1\r\n$3\r\ndel\r\n") 1394 | testBadFormat("*1\r\n$3\r\nset\r\n") 1395 | 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") 1396 | 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") 1397 | 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") 1398 | 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") 1399 | 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") 1400 | 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") 1401 | 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") 1402 | } 1403 | 1404 | func TestInsertsAndDeleted(t *testing.T) { 1405 | db := testOpen(t) 1406 | defer testClose(db) 1407 | if err := db.CreateIndex("any", "*", IndexString); err != nil { 1408 | t.Fatal(err) 1409 | } 1410 | if err := db.CreateSpatialIndex("rect", "*", IndexRect); err != nil { 1411 | t.Fatal(err) 1412 | } 1413 | if err := db.Update(func(tx *Tx) error { 1414 | if _, _, err := tx.Set("item1", "value1", &SetOptions{Expires: true, TTL: time.Second}); err != nil { 1415 | return err 1416 | } 1417 | if _, _, err := tx.Set("item2", "value2", nil); err != nil { 1418 | return err 1419 | } 1420 | if _, _, err := tx.Set("item3", "value3", &SetOptions{Expires: true, TTL: time.Second}); err != nil { 1421 | return err 1422 | } 1423 | return nil 1424 | }); err != nil { 1425 | t.Fatal(err) 1426 | } 1427 | 1428 | // test replacing items in the database 1429 | if err := db.Update(func(tx *Tx) error { 1430 | if _, _, err := tx.Set("item1", "nvalue1", nil); err != nil { 1431 | return err 1432 | } 1433 | if _, _, err := tx.Set("item2", "nvalue2", nil); err != nil { 1434 | return err 1435 | } 1436 | if _, err := tx.Delete("item3"); err != nil { 1437 | return err 1438 | } 1439 | return nil 1440 | }); err != nil { 1441 | t.Fatal(err) 1442 | } 1443 | } 1444 | 1445 | // test index compare functions 1446 | func TestIndexCompare(t *testing.T) { 1447 | if !IndexFloat("1.5", "1.6") { 1448 | t.Fatalf("expected true, got false") 1449 | } 1450 | if !IndexInt("-1", "2") { 1451 | t.Fatalf("expected true, got false") 1452 | } 1453 | if !IndexUint("10", "25") { 1454 | t.Fatalf("expected true, got false") 1455 | } 1456 | if !IndexBinary("Hello", "hello") { 1457 | t.Fatalf("expected true, got false") 1458 | } 1459 | if IndexString("hello", "hello") { 1460 | t.Fatalf("expected false, got true") 1461 | } 1462 | if IndexString("Hello", "hello") { 1463 | t.Fatalf("expected false, got true") 1464 | } 1465 | if IndexString("hello", "Hello") { 1466 | t.Fatalf("expected false, got true") 1467 | } 1468 | if !IndexString("gello", "Hello") { 1469 | t.Fatalf("expected true, got false") 1470 | } 1471 | if IndexString("Hello", "gello") { 1472 | t.Fatalf("expected false, got true") 1473 | } 1474 | if Rect(IndexRect("[1 2 3 4],[5 6 7 8]")) != "[1 2 3 4],[5 6 7 8]" { 1475 | t.Fatalf("expected '%v', got '%v'", "[1 2 3 4],[5 6 7 8]", Rect(IndexRect("[1 2 3 4],[5 6 7 8]"))) 1476 | } 1477 | if Rect(IndexRect("[1 2 3 4]")) != "[1 2 3 4]" { 1478 | t.Fatalf("expected '%v', got '%v'", "[1 2 3 4]", Rect(IndexRect("[1 2 3 4]"))) 1479 | } 1480 | if Rect(nil, nil) != "[]" { 1481 | t.Fatalf("expected '%v', got '%v'", "", Rect(nil, nil)) 1482 | } 1483 | if Point(1, 2, 3) != "[1 2 3]" { 1484 | t.Fatalf("expected '%v', got '%v'", "[1 2 3]", Point(1, 2, 3)) 1485 | } 1486 | } 1487 | 1488 | // test opening a folder. 1489 | func TestOpeningAFolder(t *testing.T) { 1490 | if err := os.RemoveAll("dir.tmp"); err != nil { 1491 | t.Fatal(err) 1492 | } 1493 | if err := os.Mkdir("dir.tmp", 0700); err != nil { 1494 | t.Fatal(err) 1495 | } 1496 | defer func() { _ = os.RemoveAll("dir.tmp") }() 1497 | db, err := Open("dir.tmp") 1498 | if err == nil { 1499 | if err := db.Close(); err != nil { 1500 | t.Fatal(err) 1501 | } 1502 | t.Fatalf("opening a directory should not be allowed") 1503 | } 1504 | } 1505 | 1506 | // test opening an invalid resp file. 1507 | func TestOpeningInvalidDatabaseFile(t *testing.T) { 1508 | if err := os.RemoveAll("data.db"); err != nil { 1509 | t.Fatal(err) 1510 | } 1511 | if err := ioutil.WriteFile("data.db", []byte("invalid\r\nfile"), 0666); err != nil { 1512 | t.Fatal(err) 1513 | } 1514 | defer func() { _ = os.RemoveAll("data.db") }() 1515 | db, err := Open("data.db") 1516 | if err == nil { 1517 | if err := db.Close(); err != nil { 1518 | t.Fatal(err) 1519 | } 1520 | t.Fatalf("invalid database should not be allowed") 1521 | } 1522 | } 1523 | 1524 | // test closing a closed database. 1525 | func TestOpeningClosedDatabase(t *testing.T) { 1526 | if err := os.RemoveAll("data.db"); err != nil { 1527 | t.Fatal(err) 1528 | } 1529 | db, err := Open("data.db") 1530 | if err != nil { 1531 | t.Fatal(err) 1532 | } 1533 | defer func() { _ = os.RemoveAll("data.db") }() 1534 | if err := db.Close(); err != nil { 1535 | t.Fatal(err) 1536 | } 1537 | if err := db.Close(); err != ErrDatabaseClosed { 1538 | t.Fatal("should not be able to close a closed database") 1539 | } 1540 | db, err = Open(":memory:") 1541 | if err != nil { 1542 | t.Fatal(err) 1543 | } 1544 | if err := db.Close(); err != nil { 1545 | t.Fatal(err) 1546 | } 1547 | if err := db.Close(); err != ErrDatabaseClosed { 1548 | t.Fatal("should not be able to close a closed database") 1549 | } 1550 | } 1551 | 1552 | // test shrinking a database. 1553 | func TestShrink(t *testing.T) { 1554 | db := testOpen(t) 1555 | defer testClose(db) 1556 | if err := db.Shrink(); err != nil { 1557 | t.Fatal(err) 1558 | } 1559 | fi, err := os.Stat("data.db") 1560 | if err != nil { 1561 | t.Fatal(err) 1562 | } 1563 | if fi.Size() != 0 { 1564 | t.Fatalf("expected %v, got %v", 0, fi.Size()) 1565 | } 1566 | // add 10 items 1567 | err = db.Update(func(tx *Tx) error { 1568 | for i := 0; i < 10; i++ { 1569 | if _, _, err := tx.Set(fmt.Sprintf("key%d", i), fmt.Sprintf("val%d", i), nil); err != nil { 1570 | return err 1571 | } 1572 | } 1573 | return nil 1574 | }) 1575 | if err != nil { 1576 | t.Fatal(err) 1577 | } 1578 | // add the same 10 items 1579 | // this will create 10 duplicate log entries 1580 | err = db.Update(func(tx *Tx) error { 1581 | for i := 0; i < 10; i++ { 1582 | if _, _, err := tx.Set(fmt.Sprintf("key%d", i), fmt.Sprintf("val%d", i), nil); err != nil { 1583 | return err 1584 | } 1585 | } 1586 | return nil 1587 | }) 1588 | if err != nil { 1589 | t.Fatal(err) 1590 | } 1591 | fi, err = os.Stat("data.db") 1592 | if err != nil { 1593 | t.Fatal(err) 1594 | } 1595 | sz1 := fi.Size() 1596 | if sz1 == 0 { 1597 | t.Fatalf("expected > 0, got %v", sz1) 1598 | } 1599 | if err := db.Shrink(); err != nil { 1600 | t.Fatal(err) 1601 | } 1602 | fi, err = os.Stat("data.db") 1603 | if err != nil { 1604 | t.Fatal(err) 1605 | } 1606 | sz2 := fi.Size() 1607 | if sz2 >= sz1 { 1608 | t.Fatalf("expected < %v, got %v", sz1, sz2) 1609 | } 1610 | if err := db.Close(); err != nil { 1611 | t.Fatal(err) 1612 | } 1613 | if err := db.Shrink(); err != ErrDatabaseClosed { 1614 | t.Fatal("shrink on a closed databse should not be allowed") 1615 | } 1616 | // Now we will open a db that does not persist 1617 | db, err = Open(":memory:") 1618 | if err != nil { 1619 | t.Fatal(err) 1620 | } 1621 | defer func() { _ = db.Close() }() 1622 | // add 10 items 1623 | err = db.Update(func(tx *Tx) error { 1624 | for i := 0; i < 10; i++ { 1625 | if _, _, err := tx.Set(fmt.Sprintf("key%d", i), fmt.Sprintf("val%d", i), nil); err != nil { 1626 | return err 1627 | } 1628 | } 1629 | return nil 1630 | }) 1631 | if err != nil { 1632 | t.Fatal(err) 1633 | } 1634 | // add the same 10 items 1635 | // this will create 10 duplicate log entries 1636 | err = db.Update(func(tx *Tx) error { 1637 | for i := 0; i < 10; i++ { 1638 | if _, _, err := tx.Set(fmt.Sprintf("key%d", i), fmt.Sprintf("val%d", i), nil); err != nil { 1639 | return err 1640 | } 1641 | } 1642 | return nil 1643 | }) 1644 | if err != nil { 1645 | t.Fatal(err) 1646 | } 1647 | err = db.View(func(tx *Tx) error { 1648 | n, err := tx.Len() 1649 | if err != nil { 1650 | t.Fatal(err) 1651 | } 1652 | if n != 10 { 1653 | t.Fatalf("expecting %v, got %v", 10, n) 1654 | } 1655 | return nil 1656 | }) 1657 | if err != nil { 1658 | t.Fatal(err) 1659 | } 1660 | // this should succeed even though it's basically a noop. 1661 | if err := db.Shrink(); err != nil { 1662 | t.Fatal(err) 1663 | } 1664 | } 1665 | 1666 | func TestVariousIndexOperations(t *testing.T) { 1667 | db := testOpen(t) 1668 | defer testClose(db) 1669 | // test creating an index with no index name. 1670 | err := db.CreateIndex("", "", nil) 1671 | if err == nil { 1672 | t.Fatal("should not be able to create an index with no name") 1673 | } 1674 | // test creating an index with a name that has already been used. 1675 | err = db.CreateIndex("hello", "", nil) 1676 | if err != nil { 1677 | t.Fatal(err) 1678 | } 1679 | err = db.CreateIndex("hello", "", nil) 1680 | if err == nil { 1681 | t.Fatal("should not be able to create a duplicate index") 1682 | } 1683 | err = db.Update(func(tx *Tx) error { 1684 | 1685 | if _, _, err := tx.Set("user:1", "tom", nil); err != nil { 1686 | return err 1687 | } 1688 | if _, _, err := tx.Set("user:2", "janet", nil); err != nil { 1689 | return err 1690 | } 1691 | if _, _, err := tx.Set("alt:1", "from", nil); err != nil { 1692 | return err 1693 | } 1694 | if _, _, err := tx.Set("alt:2", "there", nil); err != nil { 1695 | return err 1696 | } 1697 | if _, _, err := tx.Set("rect:1", "[1 2],[3 4]", nil); err != nil { 1698 | return err 1699 | } 1700 | if _, _, err := tx.Set("rect:2", "[5 6],[7 8]", nil); err != nil { 1701 | return err 1702 | } 1703 | return nil 1704 | }) 1705 | if err != nil { 1706 | t.Fatal(err) 1707 | } 1708 | // test creating an index after adding items. use pattern matching. have some items in the match and some not. 1709 | if err := db.CreateIndex("string", "user:*", IndexString); err != nil { 1710 | t.Fatal(err) 1711 | } 1712 | // test creating a spatial index after adding items. use pattern matching. have some items in the match and some not. 1713 | if err := db.CreateSpatialIndex("rect", "rect:*", IndexRect); err != nil { 1714 | t.Fatal(err) 1715 | } 1716 | // test dropping an index 1717 | if err := db.DropIndex("hello"); err != nil { 1718 | t.Fatal(err) 1719 | } 1720 | // test dropping an index with no name 1721 | if err := db.DropIndex(""); err == nil { 1722 | t.Fatal("should not be allowed to drop an index with no name") 1723 | } 1724 | // test dropping an index with no name 1725 | if err := db.DropIndex("na"); err == nil { 1726 | t.Fatal("should not be allowed to drop an index that does not exist") 1727 | } 1728 | // test retrieving index names 1729 | names, err := db.Indexes() 1730 | if err != nil { 1731 | t.Fatal(err) 1732 | } 1733 | if strings.Join(names, ",") != "rect,string" { 1734 | t.Fatalf("expecting '%v', got '%v'", "rect,string", strings.Join(names, ",")) 1735 | } 1736 | // test creating an index after closing database 1737 | if err := db.Close(); err != nil { 1738 | t.Fatal(err) 1739 | } 1740 | if err := db.CreateIndex("new-index", "", nil); err != ErrDatabaseClosed { 1741 | t.Fatal("should not be able to create an index on a closed database") 1742 | } 1743 | // test getting index names after closing database 1744 | if _, err := db.Indexes(); err != ErrDatabaseClosed { 1745 | t.Fatal("should not be able to get index names on a closed database") 1746 | } 1747 | // test dropping an index after closing database 1748 | if err := db.DropIndex("rect"); err != ErrDatabaseClosed { 1749 | t.Fatal("should not be able to drop an index on a closed database") 1750 | } 1751 | } 1752 | 1753 | func test(t *testing.T, a, b bool) { 1754 | if a != b { 1755 | t.Fatal("failed, bummer...") 1756 | } 1757 | } 1758 | 1759 | func TestBasic(t *testing.T) { 1760 | rand.Seed(time.Now().UnixNano()) 1761 | db := testOpen(t) 1762 | defer testClose(db) 1763 | 1764 | // create a simple index 1765 | if err := db.CreateIndex("users", "fun:user:*", IndexString); err != nil { 1766 | t.Fatal(err) 1767 | } 1768 | 1769 | // create a spatial index 1770 | if err := db.CreateSpatialIndex("rects", "rect:*", IndexRect); err != nil { 1771 | t.Fatal(err) 1772 | } 1773 | if true { 1774 | err := db.Update(func(tx *Tx) error { 1775 | if _, _, err := tx.Set("fun:user:0", "tom", nil); err != nil { 1776 | return err 1777 | } 1778 | if _, _, err := tx.Set("fun:user:1", "Randi", nil); err != nil { 1779 | return err 1780 | } 1781 | if _, _, err := tx.Set("fun:user:2", "jane", nil); err != nil { 1782 | return err 1783 | } 1784 | if _, _, err := tx.Set("fun:user:4", "Janet", nil); err != nil { 1785 | return err 1786 | } 1787 | if _, _, err := tx.Set("fun:user:5", "Paula", nil); err != nil { 1788 | return err 1789 | } 1790 | if _, _, err := tx.Set("fun:user:6", "peter", nil); err != nil { 1791 | return err 1792 | } 1793 | if _, _, err := tx.Set("fun:user:7", "Terri", nil); err != nil { 1794 | return err 1795 | } 1796 | return nil 1797 | }) 1798 | if err != nil { 1799 | t.Fatal(err) 1800 | } 1801 | // add some random items 1802 | start := time.Now() 1803 | if err := db.Update(func(tx *Tx) error { 1804 | for _, i := range rand.Perm(100) { 1805 | if _, _, err := tx.Set(fmt.Sprintf("tag:%d", i+100), fmt.Sprintf("val:%d", rand.Int()%100+100), nil); err != nil { 1806 | return err 1807 | } 1808 | } 1809 | return nil 1810 | }); err != nil { 1811 | t.Fatal(err) 1812 | } 1813 | if false { 1814 | println(time.Now().Sub(start).String(), db.keys.Len()) 1815 | } 1816 | // add some random rects 1817 | if err := db.Update(func(tx *Tx) error { 1818 | if _, _, err := tx.Set("rect:1", Rect([]float64{10, 10}, []float64{20, 20}), nil); err != nil { 1819 | return err 1820 | } 1821 | if _, _, err := tx.Set("rect:2", Rect([]float64{15, 15}, []float64{24, 24}), nil); err != nil { 1822 | return err 1823 | } 1824 | if _, _, err := tx.Set("rect:3", Rect([]float64{17, 17}, []float64{27, 27}), nil); err != nil { 1825 | return err 1826 | } 1827 | return nil 1828 | }); err != nil { 1829 | t.Fatal(err) 1830 | } 1831 | } 1832 | // verify the data has been created 1833 | buf := &bytes.Buffer{} 1834 | err := db.View(func(tx *Tx) error { 1835 | err := tx.Ascend("users", func(key, val string) bool { 1836 | fmt.Fprintf(buf, "%s %s\n", key, val) 1837 | return true 1838 | }) 1839 | if err != nil { 1840 | t.Fatal(err) 1841 | } 1842 | err = tx.AscendRange("", "tag:170", "tag:172", func(key, val string) bool { 1843 | fmt.Fprintf(buf, "%s\n", key) 1844 | return true 1845 | }) 1846 | if err != nil { 1847 | t.Fatal(err) 1848 | } 1849 | err = tx.AscendGreaterOrEqual("", "tag:195", func(key, val string) bool { 1850 | fmt.Fprintf(buf, "%s\n", key) 1851 | return true 1852 | }) 1853 | if err != nil { 1854 | t.Fatal(err) 1855 | } 1856 | err = tx.AscendGreaterOrEqual("", "rect:", func(key, val string) bool { 1857 | if !strings.HasPrefix(key, "rect:") { 1858 | return false 1859 | } 1860 | min, max := IndexRect(val) 1861 | fmt.Fprintf(buf, "%s: %v,%v\n", key, min, max) 1862 | return true 1863 | }) 1864 | expect := make([]string, 2) 1865 | n := 0 1866 | err = tx.Intersects("rects", "[0 0],[15 15]", func(key, val string) bool { 1867 | if n == 2 { 1868 | t.Fatalf("too many rects where received, expecting only two") 1869 | } 1870 | min, max := IndexRect(val) 1871 | s := fmt.Sprintf("%s: %v,%v\n", key, min, max) 1872 | if key == "rect:1" { 1873 | expect[0] = s 1874 | } else if key == "rect:2" { 1875 | expect[1] = s 1876 | } 1877 | n++ 1878 | return true 1879 | }) 1880 | if err != nil { 1881 | t.Fatal(err) 1882 | } 1883 | for _, s := range expect { 1884 | if _, err := buf.WriteString(s); err != nil { 1885 | return err 1886 | } 1887 | } 1888 | return nil 1889 | }) 1890 | if err != nil { 1891 | t.Fatal(err) 1892 | } 1893 | res := ` 1894 | fun:user:2 jane 1895 | fun:user:4 Janet 1896 | fun:user:5 Paula 1897 | fun:user:6 peter 1898 | fun:user:1 Randi 1899 | fun:user:7 Terri 1900 | fun:user:0 tom 1901 | tag:170 1902 | tag:171 1903 | tag:195 1904 | tag:196 1905 | tag:197 1906 | tag:198 1907 | tag:199 1908 | rect:1: [10 10],[20 20] 1909 | rect:2: [15 15],[24 24] 1910 | rect:3: [17 17],[27 27] 1911 | rect:1: [10 10],[20 20] 1912 | rect:2: [15 15],[24 24] 1913 | ` 1914 | res = strings.Replace(res, "\r", "", -1) 1915 | if strings.TrimSpace(buf.String()) != strings.TrimSpace(res) { 1916 | t.Fatalf("expected [%v], got [%v]", strings.TrimSpace(res), strings.TrimSpace(buf.String())) 1917 | } 1918 | } 1919 | 1920 | func TestIndexAscend(t *testing.T) { 1921 | rand.Seed(time.Now().UnixNano()) 1922 | db := testOpen(t) 1923 | defer testClose(db) 1924 | 1925 | // create a simple index 1926 | if err := db.CreateIndex("usr", "usr:*", IndexInt); err != nil { 1927 | t.Fatal(err) 1928 | } 1929 | if err := db.Update(func(tx *Tx) error { 1930 | for i := 10; i > 0; i-- { 1931 | tx.Set(fmt.Sprintf("usr:%d", i), fmt.Sprintf("%d", 10-i), nil) 1932 | } 1933 | return nil 1934 | }); err != nil { 1935 | t.Fatal(err) 1936 | } 1937 | 1938 | buf := &bytes.Buffer{} 1939 | err := db.View(func(tx *Tx) error { 1940 | tx.Ascend("usr", func(key, value string) bool { 1941 | fmt.Fprintf(buf, "%s %s\n", key, value) 1942 | return true 1943 | }) 1944 | fmt.Fprintln(buf) 1945 | 1946 | tx.AscendGreaterOrEqual("usr", "8", func(key, value string) bool { 1947 | fmt.Fprintf(buf, "%s %s\n", key, value) 1948 | return true 1949 | }) 1950 | fmt.Fprintln(buf) 1951 | 1952 | tx.AscendLessThan("usr", "3", func(key, value string) bool { 1953 | fmt.Fprintf(buf, "%s %s\n", key, value) 1954 | return true 1955 | }) 1956 | fmt.Fprintln(buf) 1957 | 1958 | tx.AscendRange("usr", "4", "8", func(key, value string) bool { 1959 | fmt.Fprintf(buf, "%s %s\n", key, value) 1960 | return true 1961 | }) 1962 | return nil 1963 | }) 1964 | 1965 | if err != nil { 1966 | t.Fatal(err) 1967 | } 1968 | 1969 | res := ` 1970 | usr:10 0 1971 | usr:9 1 1972 | usr:8 2 1973 | usr:7 3 1974 | usr:6 4 1975 | usr:5 5 1976 | usr:4 6 1977 | usr:3 7 1978 | usr:2 8 1979 | usr:1 9 1980 | 1981 | usr:2 8 1982 | usr:1 9 1983 | 1984 | usr:10 0 1985 | usr:9 1 1986 | usr:8 2 1987 | 1988 | usr:6 4 1989 | usr:5 5 1990 | usr:4 6 1991 | usr:3 7 1992 | ` 1993 | res = strings.Replace(res, "\r", "", -1) 1994 | s1 := strings.TrimSpace(buf.String()) 1995 | s2 := strings.TrimSpace(res) 1996 | if s1 != s2 { 1997 | t.Fatalf("expected [%v], got [%v]", s1, s2) 1998 | } 1999 | } 2000 | 2001 | func testRectStringer(min, max []float64) error { 2002 | nmin, nmax := IndexRect(Rect(min, max)) 2003 | if len(nmin) != len(min) { 2004 | return fmt.Errorf("rect=%v,%v, expect=%v,%v", nmin, nmax, min, max) 2005 | } 2006 | for i := 0; i < len(min); i++ { 2007 | if min[i] != nmin[i] || max[i] != nmax[i] { 2008 | return fmt.Errorf("rect=%v,%v, expect=%v,%v", nmin, nmax, min, max) 2009 | } 2010 | } 2011 | return nil 2012 | } 2013 | func TestRectStrings(t *testing.T) { 2014 | test(t, Rect(IndexRect(Point(1))) == "[1]", true) 2015 | test(t, Rect(IndexRect(Point(1, 2, 3, 4))) == "[1 2 3 4]", true) 2016 | test(t, Rect(IndexRect(Rect(IndexRect("[1 2],[1 2]")))) == "[1 2]", true) 2017 | test(t, Rect(IndexRect(Rect(IndexRect("[1 2],[2 2]")))) == "[1 2],[2 2]", true) 2018 | test(t, Rect(IndexRect(Rect(IndexRect("[1 2],[2 2],[3]")))) == "[1 2],[2 2]", true) 2019 | test(t, Rect(IndexRect(Rect(IndexRect("[1 2]")))) == "[1 2]", true) 2020 | test(t, Rect(IndexRect(Rect(IndexRect("[1.5 2 4.5 5.6]")))) == "[1.5 2 4.5 5.6]", true) 2021 | test(t, Rect(IndexRect(Rect(IndexRect("[1.5 2 4.5 5.6 -1],[]")))) == "[1.5 2 4.5 5.6 -1]", true) 2022 | test(t, Rect(IndexRect(Rect(IndexRect("[]")))) == "[]", true) 2023 | test(t, Rect(IndexRect(Rect(IndexRect("")))) == "[]", true) 2024 | if err := testRectStringer(nil, nil); err != nil { 2025 | t.Fatal(err) 2026 | } 2027 | if err := testRectStringer([]float64{}, []float64{}); err != nil { 2028 | t.Fatal(err) 2029 | } 2030 | if err := testRectStringer([]float64{1}, []float64{2}); err != nil { 2031 | t.Fatal(err) 2032 | } 2033 | if err := testRectStringer([]float64{1, 2}, []float64{3, 4}); err != nil { 2034 | t.Fatal(err) 2035 | } 2036 | if err := testRectStringer([]float64{1, 2, 3}, []float64{4, 5, 6}); err != nil { 2037 | t.Fatal(err) 2038 | } 2039 | if err := testRectStringer([]float64{1, 2, 3, 4}, []float64{5, 6, 7, 8}); err != nil { 2040 | t.Fatal(err) 2041 | } 2042 | if err := testRectStringer([]float64{1, 2, 3, 4, 5}, []float64{6, 7, 8, 9, 10}); err != nil { 2043 | t.Fatal(err) 2044 | } 2045 | } 2046 | 2047 | // TestTTLReOpen test setting a TTL and then immediately closing the database and 2048 | // then waiting the TTL before reopening. The key should not be accessible. 2049 | func TestTTLReOpen(t *testing.T) { 2050 | ttl := time.Second * 3 2051 | db := testOpen(t) 2052 | defer testClose(db) 2053 | err := db.Update(func(tx *Tx) error { 2054 | if _, _, err := tx.Set("key1", "val1", &SetOptions{Expires: true, TTL: ttl}); err != nil { 2055 | return err 2056 | } 2057 | return nil 2058 | }) 2059 | if err != nil { 2060 | t.Fatal(err) 2061 | } 2062 | db = testReOpenDelay(t, db, ttl/4) 2063 | err = db.View(func(tx *Tx) error { 2064 | val, err := tx.Get("key1") 2065 | if err != nil { 2066 | return err 2067 | } 2068 | if val != "val1" { 2069 | t.Fatalf("expecting '%v', got '%v'", "val1", val) 2070 | } 2071 | return nil 2072 | }) 2073 | if err != nil { 2074 | t.Fatal(err) 2075 | } 2076 | db = testReOpenDelay(t, db, ttl-ttl/4) 2077 | defer testClose(db) 2078 | err = db.View(func(tx *Tx) error { 2079 | val, err := tx.Get("key1") 2080 | if err == nil || err != ErrNotFound || val != "" { 2081 | t.Fatal("expecting not found") 2082 | } 2083 | 2084 | return nil 2085 | }) 2086 | if err != nil { 2087 | t.Fatal(err) 2088 | } 2089 | } 2090 | 2091 | func TestTTL(t *testing.T) { 2092 | db := testOpen(t) 2093 | defer testClose(db) 2094 | err := db.Update(func(tx *Tx) error { 2095 | if _, _, err := tx.Set("key1", "val1", &SetOptions{Expires: true, TTL: time.Second}); err != nil { 2096 | return err 2097 | } 2098 | if _, _, err := tx.Set("key2", "val2", nil); err != nil { 2099 | return err 2100 | } 2101 | return nil 2102 | }) 2103 | if err != nil { 2104 | t.Fatal(err) 2105 | } 2106 | err = db.View(func(tx *Tx) error { 2107 | dur1, err := tx.TTL("key1") 2108 | if err != nil { 2109 | t.Fatal(err) 2110 | } 2111 | if dur1 > time.Second || dur1 <= 0 { 2112 | t.Fatalf("expecting between zero and one second, got '%v'", dur1) 2113 | } 2114 | dur1, err = tx.TTL("key2") 2115 | if err != nil { 2116 | t.Fatal(err) 2117 | } 2118 | if dur1 >= 0 { 2119 | t.Fatalf("expecting a negative value, got '%v'", dur1) 2120 | } 2121 | return nil 2122 | }) 2123 | if err != nil { 2124 | t.Fatal(err) 2125 | } 2126 | } 2127 | 2128 | func TestConfig(t *testing.T) { 2129 | db := testOpen(t) 2130 | defer testClose(db) 2131 | 2132 | err := db.SetConfig(Config{SyncPolicy: SyncPolicy(-1)}) 2133 | if err == nil { 2134 | t.Fatal("expecting a config syncpolicy error") 2135 | } 2136 | err = db.SetConfig(Config{SyncPolicy: SyncPolicy(3)}) 2137 | if err == nil { 2138 | t.Fatal("expecting a config syncpolicy error") 2139 | } 2140 | err = db.SetConfig(Config{SyncPolicy: Never}) 2141 | if err != nil { 2142 | t.Fatal(err) 2143 | } 2144 | err = db.SetConfig(Config{SyncPolicy: EverySecond}) 2145 | if err != nil { 2146 | t.Fatal(err) 2147 | } 2148 | err = db.SetConfig(Config{AutoShrinkMinSize: 100, AutoShrinkPercentage: 200, SyncPolicy: Always}) 2149 | if err != nil { 2150 | t.Fatal(err) 2151 | } 2152 | 2153 | var c Config 2154 | if err := db.ReadConfig(&c); err != nil { 2155 | t.Fatal(err) 2156 | } 2157 | if c.AutoShrinkMinSize != 100 || c.AutoShrinkPercentage != 200 && c.SyncPolicy != Always { 2158 | t.Fatalf("expecting %v, %v, and %v, got %v, %v, and %v", 100, 200, Always, c.AutoShrinkMinSize, c.AutoShrinkPercentage, c.SyncPolicy) 2159 | } 2160 | } 2161 | func testUint64Hex(n uint64) string { 2162 | s := strconv.FormatUint(n, 16) 2163 | s = "0000000000000000" + s 2164 | return s[len(s)-16:] 2165 | } 2166 | func textHexUint64(s string) uint64 { 2167 | n, _ := strconv.ParseUint(s, 16, 64) 2168 | return n 2169 | } 2170 | func benchClose(t *testing.B, persist bool, db *DB) { 2171 | if persist { 2172 | if err := os.RemoveAll("data.db"); err != nil { 2173 | t.Fatal(err) 2174 | } 2175 | } 2176 | if err := db.Close(); err != nil { 2177 | t.Fatal(err) 2178 | } 2179 | } 2180 | 2181 | func benchOpenFillData(t *testing.B, N int, 2182 | set, persist, random bool, 2183 | geo bool, 2184 | batch int) (db *DB, keys, vals []string) { 2185 | /// 2186 | t.StopTimer() 2187 | rand.Seed(time.Now().UnixNano()) 2188 | var err error 2189 | if persist { 2190 | if err := os.RemoveAll("data.db"); err != nil { 2191 | t.Fatal(err) 2192 | } 2193 | db, err = Open("data.db") 2194 | } else { 2195 | db, err = Open(":memory:") 2196 | } 2197 | if err != nil { 2198 | t.Fatal(err) 2199 | } 2200 | keys = make([]string, N) 2201 | vals = make([]string, N) 2202 | perm := rand.Perm(N) 2203 | for i := 0; i < N; i++ { 2204 | if random && set { 2205 | keys[perm[i]] = testUint64Hex(uint64(i)) 2206 | vals[perm[i]] = strconv.FormatInt(rand.Int63()%1000+1000, 10) 2207 | } else { 2208 | keys[i] = testUint64Hex(uint64(i)) 2209 | vals[i] = strconv.FormatInt(rand.Int63()%1000+1000, 10) 2210 | } 2211 | } 2212 | if set { 2213 | t.StartTimer() 2214 | } 2215 | for i := 0; i < N; { 2216 | err := db.Update(func(tx *Tx) error { 2217 | var err error 2218 | for j := 0; j < batch && i < N; j++ { 2219 | _, _, err = tx.Set(keys[i], vals[i], nil) 2220 | i++ 2221 | } 2222 | return err 2223 | }) 2224 | if err != nil { 2225 | t.Fatal(err) 2226 | } 2227 | } 2228 | if set { 2229 | t.StopTimer() 2230 | } 2231 | var n uint64 2232 | err = db.View(func(tx *Tx) error { 2233 | err := tx.Ascend("", func(key, value string) bool { 2234 | n2 := textHexUint64(key) 2235 | if n2 != n { 2236 | t.Fatalf("expecting '%v', got '%v'", n2, n) 2237 | } 2238 | n++ 2239 | return true 2240 | }) 2241 | return err 2242 | }) 2243 | if err != nil { 2244 | t.Fatal(err) 2245 | } 2246 | if n != uint64(N) { 2247 | t.Fatalf("expecting '%v', got '%v'", N, n) 2248 | } 2249 | t.StartTimer() 2250 | return db, keys, vals 2251 | } 2252 | 2253 | func benchSetGet(t *testing.B, set, persist, random bool, batch int) { 2254 | N := t.N 2255 | for N > 0 { 2256 | n := 0 2257 | if N >= 100000 { 2258 | n = 100000 2259 | } else { 2260 | n = N 2261 | } 2262 | func() { 2263 | db, keys, _ := benchOpenFillData(t, n, set, persist, random, false, batch) 2264 | defer benchClose(t, persist, db) 2265 | if !set { 2266 | for i := 0; i < n; { 2267 | err := db.View(func(tx *Tx) error { 2268 | var err error 2269 | for j := 0; j < batch && i < n; j++ { 2270 | _, err = tx.Get(keys[i]) 2271 | i++ 2272 | } 2273 | return err 2274 | }) 2275 | if err != nil { 2276 | t.Fatal(err) 2277 | } 2278 | } 2279 | } 2280 | }() 2281 | N -= n 2282 | } 2283 | } 2284 | 2285 | // Set Persist 2286 | func Benchmark_Set_Persist_Random_1(t *testing.B) { 2287 | benchSetGet(t, true, true, true, 1) 2288 | } 2289 | func Benchmark_Set_Persist_Random_10(t *testing.B) { 2290 | benchSetGet(t, true, true, true, 10) 2291 | } 2292 | func Benchmark_Set_Persist_Random_100(t *testing.B) { 2293 | benchSetGet(t, true, true, true, 100) 2294 | } 2295 | func Benchmark_Set_Persist_Sequential_1(t *testing.B) { 2296 | benchSetGet(t, true, true, false, 1) 2297 | } 2298 | func Benchmark_Set_Persist_Sequential_10(t *testing.B) { 2299 | benchSetGet(t, true, true, false, 10) 2300 | } 2301 | func Benchmark_Set_Persist_Sequential_100(t *testing.B) { 2302 | benchSetGet(t, true, true, false, 100) 2303 | } 2304 | 2305 | // Set NoPersist 2306 | func Benchmark_Set_NoPersist_Random_1(t *testing.B) { 2307 | benchSetGet(t, true, false, true, 1) 2308 | } 2309 | func Benchmark_Set_NoPersist_Random_10(t *testing.B) { 2310 | benchSetGet(t, true, false, true, 10) 2311 | } 2312 | func Benchmark_Set_NoPersist_Random_100(t *testing.B) { 2313 | benchSetGet(t, true, false, true, 100) 2314 | } 2315 | func Benchmark_Set_NoPersist_Sequential_1(t *testing.B) { 2316 | benchSetGet(t, true, false, false, 1) 2317 | } 2318 | func Benchmark_Set_NoPersist_Sequential_10(t *testing.B) { 2319 | benchSetGet(t, true, false, false, 10) 2320 | } 2321 | func Benchmark_Set_NoPersist_Sequential_100(t *testing.B) { 2322 | benchSetGet(t, true, false, false, 100) 2323 | } 2324 | 2325 | // Get 2326 | func Benchmark_Get_1(t *testing.B) { 2327 | benchSetGet(t, false, false, false, 1) 2328 | } 2329 | func Benchmark_Get_10(t *testing.B) { 2330 | benchSetGet(t, false, false, false, 10) 2331 | } 2332 | func Benchmark_Get_100(t *testing.B) { 2333 | benchSetGet(t, false, false, false, 100) 2334 | } 2335 | 2336 | func benchScan(t *testing.B, asc bool, count int) { 2337 | N := count 2338 | db, _, _ := benchOpenFillData(t, N, false, false, false, false, 100) 2339 | defer benchClose(t, false, db) 2340 | for i := 0; i < t.N; i++ { 2341 | count := 0 2342 | err := db.View(func(tx *Tx) error { 2343 | if asc { 2344 | return tx.Ascend("", func(key, val string) bool { 2345 | count++ 2346 | return true 2347 | }) 2348 | } 2349 | return tx.Descend("", func(key, val string) bool { 2350 | count++ 2351 | return true 2352 | }) 2353 | 2354 | }) 2355 | if err != nil { 2356 | t.Fatal(err) 2357 | } 2358 | if count != N { 2359 | t.Fatalf("expecting '%v', got '%v'", N, count) 2360 | } 2361 | } 2362 | } 2363 | 2364 | func Benchmark_Ascend_1(t *testing.B) { 2365 | benchScan(t, true, 1) 2366 | } 2367 | func Benchmark_Ascend_10(t *testing.B) { 2368 | benchScan(t, true, 10) 2369 | } 2370 | func Benchmark_Ascend_100(t *testing.B) { 2371 | benchScan(t, true, 100) 2372 | } 2373 | func Benchmark_Ascend_1000(t *testing.B) { 2374 | benchScan(t, true, 1000) 2375 | } 2376 | func Benchmark_Ascend_10000(t *testing.B) { 2377 | benchScan(t, true, 10000) 2378 | } 2379 | 2380 | func Benchmark_Descend_1(t *testing.B) { 2381 | benchScan(t, false, 1) 2382 | } 2383 | func Benchmark_Descend_10(t *testing.B) { 2384 | benchScan(t, false, 10) 2385 | } 2386 | func Benchmark_Descend_100(t *testing.B) { 2387 | benchScan(t, false, 100) 2388 | } 2389 | func Benchmark_Descend_1000(t *testing.B) { 2390 | benchScan(t, false, 1000) 2391 | } 2392 | func Benchmark_Descend_10000(t *testing.B) { 2393 | benchScan(t, false, 10000) 2394 | } 2395 | 2396 | /* 2397 | func Benchmark_Spatial_2D(t *testing.B) { 2398 | N := 100000 2399 | db, _, _ := benchOpenFillData(t, N, true, true, false, true, 100) 2400 | defer benchClose(t, false, db) 2401 | 2402 | } 2403 | */ 2404 | func TestCoverCloseAlreadyClosed(t *testing.T) { 2405 | db := testOpen(t) 2406 | defer testClose(db) 2407 | _ = db.file.Close() 2408 | if err := db.Close(); err == nil { 2409 | t.Fatal("expecting an error") 2410 | } 2411 | } 2412 | 2413 | func TestCoverConfigClosed(t *testing.T) { 2414 | db := testOpen(t) 2415 | defer testClose(db) 2416 | _ = db.Close() 2417 | var config Config 2418 | if err := db.ReadConfig(&config); err != ErrDatabaseClosed { 2419 | t.Fatal("expecting database closed error") 2420 | } 2421 | if err := db.SetConfig(config); err != ErrDatabaseClosed { 2422 | t.Fatal("expecting database closed error") 2423 | } 2424 | } 2425 | func TestCoverShrinkShrink(t *testing.T) { 2426 | db := testOpen(t) 2427 | defer testClose(db) 2428 | if err := db.Update(func(tx *Tx) error { 2429 | for i := 0; i < 10000; i++ { 2430 | _, _, err := tx.Set(fmt.Sprintf("%d", i), fmt.Sprintf("%d", i), nil) 2431 | if err != nil { 2432 | return err 2433 | } 2434 | } 2435 | return nil 2436 | }); err != nil { 2437 | t.Fatal(err) 2438 | } 2439 | if err := db.Update(func(tx *Tx) error { 2440 | for i := 250; i < 250+100; i++ { 2441 | _, err := tx.Delete(fmt.Sprintf("%d", i)) 2442 | if err != nil { 2443 | return err 2444 | } 2445 | } 2446 | return nil 2447 | }); err != nil { 2448 | t.Fatal(err) 2449 | } 2450 | var err1, err2 error 2451 | var wg sync.WaitGroup 2452 | wg.Add(2) 2453 | go func() { 2454 | defer wg.Done() 2455 | err1 = db.Shrink() 2456 | }() 2457 | go func() { 2458 | defer wg.Done() 2459 | err2 = db.Shrink() 2460 | }() 2461 | wg.Wait() 2462 | //println(123) 2463 | //fmt.Printf("%v\n%v\n", err1, err2) 2464 | if err1 != ErrShrinkInProcess && err2 != ErrShrinkInProcess { 2465 | t.Fatal("expecting a shrink in process error") 2466 | } 2467 | db = testReOpen(t, db) 2468 | defer testClose(db) 2469 | if err := db.View(func(tx *Tx) error { 2470 | n, err := tx.Len() 2471 | if err != nil { 2472 | return err 2473 | } 2474 | if n != 9900 { 2475 | t.Fatal("expecting 9900 items") 2476 | } 2477 | return nil 2478 | }); err != nil { 2479 | t.Fatal(err) 2480 | } 2481 | } 2482 | 2483 | func TestPreviousItem(t *testing.T) { 2484 | db := testOpen(t) 2485 | defer testClose(db) 2486 | err := db.Update(func(tx *Tx) error { 2487 | _, _, err := tx.Set("hello", "world", nil) 2488 | if err != nil { 2489 | return err 2490 | } 2491 | prev, replaced, err := tx.Set("hello", "planet", nil) 2492 | if err != nil { 2493 | return err 2494 | } 2495 | if !replaced { 2496 | t.Fatal("should be replaced") 2497 | } 2498 | if prev != "world" { 2499 | t.Fatalf("expecting '%v', got '%v'", "world", prev) 2500 | } 2501 | return nil 2502 | }) 2503 | if err != nil { 2504 | t.Fatal(err) 2505 | } 2506 | } 2507 | 2508 | func TestJSONIndex(t *testing.T) { 2509 | db := testOpen(t) 2510 | defer testClose(db) 2511 | 2512 | _ = db.CreateIndex("last_name", "*", IndexJSON("name.last")) 2513 | _ = db.CreateIndex("last_name_cs", "*", IndexJSONCaseSensitive("name.last")) 2514 | _ = db.CreateIndex("age", "*", IndexJSON("age")) 2515 | _ = db.CreateIndex("student", "*", IndexJSON("student")) 2516 | _ = db.Update(func(tx *Tx) error { 2517 | _, _, _ = tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":38,"student":false}`, nil) 2518 | _, _, _ = tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":47,"student":true}`, nil) 2519 | _, _, _ = tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":52,"student":true}`, nil) 2520 | _, _, _ = tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":28,"student":false}`, nil) 2521 | _, _, _ = tx.Set("5", `{"name":{"first":"bill","last":"frank"},"age":21,"student":true}`, nil) 2522 | _, _, _ = tx.Set("6", `{"name":{"first":"sally","last":"randall"},"age":68,"student":false}`, nil) 2523 | return nil 2524 | }) 2525 | var keys []string 2526 | _ = db.View(func(tx *Tx) error { 2527 | _ = tx.Ascend("last_name_cs", func(key, value string) bool { 2528 | //fmt.Printf("%s: %s\n", key, value) 2529 | keys = append(keys, key) 2530 | return true 2531 | }) 2532 | _ = tx.Ascend("last_name", func(key, value string) bool { 2533 | //fmt.Printf("%s: %s\n", key, value) 2534 | keys = append(keys, key) 2535 | return true 2536 | }) 2537 | _ = tx.Ascend("age", func(key, value string) bool { 2538 | //fmt.Printf("%s: %s\n", key, value) 2539 | keys = append(keys, key) 2540 | return true 2541 | }) 2542 | _ = tx.Ascend("student", func(key, value string) bool { 2543 | //fmt.Printf("%s: %s\n", key, value) 2544 | keys = append(keys, key) 2545 | return true 2546 | }) 2547 | return nil 2548 | }) 2549 | 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" 2550 | if strings.Join(keys, ",") != expect { 2551 | t.Fatalf("expected %v, got %v", expect, strings.Join(keys, ",")) 2552 | } 2553 | } 2554 | 2555 | func TestOnExpiredSync(t *testing.T) { 2556 | db := testOpen(t) 2557 | defer testClose(db) 2558 | 2559 | var config Config 2560 | if err := db.ReadConfig(&config); err != nil { 2561 | t.Fatal(err) 2562 | } 2563 | hits := make(chan int, 3) 2564 | config.OnExpiredSync = func(key, value string, tx *Tx) error { 2565 | n, err := strconv.Atoi(value) 2566 | if err != nil { 2567 | return err 2568 | } 2569 | defer func() { hits <- n }() 2570 | if n >= 2 { 2571 | _, err = tx.Delete(key) 2572 | if err != ErrNotFound { 2573 | return err 2574 | } 2575 | return nil 2576 | } 2577 | n++ 2578 | _, _, err = tx.Set(key, strconv.Itoa(n), &SetOptions{Expires: true, TTL: time.Millisecond * 100}) 2579 | return err 2580 | } 2581 | if err := db.SetConfig(config); err != nil { 2582 | t.Fatal(err) 2583 | } 2584 | err := db.Update(func(tx *Tx) error { 2585 | _, _, err := tx.Set("K", "0", &SetOptions{Expires: true, TTL: time.Millisecond * 100}) 2586 | return err 2587 | }) 2588 | if err != nil { 2589 | t.Fail() 2590 | } 2591 | 2592 | done := make(chan struct{}) 2593 | go func() { 2594 | ticks := time.NewTicker(time.Millisecond * 50) 2595 | defer ticks.Stop() 2596 | for { 2597 | select { 2598 | case <-done: 2599 | return 2600 | case <-ticks.C: 2601 | err := db.View(func(tx *Tx) error { 2602 | v, err := tx.Get("K", true) 2603 | if err != nil { 2604 | return err 2605 | } 2606 | n, err := strconv.Atoi(v) 2607 | if err != nil { 2608 | return err 2609 | } 2610 | if n < 0 || n > 2 { 2611 | t.Fail() 2612 | } 2613 | return nil 2614 | }) 2615 | if err != nil { 2616 | t.Fail() 2617 | } 2618 | } 2619 | } 2620 | }() 2621 | 2622 | OUTER1: 2623 | for { 2624 | select { 2625 | case <-time.After(time.Second * 2): 2626 | t.Fail() 2627 | case v := <-hits: 2628 | if v >= 2 { 2629 | break OUTER1 2630 | } 2631 | } 2632 | } 2633 | err = db.View(func(tx *Tx) error { 2634 | defer close(done) 2635 | v, err := tx.Get("K") 2636 | if err != nil { 2637 | t.Fail() 2638 | return err 2639 | } 2640 | if v != "2" { 2641 | t.Fail() 2642 | } 2643 | return nil 2644 | }) 2645 | if err != nil { 2646 | t.Fail() 2647 | } 2648 | } 2649 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzhang08/buntdb/6249481c29c2cd96f53b691b74ac1893f72774c2/logo.png --------------------------------------------------------------------------------