You can either query the database at a given time or listen to changes for display update or data manipulation.
Let's consider the following data
// Store some objects
int key1, key2, key3;
await db.transaction((txn) async {
key1 = await store.add(txn, {'name': 'fish'});
key2 = await store.add(txn, {'name': 'cat'});
key3 = await store.add(txn, {'name': 'dog'});
});
You can get a record by key
// Read by key
expect(await store.record(key1).get(db), {'name': 'fish'});
// Read 2 records by key
var records = await store.records([key2, key3]).get(db);
expect(records[0], {'name': 'cat'});
expect(records[1], {'name': 'dog'});
Records you get are immutable/read-only. When using map, it you want to use the resulting map as a base for a new object for modification and creation, you should first clone the value:
import 'package:sembast/utils/value_utils.dart';
// Read by key
var value = await store.record(key1).get(db);
// read values are immutable/read-only. If you want to modify it you
// should clone it first
// the following will throw an exception
value['name'] = 'nice fish'; // Will throw!
// clone the resulting map for modification
var map = cloneMap(value);
map['name'] = 'nice fish';
// map is ready to be stored
Filtering and sorting can be done on any field
// Look for any animal "greater than" (alphabetically) 'cat'
// ordered by name
var finder = Finder(
filter: Filter.greaterThan('name', 'cat'),
sortOrders: [SortOrder('name')]);
var records = await store.find(db, finder: finder);
expect(records.length, 2);
expect(records[0]['name'], 'dog');
expect(records[1]['name'], 'fish');
You can use the Filter
class to filter records. Simple filter are available:
Filter.equals
Filter.notEquals
Filter.isNull
Filter.noNull
Filter.lessThan
Filter.lessThanOrEquals
Filter.greaterThan
Filter.greaterThanOrEquals
Filter.inList
Filter.matches
Filter.matchesRegExp
Filter.custom
(Custom filter, use with caution and do not modify record data as it provides a raw access to the record internal value for efficiency.)
Filter.and
Filter.or
Filter.not
You can combine multiple filters using the operators &
and |
:
var filterAnd = Filter.greaterThan(Field.value, "hi") &
Filter.lessThan(Field.value, "hum");
var filterOr = Filter.lessThan(Field.value, "hi") |
Filter.greaterThan(Field.value, "hum");
If you have more than two filters, you can also use Filter.or
and Filter.and
:
var filter = Filter.and([
Filter.greaterThan(Field.value, "hi"),
Filter.lessThan(Field.value, "hum"),
Filter.notEquals(Field.value, "ho"),
]);
Let says that you want to find a given searchText
in either of the fields clientName or
companyName AND the date
must be in 2023, and sort the result by date, you could write a filter like this:
var startTimestamp = Timestamp.fromDateTime(DateTime(2023));
var endTimestamp = Timestamp.fromDateTime(DateTime(2024));
var regExp = RegExp(searchText, caseSensitive: false);
var filter = Filter.and([
Filter.or([
Filter.matchesRegExp(
'clientName',
regExp,
),
Filter.matchesRegExp(
'companyName',
regExp,
),
]),
Filter.greaterThanOrEquals('date', startTimestamp),
Filter.lessThan('date', endTimestamp),
]);
var finder = Finder(filter: filter, sortOrders: [SortOrder('date')]);
You can limit your query to the first element found
// Find the first record matching the finder
var record = await store.findFirst(db, finder: finder);
// Get the record id
var recordId = record.key;
// Get the record value
var recordValue = record.value;
Records you get are immutable/read-only. When using map, it you want to use the resulting map as a base for a new object for modification and creation, you should first clone the value:
import 'package:sembast/utils/value_utils.dart';
// find a record
var record = await store.findFirst(db, finder: finder);
// record snapshot are read-only.
// If you want to modify it you should clone it
var map = cloneMap(record.value);
map['name'] = 'nice dog';
// map is ready to be stored
Records can be sorted by key using the special Field.key
field:
// Look for the last created record
var finder = Finder(sortOrders: [SortOrder(Field.key, false)]);
var record = await store.findFirst(db, finder: finder);
expect(record['name'], 'dog');
Assuming you have the following record
{
"name": "Silver",
"product": {
"code": "1F8"
}
}
You can order on product/code
var finder = Finder(sortOrders: [SortOrder('product.code')]);
Or query on it
var finder = Finder(filter: Filter.equals('product.code', 'AF8'));
start
and end
can specify a start and end boundary, similar to firestore
// Look for the one after `cat`
var finder = Finder(
sortOrders: [SortOrder('name', true)],
start: Boundary(values: ['cat']));
var record = await store.findFirst(db, finder: finder);
expect(record['name'], 'dog');
The boundary can be a record. If values
is used, the number of values should match the number of sort orders
// Our shop store
var store = intMapStoreFactory.store('shop');
await db.transaction((txn) async {
await store.add(txn, {'name': 'Lamp', 'price': 10});
await store.add(txn, {'name': 'Chair', 'price': 10});
await store.add(txn, {'name': 'Deco', 'price': 5});
await store.add(txn, {'name': 'Table', 'price': 35});
});
// Look for object after Chair 10 (ordered by price then name) so
// should the the Lamp 10
var finder = Finder(
sortOrders: [SortOrder('price'), SortOrder('name')],
start: Boundary(values: [10, 'Chair']));
var record = await store.findFirst(db, finder: finder);
expect(record['name'], 'Lamp');
// You can also specify to look after a given record
finder = Finder(
sortOrders: [SortOrder('price'), SortOrder('name')],
start: Boundary(record: record));
record = await store.findFirst(db, finder: finder);
// After the lamp the more expensive one is the Table
expect(record['name'], 'Table');
Records can be filtered using regular expressions
// Look for any name stating with f (i.e. fish, frog...)
var finder = Finder(filter: Filter.matches('name', '^f'));
var record = await store.findFirst(db, finder: finder);
expect(record['name'], 'fish');
// Look for any name ending with og (i.e. dog, frog...)
var finder = Finder(filter: Filter.matches('name', r'og$'));
var record = await store.findFirst(db, finder: finder);
expect(record['name'], 'dog');
// Look for any name containing 'is' (fish matches)
var finder = Finder(filter: Filter.matches('name', 'is'));
var record = await store.findFirst(db, finder: finder);
expect(record['name'], 'fish');
Filter.equals
and Filter.matches
can also look for list items if a field is a list using the anyInList
option. It
will match if one item in the list matches the criteria.
// Look for record with at least one category stating with f (i.e. food...),
// if `categories` field is a list with text elements
var finder = Finder(filter: Filter.matches('categories', '^f', anyInList: true));
var record = await store.findFirst(db, finder: finder);
You can also access fields using index.
{
"name": "Silver",
"tag": ['good', 'heavy'],
}
You can order on a given index tag (here 0 for the first item)
var finder = Finder(sortOrders: [SortOrder('tag.0')]);
For Filter.equals
and Filter.matches
, you can also specify to look for any item in any list.
Filter.equals('tag.@', 'heavy');
is equivalent to
Filter.equals('tag', 'heavy', anyInList: true);
You imagine more complex example, Let's consider the following data:
[
{
'name': 'Lamp',
'attributes': [
{'tag': 'furniture'},
{'tag': 'plastic'}
]
},
{
'name': 'Chair',
'attributes': [
{'tag': 'wood'},
{'tag': 'furniture'}
]
},
]
You can filter for any records that has the furniture attribute using the following filder:
Filter.equals('attributes.@.tag', 'furniture');
There is no now convenient sum
or average
functions.
However, data being in memory getting all the records and performing operation on the results does not cost much. example below to compute the sum and average of the fied age
:
var store = intMapStoreFactory.store();
await store.addAll(db, [
{'name': 'cat', 'age': 13},
{'name': 'dog', 'age': 12},
{'name': 'rabbit', 'age': 8}
]);
var ages = (await store.find(db)).map((snapshot) => snapshot['age']);
// Compute sum
var sum = ages.reduce((value, element) => value + element);
// Compute average
var average = sum / ages.length;
In general, I don't recommend using the synchronous API as complex queries can block the UI.
As of 3.4.8-1, get by keys can now be synchronous (since the implementation is basically reading from a map).
RecordRef.getSync()
RecordRef.getSnapshotSync()
RecordRef.existsSync()
RecordsRef.getSnapshotsSync()
RecordsRef.getSync()
As of 3.4.9-1, All read access is allowed synchronously:
StoreRef.countSync()
;StoreRef.findKeysSync()
;StoreRef.findKeySync()
;StoreRef.findSync()
;StoreRef.findFirstSync()
;QueryRef.countSync()
;QueryRef.getSnapshotsSync()
;QueryRef.getSnapshotSync()
;