blob: 75a12efeb09f5aec069768b6c88ecaa34720ea84 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:collection';
import 'dart:typed_data';
import '../document/document.dart';
import '../schema/schema.dart';
import '../storage/kv_encoding.dart' as sledge_storage;
import '../uint8list_ops.dart' as utils;
import 'query_field_comparison.dart';
/// Represents a query for retrieving documents from Sledge.
class Query {
final Schema _schema;
/// Stores a QueryFieldComparison each document's field needs respect in
/// order to be returned by the query.
SplayTreeMap<String, QueryFieldComparison> _comparisons;
/// Default constructor.
/// [schema] describes the type of documents the query returns.
/// [comparisons] associates field names with constraints documents returned
/// by the query respects.
/// Throws an exception if [comparisons] references a field not part of
/// [schema], or if multiple inequalities are present.
Query(this._schema, {Map<String, QueryFieldComparison> comparisons}) {
comparisons ??= <String, QueryFieldComparison>{};
final fieldsWithInequalities = <String>[];
comparisons.forEach((fieldPath, comparison) {
if (comparison.comparisonType != ComparisonType.equal) {
fieldsWithInequalities.add(fieldPath);
}
_checkComparisonWithField(fieldPath, comparison);
});
if (fieldsWithInequalities.length > 1) {
throw ArgumentError(
'Queries can have at most one inequality. Inequalities founds: $fieldsWithInequalities.');
}
_comparisons =
SplayTreeMap<String, QueryFieldComparison>.from(comparisons);
}
/// The Schema of documents returned by this query.
Schema get schema => _schema;
/// Returns whether this query filters the Documents based on the content of
/// their fields.
bool filtersDocuments() {
return _comparisons.isNotEmpty;
}
/// The prefix of the key values encoding the index that helps compute the
/// results of this query.
/// Must only be called if `filtersDocuments()` returns true.
Uint8List prefixInIndex() {
assert(filtersDocuments());
final equalityValueHashes = <Uint8List>[];
_comparisons.forEach((field, comparison) {
if (comparison.comparisonType == ComparisonType.equal) {
equalityValueHashes.add(utils.getUint8ListFromString(field));
}
});
Uint8List equalityHash =
utils.hash(utils.concatListOfUint8Lists(equalityValueHashes));
// TODO: get the correct index hash.
Uint8List indexHash = Uint8List(20);
// TODO: take into account the inequality to compute the prefix.
Uint8List prefix = utils.concatListOfUint8Lists([
sledge_storage.prefixForType(sledge_storage.KeyValueType.indexEntry),
indexHash,
equalityHash
]);
return prefix;
}
/// Returns whether [doc] is matched by the query.
/// Throws an error if [doc] is not of the same Schema the query was created
/// with.
bool documentMatchesQuery(Document doc) {
if (doc.documentId.schema != _schema) {
throw ArgumentError(
'The Document `doc` is of an incorrect Schema type.');
}
for (final fieldName in _comparisons.keys) {
if (!_comparisons[fieldName].valueMatchesComparison(doc[fieldName])) {
return false;
}
}
return true;
}
void _checkComparisonWithField(
String fieldPath, QueryFieldComparison comparison) {
final expectedType = _schema.fieldAtPath(fieldPath);
if (!comparison.comparisonValue.comparableTo(expectedType)) {
String runtimeType = expectedType.runtimeType.toString();
throw ArgumentError(
'Field `$fieldPath` of type `$runtimeType` is not comparable with `$comparison.comparisonValue`.');
}
}
}