Queries

ObjectBox queries help you quickly find objects matching criteria you've specified.

Using queries is simple: from your entity's Box, call Query() with conditions as arguments:

query := box.Query(Device_.Location.HasPrefix("US-", false))
devices, err := query.Find()

Building queries

The query in the code above uses a function HasPrefix on a device location. Where does this come from? ObjectBox generates a Device_ struct for you to reference available properties conveniently. This also allows code completion in your IDE and avoids typos: correctness is checked at compile time (string based queries would only be checked at run-time).

Let's say you have the following entity defined in your package:

type Device struct {
Id uint64
Name string
Location string
Profile uint32
}

Using this input, the ObjectBox code generator creates a variable Device_ in the same package:

var Device_ = struct {
Id *objectbox.PropertyUint64
Name *objectbox.PropertyString
Location *objectbox.PropertyString
Profile *objectbox.PropertyUint32
}{...}

You can use Device_ to construct type-specific conditions in place and combining them, forming the full query. The following example looks for devices located in the U. S. with profile number 42.

box.Query(Device_.Profile.Equals(42), Device_.Location.HasPrefix("US-", false))

Reusing Queries and Parameters

If you frequently run a Query you should cache the Query object and re-use it. To make a Query more reusable you can change the values, or query parameters, of each condition you added even after the Query is built. Let's see how.

Assume we want to find a list of User with specific FirstName values. First, we build a regular Query with an equal() condition for FirstName. Because we have to pass an initial parameter value to equal() but plan to override it before running the Query later, we just pass an empty string:

var caseSensitive = false
var query = box.Query(User_.FirstName.Equals("", caseSensitive))

Now at some later point we want to actually run the Query. To set a value for the FirstName parameter we call setStringParams() on the Query and pass the FirstName property and the new parameter value:

query.SetStringParams(User_.FirstName, "Joe")
joes, _ := query.Find()

Limit, Offset, and Pagination

Sometimes you only need a subset of a query, for example the first 10 elements. This is especially helpful (and resourceful) when you have a high number of entities and you cannot limit the result using query conditions only. The built Query has .Offset() and .Limit() methods to help you do that

query := box.Query(User_.FirstName.Equals("Joe", false))
joes, err := query.Offset(10).Limit(5).Find()

Offset(n uint64): the first n results are skipped. Limit(n uint64): at most n results of this query are returned.

Notable conditions/operators

In addition to expected conditions like Equals(), NotEquals(), GreaterThan() and LessThan() there are also conditions like:

  • Between() to filter for values that are between the given two (inclusive)

  • In() and NotIn() to filter for values that match any in the given set,

  • HasPrefix(), HasSuffix() and Contains() for extended String filtering.

Working with query results

You have a few options how to handle the results of a query:

  • Find() returns a slice of the matching objects,

  • FindIds()fetches just the IDs of the matching objects as a slice, which can be more efficient in case you don't need the whole object,

  • Remove() deletes all the matching objects from the database (in a single transaction),

  • Count() gives you the number of the objects that match the query,

  • Limit() and Offset() let you select just part of the result (e. g. for paging)

  • DescribeParams() is a utility function which returns a human-readable representation of the query.

Querying linked objects (relations)

After creating a relation between entities, you might want to add a query condition for a property that only exists in the related entity. In SQL this is solved using JOINs. ObjectBox provides query links instead. Let's see how this works using an example.

Assume there is a Person that can be associated with multiple Address entities:

//go:generate go run github.com/objectbox/objectbox-go/cmd/objectbox-gogen
type Person struct {
Id uint64
Name string
Address []*Address
}
type Address struct {
Id uint64
Street string
ZIP string
}

To get a Person with a certain name that also lives on a specific street, we need to query the associated Address entities of a Person. To do this, use the Person_.Address.Link(cs ...Conditions) method of the generated Person_ variable to tell that the addresses relation should be queried and what conditions should be used to filter the addresses:

// get all Person objects named "Elmo" which have an address on "Sesame Street"
var query = BoxForPerson(ob).Query(
Person_.name.Equals("Elmo", true),
Person_.Address.Link(Address_.Street.Equals("Sesame Street", true)),
)
var elmosOnSesameStreet = query.Find()

What if we want to get a list of Address instead of Person? No problem, links are smart enough to know there's also an implicit relation in the opposite direction. Note the different box we're using here:

// get all Address objects on "Sesame Street" linked from a Person named "Elmo"
val builder = box.query().equal(Address_.street, "Sesame Street")
var query = BoxForAddress(ob).Query(
Address_.Street.Equals("Sesame Street", true),
Person_.Address.Link(Person_.name.Equals("Elmo", true)),
)
var addressesSesameStreetWithElmo = query.Find()

More to come

ObjectBox core can do much more with the queries, such as ordering, aliases, etc. These are not yet supported by our Go API, but you can take a peek at https://docs.objectbox.io/queries to get the idea what's coming in the future releases.

Feel free to open a feature request on GitHub if you have an idea or a proposal.