Class: SC.Query
Extends
SC.Copyable, SC.Freezable, SC.Object.
This class permits you to perform queries on your data store or a remote data store. Here is a simple example of a local (see below) query,
query = SC.Query.create({
conditions: "firstName = 'Johnny' AND lastName = 'Cash'"
});
This query, when used with the store, will return all records which have record attributes of
firstName
equal to 'Johnny' and lastName
equal to 'Cash'. To use the query with the store
simple pass it to find
like so,
records = MyApp.store.find(query);
In this example, records
will be an SC.RecordArray
array containing all matching records. The
amazing feature of record arrays backed by local queries is that they update automatically
whenever the records in the store change. This means that once we've run the query once, any time
data is loaded or unloaded from the store, the results of the query (i.e. records
) will
update automatically. This allows for truly powerful and dynamic uses, such as having a list of
filtered results that continually updates instantly as data pages in or out in the background.
To limit the query to a record type of MyApp.MyModel
, you can specify the type as a property of
the query like this,
query = SC.Query.create({
conditions: "firstName = 'Johnny' AND lastName = 'Cash'",
recordType: MyApp.MyModel
});
Calling find()
like above will now return only records of type MyApp.MyModel.
It is recommended
to limit your query to a record type, since the query will have to look for matching records in
the whole store if no record type is given.
You can also give an order for local queries, which the resulting records will use,
query = SC.Query.create({
conditions: "firstName = 'Johnny' AND lastName = 'Cash'",
recordType: MyApp.MyModel,
orderBy: "lastName, year DESC"
});
The default order direction is ascending. You can change it to descending by writing 'DESC'
behind the property name as was done in the example above. If no order is given, or records are
equal in respect to a given order, records will be ordered by their storeKeys
which increment
depending on load order.
Note, you can check if a certain record matches the query by calling query.contains(record)
.
Local vs. Remote Queries
The default type for queries is 'local', but there is another type we can use called 'remote'. The distinction between local and remote queries is a common tripping point for new SproutCore developers, but hopefully the following description helps keep it clear. The terms local and remote refer to the location of the data where the query is performed and by whom. A local query will be run by the client against the store of data within the client, while a remote query will be run on by the server against some remote store of data. This seems simple enough, but it can lead to a few misconceptions.
The first misconception is that local queries don't ever result in a call to a server. This is
not the case; when a local query is first used with the store it will generally result in a call
to the server, but whether or not it does depends on your store's data source. Keep this in mind,
local queries only run against the data loaded in the client's store. If the client store is
empty, the results of the query will be empty even though there may be thousands of matching
records on a server somewhere. That's why when a query is first used, the store will look for a
data source that implements the fetch(store, query)
method. Your data source can then decide
whether additional data should be loaded into the store first in order to better fulfill the
query.
This is entirely up to your client/server implementation, but a common use case is to have a
general query trigger the load of a large set of data and then any more specific queries will only
run against what was already loaded. For more details, @see SC.DataSource.prototype.fetch.
So to
recap, local queries are passed to the data source fetch method the first time they are used so
that the data source has a chance to load additional data that the query may need.
Once we get past the first misconception; local queries are actually pretty easy to understand and to work with. We run the queries, get the resulting record array and watch as the results almost magically update as the data in the client changes. Local queries are the default type, and typically we will use local queries almost exclusively in SproutCore apps. So why do we have a remote type?
In a previous paragraph, we considered how a local query would be empty if the local store was empty even though there may be thousands of matching records on a server. Well what if there were millions of records on the server? When dealing with extremely large datasets, it's not feasible to load all of the records into the client so that the client can run a query against them. This is the role of the 'remote' type. Remote queries are not actually "run" by the client at all, which is the next misconception; you cannot run a remote query.
This misconception is another way of saying that you can't set conditions or order on a remote
query. The 'remote' SC.Query
type is simply a reflection of some database query that was run
against a data store somewhere outside of the client. For example, say we want to still find all
the records on the server with firstName
equal to 'Johnny' and lastName
equal to 'Cash', but
now there are millions of records. This type of query is best left to a MySQL or other database on
a server and thus the server will have exposed an API endpoint that will return the results of
such a search when passed some search terms.
Again, this is entirely up to your client/server configuration, but the way it is handled by the
data source is nearly identical to how local queries will be handled. In both situations, the
data source is passed the query the first time it is run and when the data source is done it calls
the same method on the store, dataSourceDidFetchQuery
. In both situations too, any data that the
data source receives should be loaded into the client store using loadRecords
. However, because
there may be lots of other data in the client store, the 'remote' query must also be told which
records pertain to it and in what order, which is done by passing the store keys of the new data
also to dataSourceDidFetchQuery
.
So to recap, use 'local' queries to filter the data currently in the client store and use 'remote' queries to represent results filtered by a remote server. Both may be used by a data source to load data from a server.
SproutCore Query Language
Features of the query language:
Primitives:
- record properties
null
,undefined
true
,false
- numbers (integers and floats)
- strings (double or single quoted)
Parameters:
%@
(wild card){parameterName}
(named parameter)
Wild cards are used to identify parameters by the order in which they appear in the query string. Named parameters can be used when tracking the order becomes difficult. Both types of parameters can be used by giving the parameters as a property to your query object:
yourQuery.parameters = yourParameters
where yourParameters
should have one of the following formats:
for wild cards: [firstParam,
secondParam,
thirdParam]
for named params: {name1: param1, mane2: parma2}
You cannot use both types of parameters in a single query!
Operators:
=
!=
<
<=
>
>=
BEGINS_WITH
-- (checks if a string starts with another one)ENDS_WITH
-- (checks if a string ends with another one)CONTAINS
-- (checks if a string contains another one, or if an object is in an array)MATCHES
-- (checks if a string is matched by a regexp, you will have to use a parameter to insert the regexp)ANY
-- (checks if the thing on its left is contained in the array on its right, you will have to use a parameter to insert the array)TYPE_IS
-- (unary operator expecting a string containing the name of a Model class on its right side, only records of this type will match)
Boolean Operators:
AND
OR
NOT
Parenthesis for grouping:
(
and)
Adding Your Own Query Handlers
You can extend the query language with your own operators by calling:
SC.Query.registerQueryExtension('your_operator', your_operator_definition);
See details below. As well you can provide your own comparison functions to control ordering of specific record properties like this:
SC.Query.registerComparison(property_name, comparison_for_this_property);
Defined in: query.js
- Since:
- SproutCore 1.0
Field Summary
- conditions
- isEditable
- isQuery
- SC.Query.LOCAL
- location
- orderBy
- parameters
- queryLanguage
- recordType
- recordTypes
- SC.Query.REMOTE
- scope
- Fields borrowed from SC.Object:
- concatenatedProperties, isDestroyed, isObject, nextProperty, object, property, target, toInvalidate
- Fields borrowed from SC.Observable:
- isObservable
- Fields borrowed from SC.Copyable:
- isCopyable
- Fields borrowed from SC.Freezable:
- isFreezable, isFrozen
Class Methods
- build(location, recordType, conditions, parameters)
- compareStoreKeys(storeKey1, storeKey2, storeKey1, storeKey2)
- containsRecords(query, records, store)
- local(recordType, properties, oldParameters)
- orderStoreKeys(storeKeys, query, store)
- registerComparison(name, custom)
- registerQueryExtension(tokenName, token)
- remote(recordType, properties, oldParameters)
- storeKeyFor(query)
Instance Methods
- buildOrder(orderOp)
- buildTokenTree(tokenList, treeLogic)
- compare(record1, record2)
- contains(record, parameters)
- containsRecordTypes(types)
- expandedRecordTypes()
- isLocal()
- isRemote()
- parse()
- queryWithScope(recordArray)
- tokenizeString(inputString, grammar)
- toString()
Field Detail
conditions StringUnparsed query conditions. If you are handling a query yourself, then you will find the base query string here.
Indicates whether a record is editable or not. Defaults to NO
. Local
queries should never be made editable. Remote queries may be editable or
not depending on the data source.
Indicates the location where the result set for this query is stored. Currently the available options are:
SC.Query.LOCAL
-- indicates that the query results will be automatically computed from the in-memory store.SC.Query.REMOTE
-- indicates that the query results are kept on a remote server and hence must be loaded from theDataSource
.
The default setting for this property is SC.Query.LOCAL
.
Note that even if a query location is LOCAL
, your DataSource
will
still have its fetch()
method called for the query. For LOCAL
queries, you won't need to explicitly provide the query result set; you
can just load records into the in-memory store as needed and let the query
recompute automatically.
If your query location is REMOTE
, then your DataSource
will need to
provide the actual set of query results manually. Usually you will only
need to use a REMOTE
query if you are retrieving a large data set and you
don't want to pay the cost of computing the result set client side.
Optional orderBy
parameters. This can be a string of keys, optionally
ending with the strings " DESC"
or " ASC"
to select descending or
ascending order.
Alternatively, you can specify a comparison function, in which case the two records will be sent to it. Your comparison function, as with any other, is expected to return -1, 0, or 1.
Optional hash of parameters. These parameters may be interpolated into the query conditions. If you are handling the query manually, these parameters will not be used.
This is the definition of the query language. You can extend it
by using SC.Query.registerQueryExtension()
.
The base record type or types for the query. This must be specified to filter the kinds of records this query will work on. You may either set this to a single record type or to an array or set of record types.
Optional array of multiple record types. If the query accepts multiple record types, this is how you can check for it.
Another query that will optionally limit the search of records. This is
usually configured for you when you do find()
from another record array.
Class Method Detail
Returns a SC.Query
instance reflecting the passed properties. Where
possible this method will return cached query instances so that multiple
calls to this method will return the same instance. This is not possible
however, when you pass custom parameters or set ordering. All returned
queries are frozen.
Usually you will not call this method directly. Instead use the more
convenient SC.Query.local()
and SC.Query.remote()
.
Examples
There are a number of different ways you can call this method.
The following return local queries selecting all records of a particular type or types, including any subclasses:
var people = SC.Query.local(Ab.Person);
var peopleAndCompanies = SC.Query.local([Ab.Person, Ab.Company]);
var people = SC.Query.local('Ab.Person');
var peopleAndCompanies = SC.Query.local('Ab.Person Ab.Company'.w());
var allRecords = SC.Query.local(SC.Record);
The following will match a particular type of condition:
var married = SC.Query.local(Ab.Person, "isMarried=YES");
var married = SC.Query.local(Ab.Person, "isMarried=%@", [YES]);
var married = SC.Query.local(Ab.Person, "isMarried={married}", {
married: YES
});
You can also pass a hash of options as the second parameter. This is how you specify an order, for example:
var orderedPeople = SC.Query.local(Ab.Person, { orderBy: "firstName" });
Default sort method that is used when calling containsStoreKeys()
or containsRecords()
on this query. Simply materializes two records
based on storekey
s before passing on to compare()
.
- Parameters:
- storeKey1 Number
- a store key
- storeKey2 Number
- a store key
- storeKey1
- storeKey2
- Returns:
- Number
- -1 if record1 < record2, +1 if record1 > record2, 0 if equal
Will find which records match a give SC.Query
and return an array of
store keys. This will also apply the sorting for the query.
- Parameters:
- query SC.Query
- to apply
- records SC.RecordArray
- to search within
- store SC.Store
- to materialize record from
- Returns:
- Array
- array instance of store keys matching the SC.Query (sorted)
Returns a LOCAL
query with the passed properties.
For example,
// Show all the accounts with a value greater than 100.
query = SC.Query.local(MyApp.Account, {
conditions: 'value > {amt}',
parameters: { amt: 100 },
orderBy: 'value DESC'
});
Sorts a set of store keys according to the orderBy
property
of the SC.Query
.
Call to register a comparison for a specific property name. The function you pass should accept two values of this property and return -1 if the first is smaller than the second, 0 if they are equal and 1 if the first is greater than the second.
Call to register an extension for the query language. You should provide a name for your extension and a definition specifying how it should be parsed and evaluated.
Have a look at queryLanguage
for examples of definitions.
TODO add better documentation here
Returns a REMOTE
query with the passed properties.
For example,
// The data source can alter its remote request using the value of
// `query.beginsWith`.
query = SC.Query.remote(MyApp.Person, { beginsWith: 'T' });
Given a query, returns the associated storeKey
. For the inverse of this
method see SC.Store.queryFor()
.
- Parameters:
- query SC.Query
- the query
- Returns:
- Number
- a storeKey.
Instance Method Detail
Takes a string containing an order statement and returns an array
describing this order for easier processing.
Called by parse()
.
Takes an array of tokens and returns a tree, depending on the specified tree logic. The returned object will have an error property if building of the tree failed. Check it to get some information about what happend. If everything worked, the tree can be evaluated by calling
tree.evaluate(record, parameters)
If tokenList
is empty, a single token will be returned which will
evaluate to true for all records.
Returns the sort order of the two passed records, taking into account the
orderBy
property set on this query. This method does not verify that the
two records actually belong in the query set or not; this is checked using
contains()
.
Returns YES
if record is matched by the query, NO
otherwise. This is
used when computing a query locally.
Returns YES
if the query matches one or more of the record types in the
passed set.
Returns the complete set of recordType
s matched by this query. Includes
any named recordType
s plus their subclasses.
Returns YES
if query location is Local. This is sometimes more
convenient than checking the location.
Returns YES
if query location is Remote. This is sometimes more
convenient than checking the location.
This method has to be called before the query object can be used. You will normally not have to do this; it will be called automatically if you try to evaluate a query. You can, however, use this function for testing your queries.
- Returns:
- Boolean
- true if parsing succeeded, false otherwise
Returns the same query but with the scope set to the passed record array. This will copy the receiver. It also stores these queries in a cache to reuse them if possible.
- Parameters:
- recordArray SC.RecordArray
- the scope
- Returns:
- SC.Query
- new query
Takes a string and tokenizes it based on the grammar definition
provided. Called by parse()
.