zoom

package module
Version: v0.19.1 Latest Latest
Warning

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

Go to latest
Published: Mar 14, 2018 License: MIT Imports: 23 Imported by: 10

README

Zoom

Version Circle CI GoDoc

A blazing-fast datastore and querying engine for Go built on Redis.

Requires Redis version >= 2.8.9 and Go version >= 1.2. The latest version of both is recommended.

Full documentation is available on godoc.org.

Table of Contents

Development Status

Zoom was first started in 2013. It is well-tested and going forward the API will be relatively stable. We are closing in on Version 1.0.

At this time, Zoom can be considered safe for use in low-traffic production applications. However, as with any relatively new package, it is possible that there are some undiscovered bugs. Therefore we would recommend writing good tests, reporting any bugs you may find, and avoiding using Zoom for mission-critical or high-traffic applications.

Zoom follows semantic versioning, but offers no guarantees of backwards compatibility until version 1.0. We recommend using a dependency manager such as godep or glide to lock in a specific version of Zoom. You can also keep an eye on the Releases page to see a full changelog for each release. In addition, starting with version 0.9.0, migration guides will be provided for any non-trivial breaking changes, making it easier to stay up to date with the latest version.

When is Zoom a Good Fit?

Zoom might be a good fit if:

  1. You are building a low-latency application. Because Zoom is built on top of Redis and all data is stored in memory, it is typically much faster than datastores/ORMs based on traditional SQL databases. Latency will be the most noticeable difference, although throughput may also be improved.
  2. You want more out of Redis. Zoom offers a number of features that you don't get by using a Redis driver directly. For example, Zoom supports a larger number of types out of the box (including custom types, slices, maps, complex types, and embedded structs), provides tools for making multi-command transactions easier, and of course, provides the ability to run queries.
  3. You want an easy-to-use datastore. Zoom has a simple API and is arguably easier to use than some ORMs. For example, it doesn't require database migrations and instead builds up a schema based on your struct types. Zoom also does not typically require any knowledge of Redis in order to use effectively. Just connect it to a database and you're good to go!

Zoom might not be a good fit if:

  1. You are working with a lot of data. Redis is an in-memory database, and Zoom does not yet support sharding or Redis Cluster. Memory could be a hard constraint for larger applications. Keep in mind that it is possible (if expensive) to run Redis on machines with up to 256GB of memory on cloud providers such as Amazon EC2.
  2. You need advanced queries. Zoom currently only provides support for basic queries and is not as powerful or flexible as something like SQL. For example, Zoom currently lacks the equivalent of the IN or OR SQL keywords. See the documentation for a full list of the types of queries supported.

Installation

Zoom is powered by Redis and needs to connect to a Redis database. You can install Redis on the same machine that Zoom runs on, connect to a remote database, or even use a Redis-as-a-service provider such as Redis To Go, RedisLabs, Google Cloud Redis, or Amazon Elasticache.

If you need to install Redis, see the installation instructions on the official Redis website.

To install Zoom itself, run go get -u github.com/albrow/zoom to pull down the current master branch, or install with the dependency manager of your choice to lock in a specific version.

Initialization

First, add github.com/albrow/zoom to your import statement:

import (
	 // ...
	 github.com/albrow/zoom
)

Then, you must create a new pool with NewPool. A pool represents a pool of connections to the database. Since you may need access to the pool in different parts of your application, it is sometimes a good idea to declare a top-level variable and then initialize it in the main or init function. You must also call pool.Close when your application exits, so it's a good idea to use defer.

var pool *zoom.Pool

func main() {
	pool = zoom.NewPool("localhost:6379")
	defer func() {
		if err := pool.Close(); err != nil {
			// handle error
		}
	}()
	// ...
}

The NewPool function accepts an address which will be used to connect to Redis, and it will use all the default values for the other options. If you need to specify different options, you can use the NewPoolWithOptions function.

For convenience, the PoolOptions type has chainable methods for changing each option. Typically you would start with DefaultOptions and call WithX to change value for option X.

For example, here's how you could initialize a Pool that connects to Redis using a unix socket connection on /tmp/unix.sock:

options := zoom.DefaultPoolOptions.WithNetwork("unix").WithAddress("/tmp/unix.sock")
pool = zoom.NewPoolWithOptions(options)

Models

What is a Model?

Models in Zoom are just structs which implement the zoom.Model interface:

type Model interface {
  ModelID() string
  SetModelID(string)
}

To clarify, all you have to do to implement the Model interface is add a getter and setter for a unique id property.

If you want, you can embed zoom.RandomID to give your model all the required methods. A struct with zoom.RandomID embedded will generate a pseudo-random id for itself the first time the ModelID method is called iff it does not already have an id. The pseudo-randomly generated id consists of the current UTC unix time with second precision, an incremented atomic counter, a unique machine identifier, and an additional random string of characters. With ids generated this way collisions are extremely unlikely.

Future versions of Zoom may provide additional id implementations out of the box, e.g. one that assigns auto-incremented ids. You are also free to write your own id implementation as long as it satisfies the interface.

A struct definition serves as a sort of schema for your model. Here's an example of a model for a person:

type Person struct {
	 Name string
	 Age  int
	 zoom.RandomID
}

Because of the way Zoom uses reflection, all the fields you want to save need to be exported. Unexported fields (including unexported embedded structs with exported fields) will not be saved. This is a departure from how the encoding/json and encoding/xml packages behave. See issue #25 for discussion.

Almost any type of field is supported, including custom types, slices, maps, complex types, and embedded structs. The only things that are not supported are recursive data structures and functions.

Customizing Field Names

You can change the name used to store the field in Redis with the redis:"<name>" struct tag. So for example, if you wanted the fields to be stored as lowercase fields in Redis, you could use the following struct definition:

type Person struct {
	 Name string    `redis:"name"`
	 Age  int       `redis:"age"`
	 zoom.RandomID
}

If you don't want a field to be saved in Redis at all, you can use the special struct tag redis:"-".

Creating Collections

You must create a Collection for each type of model you want to save. A Collection is simply a set of all models of a specific type and has methods for saving, finding, deleting, and querying those models. NewCollection examines the type of a model and uses reflection to build up an internal schema. You only need to call NewCollection once per type. Each pool keeps track of its own collections, so if you wish to share a model type between two or more pools, you will need to create a collection for each pool.

// Create a new collection for the Person type.
People, err := pool.NewCollection(&Person{})
if err != nil {
	 // handle error
}

The convention is to name the Collection the plural of the corresponding model type (e.g. "People"), but it's just a variable so you can name it whatever you want.

NewCollection will use all the default options for the collection.

If you need to specify other options, use the NewCollectionWithOptions function. The second argument to NewCollectionWithOptions is a CollectionOptions. It works similarly to PoolOptions, so you can start with DefaultCollectionOptions and use the chainable WithX methods to specify a new value for option X.

Here's an example of how to create a new Collection which is indexed, allowing you to use Queries and methods like FindAll which rely on collection indexing:

options := zoom.DefaultCollectionOptions.WithIndex(true)
People, err = pool.NewCollectionWithOptions(&Person{}, options)
if err != nil {
	// handle error
}

There are a few important points to emphasize concerning collections:

  1. The collection name cannot contain a colon.
  2. Queries, as well as the FindAll, DeleteAll, and Count methods will not work if Index is false. This may change in future versions.

If you need to access a Collection in different parts of your application, it is sometimes a good idea to declare a top-level variable and then initialize it in the init function:

var (
	People *zoom.Collection
)

func init() {
	var err error
	// Assuming pool and Person are already defined.
	People, err = pool.NewCollection(&Person{})
	if err != nil {
		// handle error
	}
}
Saving Models

Continuing from the previous example, to persistently save a Person model to the database, we use the People.Save method. Recall that in this example, "People" is just the name we gave to the Collection which corresponds to the model type Person.

p := &Person{Name: "Alice", Age: 27}
if err := People.Save(p); err != nil {
	 // handle error
}

When you call Save, Zoom converts all the fields of the model into a format suitable for Redis and stores them as a Redis hash. There is a wiki page describing how zoom works under the hood in more detail.

Updating Models

Sometimes, it is preferable to only update certain fields of the model instead of saving them all again. It is more efficient and in some scenarios can allow safer simultaneous changes to the same model (as long as no two clients update the same field at the same time). In such cases, you can use UpdateFields.

if err := People.UpdateFields([]string{"Name"}, person); err != nil {
	// handle error
}

UpdateFields uses "last write wins" semantics, so if another caller updates the same field, your changes may be overwritten. That means it is not safe for "read before write" updates. See the section on Concurrent Updates for more information.

Finding a Single Model

To retrieve a model by id, use the Find method:

p := &Person{}
if err := People.Find("a_valid_person_id", p); err != nil {
	 // handle error
}

The second argument to Find must be a pointer to a struct which satisfies Model, and must have a type corresponding to the Collection. In this case, we passed in Person since that is the struct type that corresponds to our People collection. Find will mutate p by setting all its fields. Using Find in this way allows the caller to maintain type safety and avoid type casting. If Zoom couldn't find a model of type Person with the given id, it will return a ModelNotFoundError.

Finding Only Certain Fields

If you only want to find certain fields in the model instead of retrieving all of them, you can use FindFields, which works similarly to UpdateFields.

p := &Person{}
if err := People.FindFields("a_valid_person_id", []string{"Name"}, p); err != nil {
	// handle error
}
fmt.Println(p.Name, p.Age)
// Output:
// Alice 0

Fields that are not included in the given field names will not be mutated. In the above example, p.Age is 0 because p was just initialized and that's the zero value for the int type.

Finding All Models

To find all models of a given type, use the FindAll method:

people := []*Person{}
if err := People.FindAll(&people); err != nil {
	 // handle error
}

FindAll expects a pointer to a slice of some registered type that implements Model. It grows or shrinks the slice as needed, filling in all the fields of the elements inside of the slice. So the result of the call is that people will be a slice of all models in the People collection.

FindAll only works on indexed collections. To index a collection, you need to include Index: true in the CollectionOptions.

Deleting Models

To delete a model, use the Delete method:

// ok will be true iff a model with the given id existed and was deleted
if ok, err := People.Delete("a_valid_person_id"); err != nil {
	// handle err
}

Delete expects a valid id as an argument, and will attempt to delete the model with the given id. If there was no model with the given type and id, the first return value will be false.

You can also delete all models in a collection with the DeleteAll method:

numDeleted, err := People.DeleteAll()
if err != nil {
  // handle error
}

DeleteAll will return the number of models that were successfully deleted. DeleteAll only works on indexed collections. To index a collection, you need to include Index: true in the CollectionOptions.

Counting the Number of Models

You can get the number of models in a collection using the Count method:

count, err := People.Count()
if err != nil {
  // handle err
}

Count only works on indexed collections. To index a collection, you need to include Index: true in the CollectionOptions.

Transactions

Zoom exposes a transaction API which you can use to run multiple commands efficiently and atomically. Under the hood, Zoom uses a single Redis transaction to perform all the commands in a single round trip. Transactions feature delayed execution, so nothing touches the database until you call Exec. A transaction also remembers its errors to make error handling easier on the caller. The first error that occurs (if any) will be returned when you call Exec.

Here's an example of how to save two models and get the new number of models in the People collection in a single transaction.

numPeople := 0
t := pool.NewTransaction()
t.Save(People, &Person{Name: "Foo"})
t.Save(People, &Person{Name: "Bar"})
// Count expects a pointer to an integer, which it will change the value of
// when the transaction is executed.
t.Count(People, &numPeople)
if err := t.Exec(); err != nil {
  // handle error
}
// numPeople will now equal the number of `Person` models in the database
fmt.Println(numPeople)
// Output:
// 2

You can execute custom Redis commands or run custom Lua scripts inside a Transaction using the Command and Script methods. Both methods expect a ReplyHandler as an argument. A ReplyHandler is simply a function that will do something with the reply from Redis. ReplyHandler's are executed in order when you call Exec.

Right out of the box, Zoom exports a few useful ReplyHandlers. These include handlers for the primitive types int, string, bool, and float64, as well as handlers for scanning a reply into a Model or a slice of Models. You can also write your own custom ReplyHandlers if needed.

Queries

The Query Object

Zoom provides a useful abstraction for querying the database. You create queries by using the NewQuery constructor, where you must pass in the name corresponding to the type of model you want to query. For now, Zoom only supports queries on a single collection at a time.

You can add one or more query modifiers to the query, such as Order, Limit, and Filter. These methods return the query itself, so you can chain them together. The first error (if any) that occurs due to invalid arguments in the query modifiers will be remembered and returned when you attempt to run the query.

Finally, you run the query using a query finisher method, such as Run or Count. Queries feature delayed execution, so nothing touches the database until you execute the query with a finisher method.

Using Query Modifiers

You can chain a query object together with one or more different modifiers. Here's a list of all the available modifiers:

You can run a query with one of the following query finishers:

Here's an example of a more complicated query using several modifiers:

people := []*Person{}
q := People.NewQuery().Order("-Name").Filter("Age >=", 25).Limit(10)
if err := q.Run(&people); err != nil {
	// handle error
}

Full documentation on the different modifiers and finishers is available on godoc.org.

A Note About String Indexes

Because Redis does not allow you to use strings as scores for sorted sets, Zoom relies on a workaround to store string indexes. It uses a sorted set where all the scores are 0 and each member has the following format: value\x00id, where \x00 is the NULL character. With the string indexes stored this way, Zoom can issue the ZRANGEBYLEX command and related commands to filter models by their string values. As a consequence, here are some caveats to keep in mind:

  • Strings are sorted by ASCII value, exactly as they appear in an ASCII table, not alphabetically. This can have surprising effects, for example 'Z' is considered less than 'a'.
  • Indexed string values may not contain the NULL or DEL characters (the characters with ASCII codepoints of 0 and 127 respectively). Zoom uses NULL as a separator and DEL as a suffix for range queries.

More Information

Persistence

Zoom is as persistent as the underlying Redis database. If you intend to use Redis as a permanent datastore, it is recommended that you turn on both AOF and RDB persistence options and set fsync to everysec. This will give you good performance while making data loss highly unlikely.

If you want greater protections against data loss, you can set fsync to always. This will hinder performance but give you persistence guarantees very similar to SQL databases such as PostgreSQL.

Read more about Redis persistence

Atomicity

All methods and functions in Zoom that touch the database do so atomically. This is accomplished using Redis transactions and Lua scripts when necessary. What this means is that Zoom will not put Redis into an inconsistent state (e.g. where indexes to not match the rest of the data).

However, it should be noted that there is a caveat with Redis atomicity guarantees. If Redis crashes in the middle of a transaction or script execution, it is possible that your AOF file can become corrupted. If this happens, Redis will refuse to start until the AOF file is fixed. It is relatively easy to fix the problem with the redis-check-aof tool, which will remove the partial transaction from the AOF file.

If you intend to issue Redis commands directly or run custom scripts, it is highly recommended that you also make everything atomic. If you do not, Zoom can no longer guarantee that its indexes are consistent. For example, if you change the value of a field which is indexed, you should also update the index for that field in the same transaction. The keys that Zoom uses for indexes and models are provided via the ModelKey, AllIndexKey, and FieldIndexKey methods.

Read more about:

Concurrent Updates and Optimistic Locking

Zoom 0.18.0 introduced support for basic optimistic locking. You can use optimistic locking to safely implement concurrent "read before write" updates.

Optimistic locking utilizes the WATCH, MULTI, and EXEC commands in Redis and only works in the context of transactions. You can use the Transaction.Watch method to watch a model for changes. If the model changes after you call Watch but before you call Exec, the transaction will not be executed and instead will return a WatchError. You can also use the WatchKey method, which functions exactly the same but operates on keys instead of models.

To understand why optimistic locking is useful, consider the following code:

// likePost increments the number of likes for a post with the given id.
func likePost(postID string) error {
  // Find the Post with the given postID
  post := &Post{}
  if err := Posts.Find(postID, post); err != nil {
	 return err
  }
  // Increment the number of likes
  post.Likes += 1
  // Save the post
  if err := Posts.Save(post); err != nil {
	 return err
  }
}

The line post.Likes += 1 is a "read before write" operation. That's because the += operator implicitly reads the current value of post.Likes and then adds to it.

This can cause a bug if the function is called across multiple goroutines or multiple machines concurrently, because the Post model can change in between the time we retrieved it from the database with Find and saved it again with Save.

You can use optimistic locking to avoid this problem. Here's the revised code:

// likePost increments the number of likes for a post with the given id.
func likePost(postID string) error {
  // Start a new transaction and watch the post key for changes. It's important
  // to call Watch or WatchKey *before* finding the model.
  tx := pool.NewTransaction()
  if err := tx.WatchKey(Posts.ModelKey(postID)); err != nil {
    return err
  }
  // Find the Post with the given postID
  post := &Post{}
  if err := Posts.Find(postID, post); err != nil {
	 return err
  }
  // Increment the number of likes
  post.Likes += 1
  // Save the post in a transaction
  tx.Save(Posts, post)
  if err := tx.Exec(); err != nil {
  	 // If the post was modified by another goroutine or server, Exec will return
  	 // a WatchError. You could call likePost again to retry the operation.
    return err
  }
}

Optimistic locking is not appropriate for models which are frequently updated, because you would almost always get a WatchError. In fact, it's called "optimistic" locking because you are optimistically assuming that conflicts will be rare. That's not always a safe assumption.

Don't forget that Zoom allows you to run Redis commands directly. This particular problem might be best solved by the HINCRBY command.

// likePost atomically increments the number of likes for a post with the given
// id and then returns the new number of likes.
func likePost(postID string) (int, error) {
	// Get the key which is used to store the post in Redis
	postKey := Posts.ModelKey(postID, post)
	// Start a new transaction
	tx := pool.NewTransaction()
	// Add a command to increment the number of Likes. The HINCRBY command returns
	// an integer which we will scan into numLikes.
	var numLikes int
	tx.Command(
		"HINCRBY",
		redis.Args{postKey, "Likes", 1},
		zoom.NewScanIntHandler(&numLikes),
	)
	if err := tx.Exec(); err != nil {
		return 0, err
	}
	return numLikes, nil
}

Finally, if optimistic locking is not appropriate and there is no built-in Redis command that offers the functionality you need, Zoom also supports custom Lua scripts via the Transaction.Script method. Redis is single-threaded and scripts are always executed atomically, so you can perform complicated updates without worrying about other clients changing the database.

Read more about:

Testing & Benchmarking

Running the Tests

To run the tests, make sure you're in the root directory for Zoom and run:

go test

If everything passes, you should see something like:

ok    github.com/albrow/zoom  2.267s

If any of the tests fail, please open an issue and describe what happened.

By default, tests and benchmarks will run on localhost:6379 and use database #9. You can change the address, network, and database used with flags. So to run on a unix socket at /tmp/redis.sock and use database #3, you could use:

go test -network=unix -address=/tmp/redis.sock -database=3
Running the Benchmarks

To run the benchmarks, make sure you're in the root directory for the project and run:

go test -run=none -bench .

The -run=none flag is optional, and just tells the test runner to skip the tests and run only the benchmarks (because no test function matches the pattern "none"). You can also use the same flags as above to change the network, address, and database used.

You should see some runtimes for various operations. If you see an error or if the build fails, please open an issue.

Here are the results from my laptop (2.8GHz quad-core i7 CPU, 16GB 1600MHz RAM) using a socket connection with Redis set to append-only mode:

BenchmarkConnection-8                  	 5000000	       318 ns/op
BenchmarkPing-8                        	  100000	     15146 ns/op
BenchmarkSet-8                         	  100000	     18782 ns/op
BenchmarkGet-8                         	  100000	     15556 ns/op
BenchmarkSave-8                        	   50000	     29307 ns/op
BenchmarkSave100-8                     	    3000	    546427 ns/op
BenchmarkFind-8                        	   50000	     24767 ns/op
BenchmarkFind100-8                     	    5000	    374947 ns/op
BenchmarkFindAll100-8                  	    5000	    383919 ns/op
BenchmarkFindAll10000-8                	      30	  47267433 ns/op
BenchmarkDelete-8                      	   50000	     29902 ns/op
BenchmarkDelete100-8                   	    3000	    530866 ns/op
BenchmarkDeleteAll100-8                	    2000	    730934 ns/op
BenchmarkDeleteAll1000-8               	     200	   9185093 ns/op
BenchmarkCount100-8                    	  100000	     16411 ns/op
BenchmarkCount10000-8                  	  100000	     16454 ns/op
BenchmarkQueryFilterInt1From1-8        	   20000	     82152 ns/op
BenchmarkQueryFilterInt1From10-8       	   20000	     83816 ns/op
BenchmarkQueryFilterInt10From100-8     	   10000	    144206 ns/op
BenchmarkQueryFilterInt100From1000-8   	    2000	   1010463 ns/op
BenchmarkQueryFilterString1From1-8     	   20000	     87347 ns/op
BenchmarkQueryFilterString1From10-8    	   20000	     88031 ns/op
BenchmarkQueryFilterString10From100-8  	   10000	    158968 ns/op
BenchmarkQueryFilterString100From1000-8	    2000	   1088961 ns/op
BenchmarkQueryFilterBool1From1-8       	   20000	     82537 ns/op
BenchmarkQueryFilterBool1From10-8      	   20000	     84556 ns/op
BenchmarkQueryFilterBool10From100-8    	   10000	    149463 ns/op
BenchmarkQueryFilterBool100From1000-8  	    2000	   1017342 ns/op
BenchmarkQueryOrderInt100-8            	    3000	    386156 ns/op
BenchmarkQueryOrderInt10000-8          	      30	  50011375 ns/op
BenchmarkQueryOrderString100-8         	    2000	   1004530 ns/op
BenchmarkQueryOrderString10000-8       	      20	  77855970 ns/op
BenchmarkQueryOrderBool100-8           	    3000	    387056 ns/op
BenchmarkQueryOrderBool10000-8         	      30	  49116863 ns/op
BenchmarkComplexQuery-8                	   20000	     84614 ns/op

The results of these benchmarks can vary widely from system to system, and so the benchmarks here are really only useful for comparing across versions of Zoom, and for identifying possible performance regressions or improvements during development. You should run your own benchmarks that are closer to your use case to get a real sense of how Zoom will perform for you. High performance is one of the top priorities for this project.

Contributing

See CONTRIBUTING.md.

Example Usage

albrow/people is an example HTTP/JSON API which uses the latest version of Zoom. It is a simple example that doesn't use all of Zoom's features, but should be good enough for understanding how Zoom can work in a real application.

License

Zoom is licensed under the MIT License. See the LICENSE file for more information.

Documentation

Overview

Package zoom is a blazing-fast datastore and querying engine for Go built on Redis. It supports models of any arbitrary struct type and provides basic querying functionality. It also supports atomic transactions, lua scripts, and running Redis commands directly if needed.

Version 0.18.0

For installation instructions, examples, and more information visit https://github.com/albrow/zoom.

Index

Constants

This section is empty.

Variables

View Source
var DefaultCollectionOptions = CollectionOptions{
	FallbackMarshalerUnmarshaler: GobMarshalerUnmarshaler,
	Index:                        false,
	Name:                         "",
}

DefaultCollectionOptions is the default set of options for a collection.

View Source
var DefaultPoolOptions = PoolOptions{
	Address:     "localhost:6379",
	Database:    0,
	IdleTimeout: 240 * time.Second,
	MaxActive:   1000,
	MaxIdle:     1000,
	Network:     "tcp",
	Password:    "",
	Wait:        true,
}

DefaultPoolOptions is the default set of options for a Pool.

Functions

func Interfaces added in v0.4.0

func Interfaces(in interface{}) []interface{}

Interfaces converts in to []interface{}. It will panic if the underlying type of in is not a slice.

Types

type Action added in v0.9.0

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

Action is a single step in a transaction and must be either a command or a script with optional arguments and a reply handler.

type Collection added in v0.11.0

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

Collection represents a specific registered type of model. It has methods for saving, finding, and deleting models of a specific type. Use the NewCollection method to create a new collection.

func (*Collection) Count added in v0.11.0

func (c *Collection) Count() (int, error)

Count returns the number of models of the given type that exist in the database. It returns an error if there was a problem connecting to the database.

func (*Collection) Delete added in v0.11.0

func (c *Collection) Delete(id string) (bool, error)

Delete removes the model with the given type and id from the database. It will not return an error if the model corresponding to the given id was not found in the database. Instead, it will return a boolean representing whether or not the model was found and deleted, and will only return an error if there was a problem connecting to the database.

func (*Collection) DeleteAll added in v0.11.0

func (c *Collection) DeleteAll() (int, error)

DeleteAll deletes all the models of the given type in a single transaction. See http://redis.io/topics/transactions. It returns the number of models deleted and an error if there was a problem connecting to the database.

func (*Collection) Exists added in v0.18.0

func (c *Collection) Exists(id string) (bool, error)

Exists returns true if the collection has a model with the given id. It returns an error if there was a problem connecting to the database.

func (*Collection) FieldIndexKey added in v0.11.0

func (c *Collection) FieldIndexKey(fieldName string) (string, error)

FieldIndexKey returns the key for the sorted set used to index the field identified by fieldName. It returns an error if fieldName does not identify a field in the spec or if the field it identifies is not an indexed field.

func (*Collection) FieldNames added in v0.16.0

func (c *Collection) FieldNames() []string

FieldNames returns all the field names for the Collection. The order is always the same and is used internally by Zoom to determine the order of fields in Redis commands such as HMGET.

func (*Collection) FieldRedisNames added in v0.16.0

func (c *Collection) FieldRedisNames() []string

FieldRedisNames returns all the Redis names for the fields of the Collection. For example, if a Collection was created with a model type that includes custom field names via the `redis` struct tag, those names will be returned. The order is always the same and is used internally by Zoom to determine the order of fields in Redis commands such as HMGET.

func (*Collection) Find added in v0.11.0

func (c *Collection) Find(id string, model Model) error

Find retrieves a model with the given id from redis and scans its values into model. model should be a pointer to a struct of a registered type corresponding to the Collection. Find will mutate the struct, filling in its fields and overwriting any previous values. It returns an error if a model with the given id does not exist, if the given model was the wrong type, or if there was a problem connecting to the database.

func (*Collection) FindAll added in v0.11.0

func (c *Collection) FindAll(models interface{}) error

FindAll finds all the models of the given type. It executes the commands needed to retrieve the models in a single transaction. See http://redis.io/topics/transactions. models must be a pointer to a slice of models with a type corresponding to the Collection. FindAll will grow or shrink the models slice as needed and if any of the models in the models slice are nil, FindAll will use reflection to allocate memory for them. FindAll returns an error if models is the wrong type or if there was a problem connecting to the database.

func (*Collection) FindFields added in v0.13.0

func (c *Collection) FindFields(id string, fieldNames []string, model Model) error

FindFields is like Find but finds and sets only the specified fields. Any fields of the model which are not in the given fieldNames are not mutated. FindFields will return an error if any of the given fieldNames are not found in the model type.

func (*Collection) IndexKey added in v0.11.0

func (c *Collection) IndexKey() string

IndexKey returns the key that identifies a set in the database that stores all the ids for models in the given collection.

func (*Collection) ModelKey added in v0.11.0

func (c *Collection) ModelKey(id string) string

ModelKey returns the key that identifies a hash in the database which contains all the fields of the model corresponding to the given id. If id is an empty string, it will return an empty string.

func (*Collection) Name added in v0.11.0

func (c *Collection) Name() string

Name returns the name for the given collection. The name is a unique string identifier to use for the collection in redis. All models in this collection that are saved in the database will use the collection name as a prefix.

func (*Collection) NewQuery added in v0.11.0

func (collection *Collection) NewQuery() *Query

NewQuery is used to construct a query. The query returned can be chained together with one or more query modifiers (e.g. Filter or Order), and then executed using the Run, RunOne, Count, or IDs methods. If no query modifiers are used, running the query will return all models of the given type in unspecified order. Queries use delayed execution, so nothing touches the database until you execute them.

func (*Collection) Save added in v0.11.0

func (c *Collection) Save(model Model) error

Save writes a model (a struct which satisfies the Model interface) to the redis database. Save returns an error if the type of model does not match the registered Collection. To make a struct satisfy the Model interface, you can embed zoom.RandomID, which will generate pseudo-random ids for each model.

func (*Collection) SaveFields added in v0.17.0

func (c *Collection) SaveFields(fieldNames []string, model Model) error

SaveFields saves only the given fields of the model. SaveFields uses "last write wins" semantics. If another caller updates the the same fields concurrently, your updates may be overwritten. It will return an error if the type of model does not match the registered Collection, or if any of the given fieldNames are not found in the registered Collection. If SaveFields is called on a model that has not yet been saved, it will not return an error. Instead, only the given fields will be saved in the database.

type CollectionOptions added in v0.11.0

type CollectionOptions struct {
	// FallbackMarshalerUnmarshaler is used to marshal/unmarshal any type into a
	// slice of bytes which is suitable for storing in the database. If Zoom does
	// not know how to directly encode a certain type into bytes, it will use the
	// FallbackMarshalerUnmarshaler. Zoom provides GobMarshalerUnmarshaler and
	// JSONMarshalerUnmarshaler out of the box. You are also free to write your
	// own implementation.
	FallbackMarshalerUnmarshaler MarshalerUnmarshaler
	// If Index is true, any model in the collection that is saved will be added
	// to a set in Redis which acts as an index on all models in the collection.
	// The key for the set is exposed via the IndexKey method. Queries and the
	// FindAll, Count, and DeleteAll methods will not work for unindexed
	// collections. This may change in future versions.
	Index bool
	// Name is a unique string identifier to use for the collection in Redis. All
	// models in this collection that are saved in the database will use the
	// collection name as a prefix. If Name is an empty string, Zoom will use the
	// name of the concrete model type, excluding package prefix and pointer
	// declarations, as the name for the collection. So for example, the default
	// name corresponding to *models.User would be "User". If a custom name is
	// provided, it cannot contain a colon.
	Name string
}

CollectionOptions contains various options for a pool.

func (CollectionOptions) WithFallbackMarshalerUnmarshaler added in v0.17.0

func (options CollectionOptions) WithFallbackMarshalerUnmarshaler(fallback MarshalerUnmarshaler) CollectionOptions

WithFallbackMarshalerUnmarshaler returns a new copy of the options with the FallbackMarshalerUnmarshaler property set to the given value. It does not mutate the original options.

func (CollectionOptions) WithIndex added in v0.17.0

func (options CollectionOptions) WithIndex(index bool) CollectionOptions

WithIndex returns a new copy of the options with the Index property set to the given value. It does not mutate the original options.

func (CollectionOptions) WithName added in v0.17.0

func (options CollectionOptions) WithName(name string) CollectionOptions

WithName returns a new copy of the options with the Name property set to the given value. It does not mutate the original options.

type MarshalerUnmarshaler added in v0.4.0

type MarshalerUnmarshaler interface {
	// Marshal returns a byte-encoded representation of v
	Marshal(v interface{}) ([]byte, error)
	// Unmarshal parses byte-encoded data and store the result in the value
	// pointed to by v.
	Unmarshal(data []byte, v interface{}) error
}

MarshalerUnmarshaler defines a handler for marshaling arbitrary data structures into a byte format and unmarshaling bytes into arbitrary data structures. Any struct which correctly implements the interface should have the property that what you put in using Marshal is identical to what you get out using Unmarshal.

var (
	// GobMarshalerUnmarshaler is an object that implements MarshalerUnmarshaler
	// and uses uses the builtin gob package. Note that not all types are
	// supported by the gob package. See https://golang.org/pkg/encoding/gob/
	GobMarshalerUnmarshaler MarshalerUnmarshaler = gobMarshalerUnmarshaler{}
	// JSONMarshalerUnmarshaler is an object that implements MarshalerUnmarshaler
	// and uses the builtin json package. Note that not all types are supported
	// by the json package. See https://golang.org/pkg/encoding/json/#Marshal
	JSONMarshalerUnmarshaler MarshalerUnmarshaler = jsonMarshalerUnmarshaler{}
)

type Model

type Model interface {
	ModelID() string
	SetModelID(string)
}

Model is an interface encapsulating anything that can be saved and retrieved by Zoom. The only requirement is that a Model must have a getter and a setter for a unique id property.

func Models added in v0.3.0

func Models(in interface{}) []Model

Models converts in to []Model. It will panic if the underlying type of in is not a slice of some concrete type which implements Model.

type ModelNotFoundError added in v0.6.0

type ModelNotFoundError struct {
	Collection *Collection
	Msg        string
}

ModelNotFoundError is returned from Find and Query methods if a model that fits the given criteria is not found.

func (ModelNotFoundError) Error added in v0.6.0

func (e ModelNotFoundError) Error() string

type Pool added in v0.10.0

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

Pool represents a pool of connections. Each pool connects to one database and manages its own set of registered models.

func NewPool added in v0.10.0

func NewPool(address string) *Pool

NewPool creates and returns a new pool using the given address to connect to Redis. All the other options will be set to their default values, which can be found in DefaultPoolOptions.

func NewPoolWithOptions added in v0.17.0

func NewPoolWithOptions(options PoolOptions) *Pool

NewPoolWithOptions initializes and returns a pool with the given options. You can pass in DefaultOptions to use all the default options. Or cal the WithX methods of DefaultOptions to change the options you want to change.

func (*Pool) Close added in v0.10.0

func (p *Pool) Close() error

Close closes the pool. It should be run whenever the pool is no longer needed. It is often used in conjunction with defer.

func (*Pool) NewCollection added in v0.11.0

func (p *Pool) NewCollection(model Model) (*Collection, error)

NewCollection registers and returns a new collection of the given model type. You must create a collection for each model type you want to save. The type of model must be unique, i.e., not already registered, and must be a pointer to a struct. NewCollection will use all the default options for the collection, which are specified in DefaultCollectionOptions. If you want to specify different options, use the NewCollectionWithOptions method.

func (*Pool) NewCollectionWithOptions added in v0.17.0

func (p *Pool) NewCollectionWithOptions(model Model, options CollectionOptions) (*Collection, error)

NewCollectionWithOptions registers and returns a new collection of the given model type and with the provided options.

func (*Pool) NewConn added in v0.10.0

func (p *Pool) NewConn() redis.Conn

NewConn gets a connection from the pool and returns it. It can be used for directly interacting with the database. See http://godoc.org/github.com/garyburd/redigo/redis for full documentation on the redis.Conn type. You must call Close on any connections after you are done using them. Failure to call Close can cause a resource leak.

func (*Pool) NewTransaction added in v0.10.0

func (p *Pool) NewTransaction() *Transaction

NewTransaction instantiates and returns a new transaction.

type PoolOptions added in v0.11.0

type PoolOptions struct {
	// Address to use when connecting to Redis.
	Address string
	// Database id to use (using SELECT).
	Database int
	// IdleTimeout is the amount of time to wait before timing out (closing) idle
	// connections.
	IdleTimeout time.Duration
	// MaxActive is the maximum number of active connections the pool will keep.
	// A value of 0 means unlimited.
	MaxActive int
	// MaxIdle is the maximum number of idle connections the pool will keep. A
	// value of 0 means unlimited.
	MaxIdle int
	// Network to use.
	Network string
	// Password for a password-protected redis database. If not empty,
	// every connection will use the AUTH command during initialization
	// to authenticate with the database.
	Password string
	// Wait indicates whether or not the pool should wait for a free connection
	// if the MaxActive limit has been reached. If Wait is false and the
	// MaxActive limit is reached, Zoom will return an error indicating that the
	// pool is exhausted.
	Wait bool
}

PoolOptions contains various options for a pool.

func (PoolOptions) WithAddress added in v0.17.0

func (options PoolOptions) WithAddress(address string) PoolOptions

WithAddress returns a new copy of the options with the Address property set to the given value. It does not mutate the original options.

func (PoolOptions) WithDatabase added in v0.17.0

func (options PoolOptions) WithDatabase(database int) PoolOptions

WithDatabase returns a new copy of the options with the Database property set to the given value. It does not mutate the original options.

func (PoolOptions) WithIdleTimeout added in v0.17.0

func (options PoolOptions) WithIdleTimeout(timeout time.Duration) PoolOptions

WithIdleTimeout returns a new copy of the options with the IdleTimeout property set to the given value. It does not mutate the original options.

func (PoolOptions) WithMaxActive added in v0.17.0

func (options PoolOptions) WithMaxActive(maxActive int) PoolOptions

WithMaxActive returns a new copy of the options with the MaxActive property set to the given value. It does not mutate the original options.

func (PoolOptions) WithMaxIdle added in v0.17.0

func (options PoolOptions) WithMaxIdle(maxIdle int) PoolOptions

WithMaxIdle returns a new copy of the options with the MaxIdle property set to the given value. It does not mutate the original options.

func (PoolOptions) WithNetwork added in v0.17.0

func (options PoolOptions) WithNetwork(network string) PoolOptions

WithNetwork returns a new copy of the options with the Network property set to the given value. It does not mutate the original options.

func (PoolOptions) WithPassword added in v0.17.0

func (options PoolOptions) WithPassword(password string) PoolOptions

WithPassword returns a new copy of the options with the Password property set to the given value. It does not mutate the original options.

func (PoolOptions) WithWait added in v0.17.0

func (options PoolOptions) WithWait(wait bool) PoolOptions

WithWait returns a new copy of the options with the Wait property set to the given value. It does not mutate the original options.

type Query added in v0.3.0

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

Query represents a query which will retrieve some models from the database. A Query may consist of one or more query modifiers (e.g. Filter or Order) and may be executed with a query finisher (e.g. Run or IDs).

func (*Query) Count added in v0.4.0

func (q *Query) Count() (int, error)

Count counts the number of models that would be returned by the query without actually retrieving the models themselves. Count will also return the first error that occurred during the lifetime of the query (if any).

func (*Query) Exclude added in v0.4.0

func (q *Query) Exclude(fields ...string) *Query

Exclude specifies one or more field names which will *not* be read from the database and scanned. Any other fields *will* be read and scanned into the resulting models when the query is run. You can only use one of Include or Exclude, not both on the same query. Exclude will set an error if you try to use it with Include on the same query. The error, same as any other error that occurs during the lifetime of the query, is not returned until the query is executed.

func (*Query) Filter added in v0.4.0

func (q *Query) Filter(filterString string, value interface{}) *Query

Filter applies a filter to the query, which will cause the query to only return models with field values matching the expression. filterString should be an expression which includes a fieldName, a space, and an operator in that order. For example: Filter("Age >=", 30) would only return models which have an Age value greater than or equal to 30. Operators must be one of "=", "!=", ">", "<", ">=", or "<=". You can only use Filter on fields which are indexed, i.e. those which have the `zoom:"index"` struct tag. If multiple filters are applied to the same query, the query will only return models which have matches for *all* of the filters. Filter will set an error on the query if the arguments are improperly formated, if the field you are attempting to filter is not indexed, or if the type of value does not match the type of the field. The error, same as any other error that occurs during the lifetime of the query, is not returned until the query is executed.

func (*Query) IDs added in v0.19.0

func (q *Query) IDs() ([]string, error)

IDs returns only the ids of the models without actually retrieving the models themselves. IDs will return the first error that occurred during the lifetime of the query (if any).

func (*Query) Include added in v0.4.0

func (q *Query) Include(fields ...string) *Query

Include specifies one or more field names which will be read from the database and scanned into the resulting models when the query is run. Field names which are not specified in Include will not be read or scanned. You can only use one of Include or Exclude, not both on the same query. Include will set an error if you try to use it with Exclude on the same query. The error, same as any other error that occurs during the lifetime of the query, is not returned until the query is executed.

func (*Query) Limit added in v0.4.0

func (q *Query) Limit(amount uint) *Query

Limit specifies an upper limit on the number of models to return. If amount is 0, no limit will be applied and any number of models may be returned. The default value is 0.

func (*Query) Offset added in v0.4.0

func (q *Query) Offset(amount uint) *Query

Offset specifies a starting index (inclusive) from which to start counting models that will be returned. For example, if offset is 10, the first 10 models that the query would otherwise return will be skipped. The default value is 0.

func (*Query) Order added in v0.4.0

func (q *Query) Order(fieldName string) *Query

Order specifies a field by which to sort the models. fieldName should be a field in the struct type corresponding to the Collection used in the query constructor. By default, the records are sorted by ascending order by the given field. To sort by descending order, put a negative sign before the field name. Zoom can only sort by fields which have been indexed, i.e. those which have the `zoom:"index"` struct tag. Only one order may be specified per Order will set an error on the query if the fieldName is invalid, if another order has already been applied to the query, or if the fieldName specified does not correspond to an indexed field. The error, same as any other error that occurs during the lifetime of the query, is not returned until the query is executed.

func (*Query) Run added in v0.3.0

func (q *Query) Run(models interface{}) error

Run executes the query and scans the results into models. The type of models should be a pointer to a slice of Models. If no models fit the criteria, Run will set the length of models to 0 but will *not* return an error. Run will return the first error that occurred during the lifetime of the query (if any), or if models is the wrong type.

func (*Query) RunOne added in v0.6.0

func (q *Query) RunOne(model Model) error

RunOne is exactly like Run but finds only the first model that fits the query criteria and scans the values into model. If no model fits the criteria, RunOne *will* return a ModelNotFoundError.

func (*Query) StoreIDs added in v0.19.0

func (q *Query) StoreIDs(destKey string) error

StoreIDs executes the query and stores the model ids matching the query criteria in a list identified by destKey. The list will be completely overwritten, and the model ids stored there will be in the correct order if the query includes an Order modifier. StoreIDs will return the first error that occurred during the lifetime of the query (if any).

func (Query) String added in v0.5.0

func (q Query) String() string

String satisfies fmt.Stringer and prints out the query in a format that matches the go code used to declare it.

type RandomID added in v0.19.0

type RandomID struct {
	ID string
}

RandomID can be embedded in any model struct in order to satisfy the Model interface. The first time the ModelID method is called on an embedded RandomID, it will generate a pseudo-random id which is highly likely to be unique.

func (*RandomID) ModelID added in v0.19.0

func (r *RandomID) ModelID() string

ModelID returns the id of the model, satisfying the Model interface. If r.ID is an empty string, it will generate a pseudo-random id which is highly likely to be unique.

func (*RandomID) SetModelID added in v0.19.0

func (r *RandomID) SetModelID(id string)

SetModelID sets the id of the model, satisfying the Model interface.

type ReplyHandler added in v0.9.0

type ReplyHandler func(reply interface{}) error

ReplyHandler is a function which does something with the reply from a Redis command or script. See https://godoc.org/github.com/garyburd/redigo/redis for a description of the concrete types for reply.

func NewScanBoolHandler added in v0.16.0

func NewScanBoolHandler(b *bool) ReplyHandler

NewScanBoolHandler returns a ReplyHandler which will convert the reply to a bool and set the value of i to the converted bool. The ReplyHandler will return an error if there was a problem converting the reply.

func NewScanFloat64Handler added in v0.16.0

func NewScanFloat64Handler(f *float64) ReplyHandler

NewScanFloat64Handler returns a ReplyHandler which will convert the reply to a float64 and set the value of f to the converted value. The ReplyHandler will return an error if there was a problem converting the reply.

func NewScanIntHandler added in v0.16.0

func NewScanIntHandler(i *int) ReplyHandler

NewScanIntHandler returns a ReplyHandler which will convert the reply to an integer and set the value of i to the converted integer. The ReplyHandler will return an error if there was a problem converting the reply.

func NewScanModelHandler added in v0.16.0

func NewScanModelHandler(fieldNames []string, model Model) ReplyHandler

NewScanModelHandler returns a ReplyHandler which will scan all the values in the reply into the fields of model. It expects a reply that looks like the output of an HMGET command, without the field names included. The order of fieldNames must correspond to the order of the values in the reply.

fieldNames should be the actual field names as they appear in the struct definition, not the Redis names which may be custom. The special field name "-" is used to represent the id for the model and will be set by the ReplyHandler using the SetModelID method.

For example, if fieldNames is ["Age", "Name", "-"] and the reply from Redis looks like this:

1) "25"
2) "Bob"
3) "b1C7B0yETtXFYuKinndqoa"

The ReplyHandler will set the Age and the Name of the model to 25 and "Bob", respectively, using reflection. Then it will set the id of the model to "b1C7B0yETtXFYuKinndqoa" using the model's SetModelID method.

func NewScanModelsHandler added in v0.16.0

func NewScanModelsHandler(collection *Collection, fieldNames []string, models interface{}) ReplyHandler

NewScanModelsHandler returns a ReplyHandler which will scan the values of the reply into each corresponding Model in models. models should be a pointer to a slice of some concrete Model type. The type of the Models in models should match the type of the given Collection.

fieldNames should be the actual field names as they appear in the struct definition, not the Redis names which may be custom. The special value of "-" in fieldNames represents the id of the model and will be set via the SetModelID method. The ReplyHandler will use the length of fieldNames to determine which fields belong to which models.

The returned ReplyHandler will grow or shrink models as needed. It expects a reply which is a flat array of field values, with no separation between the fields for each model. The order of the values in the reply must correspond to the order of fieldNames.

For example, if fieldNames is ["Age", "Name", "-"] and the reply from Redis looks like this:

1) "25"
2) "Bob"
3) "b1C7B0yETtXFYuKinndqoa"
4) "27"
5) "Alice"
6) "NmjzzzDyNJpsCpPKnndqoa"

NewScanModelsHandler will use the first value in the reply to set the Age field of the first Model to 25 using reflection. It will use the second value of the reply to set Name value of the first Model to "Bob" using reflection. And finally, it will use the third value in reply to set the id of the first Model by calling its SetModelID method. Because the length of fieldNames is 3 in this case, the ReplyHandler will assign the first three to the first model, the next three to the second model, etc.

func NewScanStringHandler added in v0.16.0

func NewScanStringHandler(s *string) ReplyHandler

NewScanStringHandler returns a ReplyHandler which will convert the reply to a string and set the value of i to the converted string. The ReplyHandler will return an error if there was a problem converting the reply.

func NewScanStringsHandler added in v0.16.0

func NewScanStringsHandler(strings *[]string) ReplyHandler

NewScanStringsHandler returns a ReplyHandler which will convert the reply to a slice of strings and set the value of strings to the converted value. The returned ReplyHandler will grow or shrink strings as needed. The ReplyHandler will return an error if there was a problem converting the reply.

type Transaction added in v0.9.0

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

Transaction is an abstraction layer around a Redis transaction. Transactions consist of a set of actions which are either Redis commands or lua scripts. Transactions feature delayed execution, so nothing touches the database until you call Exec.

func (*Transaction) Command added in v0.9.0

func (t *Transaction) Command(name string, args redis.Args, handler ReplyHandler)

Command adds a command action to the transaction with the given args. handler will be called with the reply from this specific command when the transaction is executed.

func (*Transaction) Count added in v0.9.0

func (t *Transaction) Count(c *Collection, count *int)

Count counts the number of models of the given type in the database in an existing transaction. It sets the value of count to the number of models. Any errors encountered will be added to the transaction and returned as an error when the transaction is executed.

func (*Transaction) Delete added in v0.9.0

func (t *Transaction) Delete(c *Collection, id string, deleted *bool)

Delete removes a model with the given type and id in an existing transaction. deleted will be set to true iff the model was successfully deleted when the transaction is executed. If the no model with the given type and id existed, the value of deleted will be set to false. Any errors encountered will be added to the transaction and returned as an error when the transaction is executed. You may pass in nil for deleted if you do not care whether or not the model was deleted.

func (*Transaction) DeleteAll added in v0.9.0

func (t *Transaction) DeleteAll(c *Collection, count *int)

DeleteAll delets all models for the given model type in an existing transaction. The value of count will be set to the number of models that were successfully deleted when the transaction is executed. Any errors encountered will be added to the transaction and returned as an error when the transaction is executed. You may pass in nil for count if you do not care about the number of models that were deleted.

func (*Transaction) DeleteModelsBySetIDs added in v0.19.0

func (t *Transaction) DeleteModelsBySetIDs(setKey string, collectionName string, handler ReplyHandler)

DeleteModelsBySetIDs is a small function wrapper around a Lua script. The script will atomically delete the models corresponding to the ids in set (not sorted set) identified by setKey and return the number of models that were deleted. You can pass in a handler (e.g. NewScanIntHandler) to capture the return value of the script. You can use the Name method of a Collection to get the name.

func (*Transaction) Exec added in v0.9.0

func (t *Transaction) Exec() error

Exec executes the transaction, sequentially sending each action and calling all the action handlers with the corresponding replies.

func (*Transaction) Exists added in v0.18.0

func (t *Transaction) Exists(c *Collection, id string, exists *bool)

Exists sets the value of exists to true if a model exists in the given collection with the given id, and sets it to false otherwise. The first error encountered (if any) will be added to the transaction and returned when the transaction is executed.

func (*Transaction) ExtractIDsFromFieldIndex added in v0.19.0

func (t *Transaction) ExtractIDsFromFieldIndex(setKey string, destKey string, min interface{}, max interface{})

ExtractIDsFromFieldIndex is a small function wrapper around a Lua script. The script will get all the ids from the sorted set identified by setKey using ZRANGEBYSCORE with the given min and max, and then store them in a sorted set identified by destKey. The members of the sorted set should be model ids. Note that this method will not work on sorted sets that represents string indexes because they are stored differently.

func (*Transaction) ExtractIDsFromStringIndex added in v0.19.0

func (t *Transaction) ExtractIDsFromStringIndex(setKey, destKey, min, max string)

ExtractIDsFromStringIndex is a small function wrapper around a Lua script. The script will extract the ids from a sorted set identified by setKey using ZRANGEBYLEX with the given min and max, and then store them in a sorted set identified by destKey. All the scores for the sorted set should be 0, and the members should follow the format <value>\x00<id>, where <value> is the string value, \x000 is the NULL ASCII character and <id> is the id of the model with that value. As with all string indexes in Zoom, the value cannot contain the NULL ASCII character or the DEL character (codepoints 0 and 127 respectively). Note that the stored ids are sorted in ASCII order according to their corresponding string values.

func (*Transaction) Find added in v0.9.0

func (t *Transaction) Find(c *Collection, id string, model Model)

Find retrieves a model with the given id from redis and scans its values into model in an existing transaction. model should be a pointer to a struct of a registered type corresponding to the Collection. find will mutate the struct, filling in its fields and overwriting any previous values. Any errors encountered will be added to the transaction and returned as an error when the transaction is executed.

func (*Transaction) FindAll added in v0.9.0

func (t *Transaction) FindAll(c *Collection, models interface{})

FindAll finds all the models of the given type and scans the values of the models into models in an existing transaction. See http://redis.io/topics/transactions. models must be a pointer to a slice of models with a type corresponding to the Collection. findAll will grow the models slice as needed and if any of the models in the models slice are nil, FindAll will use reflection to allocate memory for them. Any errors encountered will be added to the transaction and returned as an error when the transaction is executed.

func (*Transaction) FindFields added in v0.13.0

func (t *Transaction) FindFields(c *Collection, id string, fieldNames []string, model Model)

FindFields is like Find but finds and sets only the specified fields. Any fields of the model which are not in the given fieldNames are not mutated. FindFields will return an error if any of the given fieldNames are not found in the model type.

func (*Transaction) Query added in v0.17.0

func (tx *Transaction) Query(collection *Collection) *TransactionQuery

Query is used to construct a query in the context of an existing Transaction It can be used to run a query atomically along with commands, scripts, or other queries in a single round trip. Note that this method returns a TransactionQuery, whereas Collection.NewQuery returns a Query. The two types are very similar, but there are differences in how they are eventually executed. Like a regular Query, a TransactionQuery can be chained together with one or more query modifiers (e.g. Filter or Order). You also need to finish the query with a method such as Run, RunOne, or Count. The major difference is that TransactionQueries are not actually run until you call Transaction.Exec(). As a consequence, the finisher methods (e.g. Run, RunOne, Count, etc) do not return anything. Instead they accept arguments which are then mutated after the transaction is executed.

func (*Transaction) Save added in v0.9.0

func (t *Transaction) Save(c *Collection, model Model)

Save writes a model (a struct which satisfies the Model interface) to the redis database inside an existing transaction. save will set the err property of the transaction if the type of model does not match the registered Collection, which will cause exec to fail immediately and return the error. To make a struct satisfy the Model interface, you can embed zoom.RandomID, which will generate pseudo-random ids for each model. Any errors encountered will be added to the transaction and returned as an error when the transaction is executed.

func (*Transaction) SaveFields added in v0.17.0

func (t *Transaction) SaveFields(c *Collection, fieldNames []string, model Model)

SaveFields saves only the given fields of the model inside an existing transaction. SaveFields will set the err property of the transaction if the type of model does not match the registered Collection, or if any of the given fieldNames are not found in the model type. In either case, the transaction will return the error when you call Exec. SaveFields uses "last write wins" semantics. If another caller updates the the same fields concurrently, your updates may be overwritten. If SaveFields is called on a model that has not yet been saved, it will not return an error. Instead, only the given fields will be saved in the database.

func (*Transaction) Script added in v0.9.0

func (t *Transaction) Script(script *redis.Script, args redis.Args, handler ReplyHandler)

Script adds a script action to the transaction with the given args. handler will be called with the reply from this specific script when the transaction is executed.

func (*Transaction) Watch added in v0.18.0

func (t *Transaction) Watch(model Model) error

Watch issues a Redis WATCH command using the key for the given model. If the model changes before the transaction is executed, Exec will return a WatchError and the commands in the transaction will not be executed. Unlike most other transaction methods, Watch does not use delayed execution. Because of how the WATCH command works, Watch must send a command to Redis immediately. You must call Watch or WatchKey before any other transaction methods.

func (*Transaction) WatchKey added in v0.18.0

func (t *Transaction) WatchKey(key string) error

WatchKey issues a Redis WATCH command using the given key. If the key changes before the transaction is executed, Exec will return a WatchError and the commands in the transaction will not be executed. Unlike most other transaction methods, WatchKey does not use delayed execution. Because of how the WATCH command works, WatchKey must send a command to Redis immediately. You must call Watch or WatchKey before any other transaction methods.

type TransactionQuery added in v0.17.0

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

TransactionQuery represents a query which will be run inside an existing transaction. A TransactionQuery may consist of one or more query modifiers (e.g. Filter or Order) and should always be finished with a query finisher (e.g. Run or IDs). Unlike Query, the finisher methods for TransactionQuery always expect pointers as arguments and will set the values when the corresponding Transaction is executed.

func (*TransactionQuery) Count added in v0.17.0

func (q *TransactionQuery) Count(count *int)

Count will count the number of models that match the query criteria and set the value of count. It works very similarly to Query.Count, so you can check the documentation for Query.Count for more information. The first error encountered will be saved to the corresponding Transaction (if there is not already an error for the Transaction) and returned when you call Transaction.Exec.

func (*TransactionQuery) Exclude added in v0.17.0

func (q *TransactionQuery) Exclude(fields ...string) *TransactionQuery

Exclude works exactly like Query.Exclude. See the documentation for Query.Exclude for more information.

func (*TransactionQuery) Filter added in v0.17.0

func (q *TransactionQuery) Filter(filterString string, value interface{}) *TransactionQuery

Filter works exactly like Query.Filter. See the documentation for Query.Filter for more information.

func (*TransactionQuery) IDs added in v0.19.0

func (q *TransactionQuery) IDs(ids *[]string)

IDs will find the ids for models matching the query criteria and set the value of ids. It works very similarly to Query.IDs, so you can check the documentation for Query.IDs for more information. The first error encountered will be saved to the corresponding Transaction (if there is not already an error for the Transaction) and returned when you call Transaction.Exec.

func (*TransactionQuery) Include added in v0.17.0

func (q *TransactionQuery) Include(fields ...string) *TransactionQuery

Include works exactly like Query.Include. See the documentation for Query.Include for more information.

func (*TransactionQuery) Limit added in v0.17.0

func (q *TransactionQuery) Limit(amount uint) *TransactionQuery

Limit works exactly like Query.Limit. See the documentation for Query.Limit for more information.

func (*TransactionQuery) Offset added in v0.17.0

func (q *TransactionQuery) Offset(amount uint) *TransactionQuery

Offset works exactly like Query.Offset. See the documentation for Query.Offset for more information.

func (*TransactionQuery) Order added in v0.17.0

func (q *TransactionQuery) Order(fieldName string) *TransactionQuery

Order works exactly like Query.Order. See the documentation for Query.Order for a full description.

func (*TransactionQuery) Run added in v0.17.0

func (q *TransactionQuery) Run(models interface{})

Run will run the query and scan the results into models when the Transaction is executed. It works very similarly to Query.Run, so you can check the documentation for Query.Run for more information. The first error encountered will be saved to the corresponding Transaction (if there is not already an error for the Transaction) and returned when you call Transaction.Exec.

func (*TransactionQuery) RunOne added in v0.17.0

func (q *TransactionQuery) RunOne(model Model)

RunOne will run the query and scan the first model which matches the query criteria into model. If no model matches the query criteria, it will set a ModelNotFoundError on the Transaction. It works very similarly to Query.RunOne, so you can check the documentation for Query.RunOne for more information. The first error encountered will be saved to the corresponding Transaction (if there is not already an error for the Transaction) and returned when you call Transaction.Exec.

func (*TransactionQuery) StoreIDs added in v0.19.0

func (q *TransactionQuery) StoreIDs(destKey string)

StoreIDs will store the ids for for models matching the criteria in a list identified by destKey. It works very similarly to Query.StoreIDs, so you can check the documentation for Query.StoreIDs for more information. The first error encountered will be saved to the corresponding Transaction (if there is not already an error for the Transaction) and returned when you call Transaction.Exec.

func (TransactionQuery) String added in v0.17.0

func (q TransactionQuery) String() string

String satisfies fmt.Stringer and prints out the query in a format that matches the go code used to declare it.

type WatchError added in v0.18.0

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

WatchError is returned whenever a watched key is modified before a transaction can execute. It is part of the implementation of optimistic locking in Zoom. You can watch a key with the Transaction.WatchKey method.

func (WatchError) Error added in v0.18.0

func (e WatchError) Error() string

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
t or T : Toggle theme light dark auto
y or Y : Canonical URL