zoom

package module
v0.7.4 Latest Latest
Warning

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

Go to latest
Published: Sep 21, 2014 License: MIT Imports: 17 Imported by: 0

README

Zoom

Version: 0.7.4

A blazing-fast, lightweight ORM for Go built on Redis.

Requires redis version >= 2.8.9 and Go version >= 1.0.

Full documentation is available on godoc.org.

WARNING: this isn't done yet and may change significantly before the official release. There is no promise of backwards compatibility until version 1.0. I do not advise using Zoom for production or mission-critical applications. Feedback and pull requests are welcome :)

Table of Contents

Philosophy

If you want to build a high-performing, low latency application, but still want some of the ease of an ORM, Zoom is for you!

Zoom allows you to:

  • Persistently save structs of any type
  • Retrieve structs from the database
  • Preserve relationships between structs
  • Preform limited queries

Zoom consciously makes the trade off of using more memory in order to increase performance. Zoom stores all data in memory at all times, so if your machine runs out of memory, zoom will either crash or start using swap space (resulting in huge performance penalties). Zoom does not do sharding (but might in the future), so be aware that memory could be a hard constraint for larger applications.

Zoom is a high-level library and abstracts away more complicated aspects of the Redis API. For example, it manages its own connection pool, performs transactions when possible, and automatically converts structs to and from a format suitable for the database. If needed, you can still execute redis commands directly.

If you want to use advanced or complicated SQL queries, Zoom is not for you. For example, Zoom currently lacks an equivalent of the SQL keywords IN and OR. Although support for more types of queries may be added in the future, it is not a high priority.

Installation

First, you must install Redis on your system. See installation instructions. By default, Zoom will use a tcp/http connection on localhost:6379 (same as the Redis default). The latest version of Redis is recommended.

To install Zoom itself:

go get github.com/albrow/zoom

This will pull the current master branch, which is (most likely) working but is quickly changing.

Getting Started

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

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

Then, call zoom.Init somewhere in your app initialization code, e.g. in the main function. You must also call zoom.Close when your application exits, so it's a good idea to use defer.

func main() {
    // ...
    zoom.Init(nil)
    defer zoom.Close()
    // ...
}

The Init function takes a *zoom.Configuration struct as an argument. Here's a list of options and their defaults:

type Configuration struct {
	Address       string // Address to connect to. Default: "localhost:6379"
	Network       string // Network to use. Default: "tcp"
	Database      int    // Database id to use (using SELECT). Default: 0
}

If possible, it is strongly recommended that you use a unix socket connection instead of tcp. Redis is roughly 50% faster this way. To connect with a unix socket, you must first configure redis to accept socket connections (typically on /tmp/redis.sock). If you are unsure how to do this, refer to the official redis docs for help. You might also find the redis quickstart guide helpful, especially the bottom sections.

To use unix sockets with Zoom, simply pass in "unix" as the Network and "/tmp/unix.sock" as the Address:

config := &zoom.Configuration {
	Address: "/tmp/redis.sock",
	Network: "unix",
}
zoom.Init(config)

Working with Models

Creating Models

In order to save a struct using Zoom, you need to embed an anonymous DefaultData field. DefaultData gives you an Id and getters and setters for that id, which are required in order to save the model to the database. Here's an example of a Person model:

type Person struct {
    Name string
    zoom.DefaultData
}

Because of the way zoom uses reflection, all the fields you want to save need to be public.

You must also call zoom.Register so that Zoom can spec out the different model types and the relations between them. You only need to do this once per type. For example, somewhere in your initialization sequence (e.g. in the main function) put the following:

// register the *Person type under the default name "Person"
if err := zoom.Register(&Person{}); err != nil {
    // handle error
}

Now the *Person type will be associated with the string name "Person." You can also use the RegisterName funcion to specify a custom name for the model type.

Saving Models

To persistently save a Person model to the databse, simply call zoom.Save.

p := &Person{Name: "Alice"}
if err := zoom.Save(p); err != nil {
    // handle error
}
Finding a Single Model

Zoom will automatically assign a random, unique id to each saved model. To retrieve a model by id, use the FindById function, which also requires the name associated with the model type. The return type is interface{} so you may need to type assert.

result, err := zoom.FindById("Person", "a_valid_person_id")
if err != nil {
    // handle error
}

// type assert
person, ok := result.(*Person)
if !ok {
    // handle !ok
}

Alternatively, you can use the ScanById function to avoid type assertion. It expects a pointer to a struct as an argument(some registered model type).

p := &Person{}
if err := zoom.ScanById("a_valid_person_id", p); err != nil {
    // handle error
}
Deleting Models

To delete a model you can just use the Delete function:

if err := zoom.Delete(person); err != nil {
	// handle err
}

Or if you know the model's id, use the DeleteById function:

if err := zoom.DeleteById("Person", "some_person_id"); err != nil {
	// handle err
}

Enforcing Thread-Safety

How it Works

If you wish to perform thread-safe updates, you can embed zoom.Sync into your model. zoom.Sync provides a default implementation of a Syncer. A Syncer consists of a unique identifier which is a reference to a global mutex map. By default the identifier is modelType + ":" + id. If a model implements the Syncer interface, any time the model is retrieved from the database, zoom will call Lock on the mutex referenced by Syncer. The effect is that you can gauruntee that only one reference to a given model is active at any given time.

IMPORTANT: When you embed zoom.Sync into a model, you must remember to call Unlock() when you are done making changes to the model.

Example

Here's what a model with an embedded Syncer should look like:

type Person struct {
	Age int
	Name string
	zoom.DefaultData
	zoom.Sync
}

And here's what a thread-safe update would look like:

func UpdatePerson() {
	p := &Person{}
	// You must unlock the mutex associated with the id when you are
	// done making changes. Using defer is sometimes a good way to do this.
	defer p.Unlock()
	// ScanById implicitly calls Lock(), so it will not return until any other
	// references to the same model id are unlocked.
	if err := zoom.ScanById("some valid id", p); err != nil {
		// handle err
	}
	p.Name = "Bill"
	p.Age = 27
	if err := zoom.Save(p); err != nil {
		// handle err
	}	
}

Running Queries

The Query Object

Zoom provides a useful abstraction for querying the database. You create queries by using the NewQuery constuctor, 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 type of model 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. Any errors 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 Scan.

Finding all Models of a Given Type

To retrieve a list of all persons, create a query and don't apply any modifiers. The return type of Run is interface{}, but the underlying type is a slice of Model, i.e. a slice of pointers to structs. You may need a type assertion.

results, err := zoom.NewQuery("Person").Run()
if err != nil {
    // handle error
}

// type assert each element. something like:
persons := make([]*Person, len(results))
for i, result := range results {
    if person, ok := result.(*Person); !ok {
        // handle !ok
    }
    persons[i] = person
}

You can use the Scan method if you want to avoid a type assertion. Scan expects a pointer to a slice of pointers to some model type.

persons := make([]*Person, 0)
if _, err := zoom.NewQuery("Person").Scan(persons); err != nil {
	// handle err
}
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:

  • Order
  • Limit
  • Offset
  • Include
  • Exclude
  • Filter

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

  • Run
  • Scan
  • IdsOnly
  • Count
  • RunOne
  • ScanOne

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

q := zoom.NewQuery("Person").Order("-Name").Filter("Age >=", 25).Limit(10)
result, err := q.Run()

You might be able to guess what each of these methods do, but if anything is not obvious, full documentation on the different modifiers and finishers is available on godoc.org.

Relationships

Relationships in Zoom are simple. There are no special return types or functions for using relationships. What you put in is what you get out.

One-to-One Relationships

For these examples we're going to introduce two new struct types:

// The PetOwner struct
type PetOwner struct {
	Name  string
	Pet   *Pet    // *Pet should also be a registered type
	zoom.DefaultData
}

// The Pet struct
type Pet struct {
	Name   string
	zoom.DefaultData
}

Assuming you've registered both the *PetOwner and *Pet types, Zoom will automatically set up a relationship when you save a PetOwner with a valid Pet. (The Pet must have an id)

// create a new PetOwner and a Pet
owner := &PetOwner{Name: "Bob"}
pet := &Pet{Name: "Spot"}

// save the pet
if err := zoom.Save(pet); err != nil {
	// handle err
}

// set the owner's pet
owner.Pet = pet

// save the owner
if err := zoom.Save(owner); err != nil {
	// handle err
}

Now if you retrieve the pet owner by it's id, the pet attribute will persist as well.

For now, Zoom does not support reflexivity of one-to-one relationships. So if you want pet ownership to be bidirectional (i.e. if you want an owner to know about its pet and a pet to know about its owner), you would have to manually set up both relationships.

ownerCopy := &PetOwner{}
if err := zoom.ScanById("the_id_of_above_pet_owner", ownerCopy); err != nil {
	// handle err
}

// the Pet attribute is still set
fmt.Println(ownerCopy.Pet.Name)

// Output:
//	Spot
One-to-Many Relationships

One-to-many relationships work similarly. This time we're going to use two new struct types in the examples.

// The Parent struct
type Parent struct {
	Name     string
	Children []*Child  // *Child should also be a registered type
	zoom.DefaultData
}

// The Child struct
type Child struct {
	Name   string
	zoom.DefaultData
}

Assuming you register both the *Parent and *Child types, Zoom will automatically set up a relationship when you save a parent with some children (as long as each child has an id). Here's an example:

// create a parent and two children
parent := &Parent{Name: "Christine"}
child1 := &Child{Name: "Derick"}
child2 := &Child{Name: "Elise"}

// save the children
if err := zoom.Save(child1, child2); err != nil {
	// handle err
}

// assign the children to the parent
parent.Children = append(parent.Children, child1, child2)

// save the parent
if err := zoom.Save(parent); err != nil {
	// handle err
}

Again, Zoom does not support reflexivity. So if you wanted a child to know about its parent, you would have to set up and manage the relationship manually. This might change in the future.

Now when you retrieve a parent by id, it's children field will automatically be populated. So getting the children again is straight forward.

parentCopy := &Parent{}
if err := zoom.ScanById("the_id_of_above_parent", parentCopy); err != nil {
	// handle error
}

// now you can access the children normally
for _, child := range parentCopy.Children {
	fmt.Println(child.Name)
}

// Output:
//	Derick
//	Elise

For a Parent with a lot of children, it may take a long time to get each Child from the database. If this is the case, it's a good idea to use a query with the Exclude modifier when you don't intend to use the children.

parents := make([]*Parent, 0)
q := zoom.NewQuery("Parent").Filter("Id =", "the_id_of_above_parent").Exclude("Children")
if err := q.Scan(parents); err != nil {
	// handle error
}

// Since it was excluded, Children is empty.
fmt.Println(parents[0].Children)

// Output:
//	[]
Many-to-Many Relationships

There is nothing special about many-to-many relationships. They are simply made up of multiple one-to-many relationships.

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	0.355s

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, again make sure you're in the root directory and run:

go test . -bench .

You can 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.3GHz quad-core i7, 8GB RAM) using a socket connection with Redis set to append-only mode:

BenchmarkConnection         20000000	      93.7 ns/op
BenchmarkPing                 100000	     24472 ns/op
BenchmarkSet                   50000	     32703 ns/op
BenchmarkGet                  100000	     25795 ns/op
BenchmarkSave                  50000	     59899 ns/op
BenchmarkMSave100               2000	    908596 ns/op
BenchmarkFindById              50000	     41050 ns/op
BenchmarkMFindById100	        2000	    671383 ns/op
BenchmarkDeleteById	          50000	     48800 ns/op
BenchmarkMDeleteById100	        2000	    617435 ns/op
BenchmarkFindAllQuery10	       10000	    165903 ns/op
BenchmarkFindAllQuery1000	      500	   7224478 ns/op
BenchmarkFindAllQuery100000	     2	 850699127 ns/op
BenchmarkCountAllQuery10	   100000	     29838 ns/op
BenchmarkCountAllQuery1000	   100000	     29739 ns/op
BenchmarkCountAllQuery100000	100000	     29798 ns/op

Currently, there are not many benchmarks for queries; I'm working on adding more.

The results of the benchmark can vary widely from system to system. 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. The speeds above are already pretty fast, but improving them is one of the top priorities for this project.

Example Usage

I have built an example json/rest application 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.

TODO

Ordered generally by priority, here's what I'm working on:

  • Improve performance and get as close as possible to raw redis
  • Add more benchmarks
  • Add godoc compatible examples in the test files
  • Support callbacks (BeforeSave, AfterSave, BeforeDelete, AfterDelete, etc.)
  • Implement high-level watching for record changes
  • Add option to make relationships reflexive (inverseOf struct tag?)
  • Add a dependent:delete struct tag
  • Support AND and OR operators on Filters
  • Support combining queries into a single transaction
  • Support automatic sharding

If you have an idea or suggestion for a feature, please open an issue and describe it.

License

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

Documentation

Overview

Package zoom is a fast and lightweight ORM powered by Redis. It supports models of any arbitrary struct type, supports relationships between models, and provides basic querying functionality. It also supports running Redis commands directly.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Close

func Close()

Close closes the connection pool and shuts down the Zoom library. It should be run when application exits, e.g. using defer.

func Delete

func Delete(model Model) error

Delete removes a model from the database. It will throw an error if the type of the model has not yet been registered, if the Id field of the model is empty, or if there is a problem connecting to the database. If the model does not exist in the database, Delete will not return an error; it will simply have no effect.

func DeleteById

func DeleteById(modelName string, id string) error

DeleteById removes a model from the database by its registered name and id. By default modelName should be the string version of the type of model (without the asterisk, ampersand, or package prefix). If you used RegisterName instead of Register, modelName should be the custom name you used. DeleteById will throw an error if modelName is invalid or if there is a problem connecting to the database. If the model does not exist, DeleteById will not return an error; it will simply have no effect.

func GetConn

func GetConn() redis.Conn

GetConn gets a connection from the connection pool and returns it. It can be used for directly interacting with the database. Check out http://godoc.org/github.com/garyburd/redigo/redis for full documentation on the redis.Conn type.

func GetMutexById added in v0.7.1

func GetMutexById(mutexId string) *sync.Mutex

GetMutexById returns a mutex identified by mutexId. It will always return the same mutex given the same mutexId. It can be used to manually lock and unlock references to specific redis keys for stricter thread-safety.

func Init

func Init(passedConfig *Configuration)

Init starts the Zoom library and creates a connection pool. It accepts a Configuration struct as an argument. Any zero values in the configuration will fallback to their default values. Init should be called once during application startup.

func Interfaces added in v0.4.0

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

Interfaces convert interface{} to []interface{} Will panic if the type is invalid.

func KeyExists

func KeyExists(key string, conn redis.Conn) (bool, error)

KeyExists returns true iff a given key exists in redis. If conn is nil, a new connection will be created and closed before the end of the function.

func MDelete added in v0.4.0

func MDelete(models []Model) error

MDelete is like Delete but accepts a slice of models and deletes them all in a single transaction. See http://redis.io/topics/transactions. If an error is encountered in the middle of the transaction, the function will halt and return the error. In that case, any models which were deleted before the error was encountered will still be deleted. Usually this is fine because calling Delete on a model a second time will have no adverse effects.

func MDeleteById added in v0.4.0

func MDeleteById(modelNames []string, ids []string) error

MDeleteById is like DeleteById but accepts a slice of modelNames and ids and deletes them all in a single transaction. See http://redis.io/topics/transactions. The slice of modelNames and ids should be properly aligned so that, e.g., modelNames[0] corresponds to ids[0]. If there is an error in the middle of the transaction, the function will halt and return the error. In that case, any models which were deleted before the error was encountered will still be deleted. Usually this is fine because calling Delete on a model a second time will have no adverse effects.

func MSave added in v0.4.0

func MSave(models []Model) error

MSave is like Save but accepts a slice of models and saves them all in a single transaction. See http://redis.io/topics/transactions. If there is an error in the middle of the transaction, any models that were saved before the error was encountered will still be saved. Usually this is fine because saving a model a second time will have no adverse effects.

func MScanById added in v0.4.0

func MScanById(ids []string, models interface{}) error

MScanById is like ScanById but accepts a slice of ids and a pointer to a slice of models. It executes the commands needed to retrieve the models in a single transaction. See http://redis.io/topics/transactions. The slice of ids and models should be properly aligned so that, e.g., ids[0] corresponds to models[0]. If there is an error in the middle of the transaction, the function will halt and return the error. Any models that were scanned before the error will still be valid. If any of the models in the models slice are nil, MScanById will use reflection to allocate memory for them.

func Register

func Register(model Model) error

Register adds a model type to the list of registered types. Any struct you wish to save must be registered first. The type of model must be unique, i.e. not already registered. Each registered model gets a name, a unique string identifier, which by default is just the string version of the type (the asterisk and any package prefixes are stripped). See RegisterName if you would prefer to specify a custom name.

func RegisterName added in v0.4.0

func RegisterName(name string, model Model) error

RegisterName is like Register but allows you to specify a custom name to use for the model type. The custom name will be used as a prefix for all models of this type stored in redis. The custom name will also be used in functions which require a model name, such as queries.

func Save

func Save(model Model) error

Save writes a model (a struct which satisfies the Model interface) to the redis database. Save throws an error if the type of the struct has not yet been registered or if there is a problem connecting to the database. If the Id field of the struct is empty, Save will mutate the struct by setting the Id. To make a struct satisfy the Model interface, you can embed zoom.DefaultData.

func ScanById added in v0.1.1

func ScanById(id string, model Model) error

ScanById retrieves a model from redis and scans it into model. model should be a pointer to a struct of a registered type. ScanById will mutate the struct, filling in its fields. It returns an error if a model with that id does not exist or if there was a problem connecting to the database.

func SetContains

func SetContains(key, member string, conn redis.Conn) (bool, error)

SetContains returns true iff the redis set identified by key contains member. If conn is nil, a new connection will be created and closed before the end of the function.

func Unregister added in v0.4.0

func Unregister(model Model) error

Unregister removes a model type from the list of registered types. You only need to call UnregisterName or UnregisterType, not both.

func UnregisterName

func UnregisterName(name string) error

UnregisterName removes a model type (identified by modelName) from the list of registered model types. You only need to call UnregisterName or UnregisterType, not both.

Types

type ById added in v0.5.0

type ById []*indexedPrimativesModel

utility type for quickly sorting by id

func (ById) Len added in v0.5.0

func (ms ById) Len() int

func (ById) Less added in v0.5.0

func (ms ById) Less(i, j int) bool

func (ById) Swap added in v0.5.0

func (ms ById) Swap(i, j int)

type Configuration

type Configuration struct {
	Address  string // Address to connect to. Default: "localhost:6379"
	Network  string // Network to use. Default: "tcp"
	Database int    // Database id to use (using SELECT). Default: 0
}

Configuration contains various options. It should be created once and passed in to the Init function during application startup.

type DefaultData added in v0.3.0

type DefaultData struct {
	Id string `redis:"-"`
}

DefaultData should be embedded in any struct you wish to save. It includes important fields and required methods to implement Model.

func (DefaultData) GetId added in v0.3.0

func (d DefaultData) GetId() string

func (*DefaultData) SetId added in v0.3.0

func (d *DefaultData) SetId(id string)

type KeyNotFoundError

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

KeyNotFoundError is returned from Find, Scan, and Query functions if the model you are trying to find does not exist in the database.

func NewKeyNotFoundError

func NewKeyNotFoundError(key string, modelType reflect.Type) *KeyNotFoundError

func (*KeyNotFoundError) Error

func (e *KeyNotFoundError) Error() string

type MarshalerUnmarshaler added in v0.4.0

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

Interface 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.

type Model

type Model interface {
	GetId() string
	SetId(string)
}

Model is an interface encapsulating anything that can be saved. Any struct which includes an embedded DefaultData field satisfies the Model interface.

func FindById

func FindById(modelName, id string) (Model, error)

FindById gets a model from the database. It returns an error if a model with that id does not exist or if there was a problem connecting to the database. By default modelName should be the string version of the type of model (without the asterisk, ampersand, or package prefix). If you used RegisterName instead of Register, modelName should be the custom name you used.

func MFindById added in v0.4.0

func MFindById(modelNames, ids []string) ([]Model, error)

MFindById is like FindById but accepts a slice of model names and ids and returns a slice of models. It executes the commands needed to retrieve the models in a single transaction. See http://redis.io/topics/transactions. The slice of modelNames and ids should be properly aligned so that, e.g., modelNames[0] corresponds to ids[0]. If there is an error in the middle of the transaction, the function will halt and return the models retrieved so far (as well as the error).

func Models added in v0.3.0

func Models(in interface{}) []Model

Models converts an interface to a slice of Model. It is typically used to convert a return value of a Query. Will panic if the type is invalid.

type ModelNameNotRegisteredError

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

ModelNameNotRegisteredError is returned if you attempt to perform certain operations for unregistered names.

func NewModelNameNotRegisteredError

func NewModelNameNotRegisteredError(name string) *ModelNameNotRegisteredError

func (*ModelNameNotRegisteredError) Error

type ModelNotFoundError added in v0.6.0

type ModelNotFoundError struct{}

ModelNotFoundError is returned from ScanOne and RunOne if a model that matches the query criteria was not found.

func NewModelNotFoundError added in v0.6.0

func NewModelNotFoundError() *ModelNotFoundError

func (*ModelNotFoundError) Error added in v0.6.0

func (e *ModelNotFoundError) Error() string

type ModelTypeNotRegisteredError

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

ModelTypeNotRegisteredError is returned if you attempt to perform certain operations for unregistered types.

func NewModelTypeNotRegisteredError

func NewModelTypeNotRegisteredError(typ reflect.Type) *ModelTypeNotRegisteredError

func (*ModelTypeNotRegisteredError) Error

type NameAlreadyRegisteredError

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

NameAlreadyRegisteredError is returned if you try to register a name which has already been registered.

func NewNameAlreadyRegisteredError

func NewNameAlreadyRegisteredError(name string) *NameAlreadyRegisteredError

func (*NameAlreadyRegisteredError) Error

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 and can be run in several different ways with different query finishers.

func NewQuery added in v0.4.0

func NewQuery(modelName string) *Query

NewQuery is used to construct a query. modelName should be the name of a registered model. The query returned can be chained together with one or more query modifiers, and then executed using the Run, Scan, Count, or IdsOnly methods. If no query modifiers are used, running the query will return all models that match the type corresponding to modelName in uspecified order. NewQuery will set an error on the query if modelName is not the name of some registered model type. The error, same as any other error that occurs during the lifetime of the query, is not returned until the Query is executed. When the query is executed the first error that occured during the lifetime of the query object (if any) will be returned.

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 retreiving the models themselves. Count will also return the first error that occured during the lifetime of the query object (if any). Otherwise, the second return value will be nil.

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. When the query is executed the first error that occured during the lifetime of the query object (if any) will be returned.

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 attributes matching the expression. filterString should be an expression which includes a fieldName, a space, and an operator in that order. 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. I.e. applying multiple filters is logially equivalent to combining them with a AND or INTERSECT operator. 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. When the query is executed the first error that occured during the lifetime of the query object (if any) will be returned.

func (*Query) IdsOnly added in v0.4.0

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

IdsOnly returns only the ids of the models without actually retreiving the models themselves. IdsOnly will also return the first error that occured during the lifetime of the query object (if any). Otherwise, the second return value will be nil.

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. When the query is executed the first error that occured during the lifetime of the query object (if any) will be returned.

func (*Query) Limit added in v0.4.0

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

Limit specifies an upper limit on the number of records to return. If amount is 0, no limit will be applied. 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 records that will be returned. 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 records and the order in which records should be sorted. fieldName should be a field in the struct type specified by the modelName argument in the query constructor. By default, the records are sorted by ascending order. 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. However, in the future this may change. Only one order may be specified per query. However in the future, secondary orders may be allowed, and will take effect when two or more models have the same value for the primary order field. 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. When the query is executed the first error that occured during the lifetime of the query object (if any) will be returned.

func (*Query) Run added in v0.3.0

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

Run executes the query and returns the results in the form of an interface. The true type of the return value will be a slice of pointers to some regestired model type. If you need a type-safe way to run queries, look at the Scan method. Run will also return the first error that occured during the lifetime of the query object (if any). Otherwise, the second return value will be nil.

func (*Query) RunOne added in v0.6.0

func (q *Query) RunOne() (interface{}, error)

RunOne is exactly like Run, returns only the first model that fits the query criteria, or if no models fit the critera, returns an error. If you need to do this in a type-safe way, look at the ScanOne method.

func (*Query) Scan added in v0.4.0

func (q *Query) Scan(in interface{}) error

Scan (like Run) executes the query but instead of returning the results it attempts to scan the results into in. The type of in should be a pointer to a slice of pointers to a registered model type. Scan will return the first error that occured during the lifetime of the query object (if any), or will return an error if you provided an interface with an invalid type. Otherwise, the return value will be nil.

func (*Query) ScanOne added in v0.6.0

func (q *Query) ScanOne(in interface{}) error

ScanOne is exactly like Scan but scans only the first model that fits the query criteria. If no model fits the criteria, an error will be returned. The type of in should be a pointer to a slice of pointers to a registered model type.

func (*Query) String added in v0.5.0

func (q *Query) String() string

String returns a string representation of the query and its modifiers

type Sync added in v0.7.0

type Sync struct {
	MutexId string `json:"-",redis:"-"`
}

Sync provides a default implementation of Syncer. It can be directly embedded into model structs.

func (Sync) Lock added in v0.7.0

func (s Sync) Lock()

func (*Sync) SetMutexId added in v0.7.0

func (s *Sync) SetMutexId(id string)

func (Sync) Unlock added in v0.7.0

func (s Sync) Unlock()

type Syncer added in v0.7.0

type Syncer interface {
	Lock()
	Unlock()
	SetMutexId(string)
}

Syncer is an interface meant for controlling accesss to a model in a thread-safe manner. Any model that implements Syncer will automatically call Lock before being retrieved from the database, so that there can effectively only be one active reference to a model (identified by a mutexId) at any given time. Zoom will not automatically call Unlock, however, and it is up to the user call Unlock when done making changes to a specific model.

type TypeAlreadyRegisteredError

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

TypeAlreadyRegisteredError is returned if you try to register a type which has already been registered.

func NewTypeAlreadyRegisteredError

func NewTypeAlreadyRegisteredError(typ reflect.Type) *TypeAlreadyRegisteredError

func (*TypeAlreadyRegisteredError) Error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL