blob: 017940d456250a5f83ee36ea42edfb8838e78516 [file] [log] [blame]
// Copyright 2017, the Chromium project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of cloud_firestore;
/// Represents a query over the data at a particular location.
class Query {
Query._(
{@required Firestore firestore,
@required List<String> pathComponents,
Map<String, dynamic> parameters})
: _firestore = firestore,
_pathComponents = pathComponents,
_parameters = parameters ??
new Map<String, dynamic>.unmodifiable(<String, dynamic>{
'where': new List<List<dynamic>>.unmodifiable(<List<dynamic>>[]),
}),
assert(firestore != null),
assert(pathComponents != null);
final Firestore _firestore;
final List<String> _pathComponents;
final Map<String, dynamic> _parameters;
/// A string containing the slash-separated path to this Query (relative to
/// the root of the database).
String get path => _pathComponents.join('/');
Query _copyWithParameters(Map<String, dynamic> parameters) {
return new Query._(
firestore: _firestore,
pathComponents: _pathComponents,
parameters: new Map<String, dynamic>.unmodifiable(
new Map<String, dynamic>.from(_parameters)..addAll(parameters),
),
);
}
Map<String, dynamic> buildArguments() {
return new Map<String, dynamic>.from(_parameters)
..addAll(<String, dynamic>{
'path': path,
});
}
/// Notifies of query results at this location
// TODO(jackson): Reduce code duplication with [DocumentReference]
Stream<QuerySnapshot> get snapshots {
Future<int> _handle;
// It's fine to let the StreamController be garbage collected once all the
// subscribers have cancelled; this analyzer warning is safe to ignore.
StreamController<QuerySnapshot> controller; // ignore: close_sinks
controller = new StreamController<QuerySnapshot>.broadcast(
onListen: () {
_handle = Firestore.channel.invokeMethod(
'Query#addSnapshotListener',
<String, dynamic>{
'path': path,
'parameters': _parameters,
},
);
_handle.then((int handle) {
Firestore._queryObservers[handle] = controller;
});
},
onCancel: () {
_handle.then((int handle) async {
await Firestore.channel.invokeMethod(
'Query#removeListener',
<String, dynamic>{'handle': handle},
);
Firestore._queryObservers.remove(handle);
});
},
);
return controller.stream;
}
/// Obtains a CollectionReference corresponding to this query's location.
CollectionReference reference() =>
new CollectionReference._(_firestore, _pathComponents);
/// Creates and returns a new [Query] with additional filter on specified
/// [field].
///
/// Only documents satisfying provided condition are included in the result
/// set.
Query where(
String field, {
dynamic isEqualTo,
dynamic isLessThan,
dynamic isLessThanOrEqualTo,
dynamic isGreaterThan,
dynamic isGreaterThanOrEqualTo,
bool isNull,
}) {
final ListEquality<dynamic> equality = const ListEquality<dynamic>();
final List<List<dynamic>> conditions =
new List<List<dynamic>>.from(_parameters['where']);
void addCondition(String field, String operator, dynamic value) {
final List<dynamic> condition = <dynamic>[field, operator, value];
assert(
conditions
.where((List<dynamic> item) => equality.equals(condition, item))
.isEmpty,
'Condition $condition already exists in this query.');
conditions.add(condition);
}
if (isEqualTo != null) addCondition(field, '==', isEqualTo);
if (isLessThan != null) addCondition(field, '<', isLessThan);
if (isLessThanOrEqualTo != null)
addCondition(field, '<=', isLessThanOrEqualTo);
if (isGreaterThan != null) addCondition(field, '>', isGreaterThan);
if (isGreaterThanOrEqualTo != null)
addCondition(field, '>=', isGreaterThanOrEqualTo);
if (isNull != null) {
assert(
isNull,
'isNull can only be set to true. '
'Use isEqualTo to filter on non-null values.');
addCondition(field, '==', null);
}
return _copyWithParameters(<String, dynamic>{'where': conditions});
}
/// Creates and returns a new [Query] that's additionally sorted by the specified
/// [field].
Query orderBy(String field, {bool descending: false}) {
assert(!_parameters.containsKey('orderBy'));
return _copyWithParameters(<String, dynamic>{
'orderBy': <dynamic>[field, descending]
});
}
}