aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar rsgowman <rgowman@google.com>2018-07-13 16:22:07 -0400
committerGravatar GitHub <noreply@github.com>2018-07-13 16:22:07 -0400
commit920cfbfa998ca9d04cce5f7731aced7e950a3477 (patch)
tree76f62fa05240d533477707bb4cd26df6808cd523
parent81ec26d4ff2f9cc295bf057167a649a34a7a11aa (diff)
Initial (incomplete) implementation of f:f:core::Filter (#1495)
And RelationFilter subclass. Used to implement the next step in core::Query.
-rw-r--r--Firestore/core/src/firebase/firestore/core/CMakeLists.txt4
-rw-r--r--Firestore/core/src/firebase/firestore/core/filter.cc41
-rw-r--r--Firestore/core/src/firebase/firestore/core/filter.h70
-rw-r--r--Firestore/core/src/firebase/firestore/core/query.cc24
-rw-r--r--Firestore/core/src/firebase/firestore/core/query.h28
-rw-r--r--Firestore/core/src/firebase/firestore/core/relation_filter.cc77
-rw-r--r--Firestore/core/src/firebase/firestore/core/relation_filter.h59
-rw-r--r--Firestore/core/src/firebase/firestore/model/field_value.cc34
-rw-r--r--Firestore/core/src/firebase/firestore/model/field_value.h9
-rw-r--r--Firestore/core/test/firebase/firestore/core/query_test.cc15
-rw-r--r--Firestore/core/test/firebase/firestore/testutil/testutil.h40
11 files changed, 372 insertions, 29 deletions
diff --git a/Firestore/core/src/firebase/firestore/core/CMakeLists.txt b/Firestore/core/src/firebase/firestore/core/CMakeLists.txt
index 1aab09a..c1557b2 100644
--- a/Firestore/core/src/firebase/firestore/core/CMakeLists.txt
+++ b/Firestore/core/src/firebase/firestore/core/CMakeLists.txt
@@ -17,10 +17,14 @@ cc_library(
SOURCES
database_info.cc
database_info.h
+ filter.cc
+ filter.h
target_id_generator.cc
target_id_generator.h
query.cc
query.h
+ relation_filter.cc
+ relation_filter.h
DEPENDS
absl_strings
firebase_firestore_model
diff --git a/Firestore/core/src/firebase/firestore/core/filter.cc b/Firestore/core/src/firebase/firestore/core/filter.cc
new file mode 100644
index 0000000..4c27f27
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/core/filter.cc
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Firestore/core/src/firebase/firestore/core/filter.h"
+
+#include <utility>
+
+#include "Firestore/core/src/firebase/firestore/core/relation_filter.h"
+
+namespace firebase {
+namespace firestore {
+namespace core {
+
+using model::FieldPath;
+using model::FieldValue;
+
+std::shared_ptr<Filter> Filter::Create(FieldPath path,
+ Operator op,
+ FieldValue value) {
+ // TODO(rsgowman): Java performs a number of checks here, and then invokes the
+ // ctor of the relevant Filter subclass. Port those checks here.
+ return std::make_shared<RelationFilter>(std::move(path), op,
+ std::move(value));
+}
+
+} // namespace core
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/core/filter.h b/Firestore/core/src/firebase/firestore/core/filter.h
new file mode 100644
index 0000000..9f25a39
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/core/filter.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_CORE_FILTER_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_CORE_FILTER_H_
+
+#include <memory>
+#include <string>
+
+#include "Firestore/core/src/firebase/firestore/model/document.h"
+#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+#include "Firestore/core/src/firebase/firestore/model/field_value.h"
+
+namespace firebase {
+namespace firestore {
+namespace core {
+
+/** Interface used for all query filters. All filters are immutable. */
+class Filter {
+ public:
+ enum class Operator {
+ LessThan,
+ LessThanOrEqual,
+ Equal,
+ GreaterThan,
+ GreaterThanOrEqual,
+ };
+
+ /**
+ * Creates a Filter instance for the provided path, operator, and value.
+ *
+ * Note that if the relational operator is Equal and the value is NullValue or
+ * NaN, then this will return the appropriate NullFilter or NaNFilter class
+ * instead of a RelationFilter.
+ */
+ static std::shared_ptr<Filter> Create(model::FieldPath path,
+ Operator op,
+ model::FieldValue value);
+
+ virtual ~Filter() {
+ }
+
+ /** Returns the field the Filter operates over. */
+ virtual const model::FieldPath& field() const = 0;
+
+ /** Returns true if a document matches the filter. */
+ virtual bool Matches(const model::Document& doc) const = 0;
+
+ /** A unique ID identifying the filter; used when serializing queries. */
+ virtual std::string CanonicalId() const = 0;
+};
+
+} // namespace core
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_CORE_FILTER_H_
diff --git a/Firestore/core/src/firebase/firestore/core/query.cc b/Firestore/core/src/firebase/firestore/core/query.cc
index ad0f7a3..0c0a0fd 100644
--- a/Firestore/core/src/firebase/firestore/core/query.cc
+++ b/Firestore/core/src/firebase/firestore/core/query.cc
@@ -16,8 +16,12 @@
#include "Firestore/core/src/firebase/firestore/core/query.h"
+#include <algorithm>
+
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/field_path.h"
#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+#include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
namespace firebase {
namespace firestore {
@@ -41,9 +45,11 @@ bool Query::MatchesPath(const Document& doc) const {
}
}
-bool Query::MatchesFilters(const Document&) const {
- // TODO(rsgowman): Implement this correctly.
- return true;
+bool Query::MatchesFilters(const Document& doc) const {
+ return std::all_of(filters_.begin(), filters_.end(),
+ [&](const std::shared_ptr<core::Filter>& filter) {
+ return filter->Matches(doc);
+ });
}
bool Query::MatchesOrderBy(const Document&) const {
@@ -56,6 +62,18 @@ bool Query::MatchesBounds(const Document&) const {
return true;
}
+Query Query::Filter(std::shared_ptr<core::Filter> filter) const {
+ HARD_ASSERT(!DocumentKey::IsDocumentKey(path_),
+ "No filter is allowed for document query");
+
+ // TODO(rsgowman): ensure only one inequality field
+ // TODO(rsgowman): ensure first orderby must match inequality field
+
+ std::vector<std::shared_ptr<core::Filter>> updated_filters = filters_;
+ updated_filters.push_back(std::move(filter));
+ return Query(path_, std::move(updated_filters));
+}
+
} // namespace core
} // namespace firestore
} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/core/query.h b/Firestore/core/src/firebase/firestore/core/query.h
index 84d645d..3df1d25 100644
--- a/Firestore/core/src/firebase/firestore/core/query.h
+++ b/Firestore/core/src/firebase/firestore/core/query.h
@@ -17,8 +17,11 @@
#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_CORE_QUERY_H_
#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_CORE_QUERY_H_
+#include <memory>
#include <utility>
+#include <vector>
+#include "Firestore/core/src/firebase/firestore/core/filter.h"
#include "Firestore/core/src/firebase/firestore/model/document.h"
#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
@@ -26,7 +29,10 @@ namespace firebase {
namespace firestore {
namespace core {
-/** Represents the internal structure of a Firestore Query. */
+/**
+ * Represents the internal structure of a Firestore Query. Query instances are
+ * immutable.
+ */
class Query {
public:
/**
@@ -36,14 +42,17 @@ class Query {
* @return A new instance of Query.
*/
static Query AtPath(model::ResourcePath path) {
- return Query(std::move(path));
+ return Query(std::move(path), {});
}
/** Initializes a query with all of its components directly. */
- explicit Query(model::ResourcePath path /* TODO(rsgowman): other params */)
- : path_(std::move(path)) {
+ Query(model::ResourcePath path,
+ std::vector<std::shared_ptr<core::Filter>>
+ filters /* TODO(rsgowman): other params */)
+ : path_(std::move(path)), filters_(std::move(filters)) {
}
+ /** The base path of the query. */
const model::ResourcePath& path() const {
return path_;
}
@@ -51,6 +60,11 @@ class Query {
/** Returns true if the document matches the constraints of this query. */
bool Matches(const model::Document& doc) const;
+ /**
+ * Returns a copy of this Query object with the additional specified filter.
+ */
+ Query Filter(std::shared_ptr<core::Filter> filter) const;
+
private:
bool MatchesPath(const model::Document& doc) const;
bool MatchesFilters(const model::Document& doc) const;
@@ -58,6 +72,12 @@ class Query {
bool MatchesBounds(const model::Document& doc) const;
const model::ResourcePath path_;
+
+ // Filters are shared across related Query instance. i.e. when you call
+ // Query::Filter(f), a new Query instance is created that contains all of the
+ // existing filters, plus the new one. (Both Query and Filter objects are
+ // immutable.) Filters are not shared across unrelated Query instances.
+ const std::vector<std::shared_ptr<core::Filter>> filters_;
};
} // namespace core
diff --git a/Firestore/core/src/firebase/firestore/core/relation_filter.cc b/Firestore/core/src/firebase/firestore/core/relation_filter.cc
new file mode 100644
index 0000000..a6ae916
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/core/relation_filter.cc
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Firestore/core/src/firebase/firestore/core/relation_filter.h"
+
+#include <utility>
+
+#include "absl/types/optional.h"
+
+namespace firebase {
+namespace firestore {
+namespace core {
+
+using model::FieldPath;
+using model::FieldValue;
+
+RelationFilter::RelationFilter(FieldPath field, Operator op, FieldValue value)
+ : field_(std::move(field)), op_(op), value_(std::move(value)) {
+}
+
+const FieldPath& RelationFilter::field() const {
+ return field_;
+}
+
+bool RelationFilter::Matches(const model::Document& doc) const {
+ if (field_.IsKeyFieldPath()) {
+ // TODO(rsgowman): Port this case
+ abort();
+ } else {
+ absl::optional<FieldValue> doc_field_value = doc.field(field_);
+ return doc_field_value && MatchesValue(doc_field_value.value());
+ }
+}
+
+bool RelationFilter::MatchesValue(const FieldValue& other) const {
+ // Only compare types with matching backend order (such as double and int).
+ return FieldValue::Comparable(value_.type(), other.type()) &&
+ MatchesComparison(other);
+}
+
+bool RelationFilter::MatchesComparison(const FieldValue& other) const {
+ switch (op_) {
+ case Operator::LessThan:
+ return value_ < other;
+ case Operator::LessThanOrEqual:
+ return value_ <= other;
+ case Operator::Equal:
+ return value_ == other;
+ case Operator::GreaterThan:
+ return value_ > other;
+ case Operator::GreaterThanOrEqual:
+ return value_ >= other;
+ }
+ UNREACHABLE();
+}
+
+std::string RelationFilter::CanonicalId() const {
+ // TODO(rsgowman): Port this
+ abort();
+}
+
+} // namespace core
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/core/relation_filter.h b/Firestore/core/src/firebase/firestore/core/relation_filter.h
new file mode 100644
index 0000000..94da208
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/core/relation_filter.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_CORE_RELATION_FILTER_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_CORE_RELATION_FILTER_H_
+
+#include <string>
+
+#include "Firestore/core/src/firebase/firestore/core/filter.h"
+#include "Firestore/core/src/firebase/firestore/model/document.h"
+#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+#include "Firestore/core/src/firebase/firestore/model/field_value.h"
+
+namespace firebase {
+namespace firestore {
+namespace core {
+
+/** Represents a filter to be applied to the query. */
+class RelationFilter : public Filter {
+ public:
+ /**
+ * Creates a new filter that compares fields and values. Only intended to be
+ * called from Filter::Create().
+ */
+ RelationFilter(model::FieldPath field, Operator op, model::FieldValue value);
+
+ const model::FieldPath& field() const override;
+
+ bool Matches(const model::Document& doc) const override;
+
+ std::string CanonicalId() const override;
+
+ private:
+ bool MatchesValue(const model::FieldValue& other) const;
+ bool MatchesComparison(const model::FieldValue& other) const;
+
+ const model::FieldPath field_;
+ const Operator op_;
+ const model::FieldValue value_;
+};
+
+} // namespace core
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_CORE_RELATION_FILTER_H_
diff --git a/Firestore/core/src/firebase/firestore/model/field_value.cc b/Firestore/core/src/firebase/firestore/model/field_value.cc
index 4f92c8b..d2fadc8 100644
--- a/Firestore/core/src/firebase/firestore/model/field_value.cc
+++ b/Firestore/core/src/firebase/firestore/model/field_value.cc
@@ -35,25 +35,6 @@ using Type = FieldValue::Type;
using firebase::firestore::util::ComparisonResult;
namespace {
-/**
- * This deviates from the other platforms that define TypeOrder. Since
- * we already define Type for union types, we use it together with this
- * function to achieve the equivalent order of types i.e.
- * i) if two types are comparable, then they are of equal order;
- * ii) otherwise, their order is the same as the order of their Type.
- */
-bool Comparable(Type lhs, Type rhs) {
- switch (lhs) {
- case Type::Integer:
- case Type::Double:
- return rhs == Type::Integer || rhs == Type::Double;
- case Type::Timestamp:
- case Type::ServerTimestamp:
- return rhs == Type::Timestamp || rhs == Type::ServerTimestamp;
- default:
- return lhs == rhs;
- }
-}
// Makes a copy excluding the specified child, which is expected to be assigned
// different value afterwards.
@@ -165,6 +146,19 @@ FieldValue& FieldValue::operator=(FieldValue&& value) {
}
}
+bool FieldValue::Comparable(Type lhs, Type rhs) {
+ switch (lhs) {
+ case Type::Integer:
+ case Type::Double:
+ return rhs == Type::Integer || rhs == Type::Double;
+ case Type::Timestamp:
+ case Type::ServerTimestamp:
+ return rhs == Type::Timestamp || rhs == Type::ServerTimestamp;
+ default:
+ return lhs == rhs;
+ }
+}
+
FieldValue FieldValue::Set(const FieldPath& field_path,
FieldValue value) const {
HARD_ASSERT(type() == Type::Object,
@@ -379,7 +373,7 @@ FieldValue FieldValue::ObjectValueFromMap(ObjectValue::Map&& value) {
}
bool operator<(const FieldValue& lhs, const FieldValue& rhs) {
- if (!Comparable(lhs.type(), rhs.type())) {
+ if (!FieldValue::Comparable(lhs.type(), rhs.type())) {
return lhs.type() < rhs.type();
}
diff --git a/Firestore/core/src/firebase/firestore/model/field_value.h b/Firestore/core/src/firebase/firestore/model/field_value.h
index 09c8531..697d5b3 100644
--- a/Firestore/core/src/firebase/firestore/model/field_value.h
+++ b/Firestore/core/src/firebase/firestore/model/field_value.h
@@ -108,6 +108,15 @@ class FieldValue {
return tag_;
}
+ /**
+ * PORTING NOTE: This deviates from the other platforms that define TypeOrder.
+ * Since we already define Type for union types, we use it together with this
+ * function to achieve the equivalent order of types i.e.
+ * i) if two types are comparable, then they are of equal order;
+ * ii) otherwise, their order is the same as the order of their Type.
+ */
+ static bool Comparable(Type lhs, Type rhs);
+
bool boolean_value() const {
HARD_ASSERT(tag_ == Type::Boolean);
return boolean_value_;
diff --git a/Firestore/core/test/firebase/firestore/core/query_test.cc b/Firestore/core/test/firebase/firestore/core/query_test.cc
index 3ac22a9..6099bee 100644
--- a/Firestore/core/test/firebase/firestore/core/query_test.cc
+++ b/Firestore/core/test/firebase/firestore/core/query_test.cc
@@ -17,6 +17,8 @@
#include "Firestore/core/src/firebase/firestore/core/query.h"
#include "Firestore/core/src/firebase/firestore/model/document.h"
+#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+#include "Firestore/core/src/firebase/firestore/model/field_value.h"
#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
#include "gtest/gtest.h"
@@ -26,7 +28,9 @@ namespace firestore {
namespace core {
using model::Document;
+using model::FieldValue;
using testutil::Doc;
+using testutil::Filter;
TEST(QueryTest, MatchesBasedOnDocumentKey) {
Document doc1 = Doc("rooms/eros/messages/1");
@@ -52,6 +56,17 @@ TEST(QueryTest, MatchesShallowAncestorQuery) {
EXPECT_FALSE(query.Matches(doc3));
}
+TEST(QueryTest, EmptyFieldsAreAllowedForQueries) {
+ Document doc1 = Doc("rooms/eros/messages/1", 0,
+ {{"text", FieldValue::StringValue("msg1")}});
+ Document doc2 = Doc("rooms/eros/messages/2");
+
+ Query query = Query::AtPath({"rooms", "eros", "messages"})
+ .Filter(Filter("text", "==", "msg1"));
+ EXPECT_TRUE(query.Matches(doc1));
+ EXPECT_FALSE(query.Matches(doc2));
+}
+
} // namespace core
} // namespace firestore
} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/testutil/testutil.h b/Firestore/core/test/firebase/firestore/testutil/testutil.h
index c470250..f13c1b7 100644
--- a/Firestore/core/test/firebase/firestore/testutil/testutil.h
+++ b/Firestore/core/test/firebase/firestore/testutil/testutil.h
@@ -19,14 +19,21 @@
#include <chrono> // NOLINT(build/c++11)
#include <cstdint>
+#include <memory>
+#include <string>
+#include <utility>
#include "Firestore/core/include/firebase/firestore/timestamp.h"
+#include "Firestore/core/src/firebase/firestore/core/relation_filter.h"
#include "Firestore/core/src/firebase/firestore/model/document.h"
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+#include "Firestore/core/src/firebase/firestore/model/field_value.h"
#include "Firestore/core/src/firebase/firestore/model/no_document.h"
#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
+#include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
+#include "absl/memory/memory.h"
#include "absl/strings/string_view.h"
namespace firebase {
@@ -59,8 +66,10 @@ inline model::SnapshotVersion Version(int64_t version) {
return model::SnapshotVersion{Timestamp::FromTimePoint(timepoint)};
}
-inline model::Document Doc(absl::string_view key, int64_t version = 0) {
- return model::Document{model::FieldValue::ObjectValueFromMap({}), Key(key),
+inline model::Document Doc(absl::string_view key,
+ int64_t version = 0,
+ const model::ObjectValue::Map& data = {}) {
+ return model::Document{model::FieldValue::ObjectValueFromMap(data), Key(key),
Version(version),
/* has_local_mutations= */ false};
}
@@ -69,6 +78,33 @@ inline model::NoDocument DeletedDoc(absl::string_view key, int64_t version) {
return model::NoDocument{Key(key), Version(version)};
}
+inline core::RelationFilter::Operator OperatorFromString(absl::string_view s) {
+ if (s == "<")
+ return core::RelationFilter::Operator::LessThan;
+ else if (s == "<=")
+ return core::RelationFilter::Operator::LessThanOrEqual;
+ else if (s == "==")
+ return core::RelationFilter::Operator::Equal;
+ else if (s == ">")
+ return core::RelationFilter::Operator::GreaterThan;
+ else if (s == ">=")
+ return core::RelationFilter::Operator::GreaterThanOrEqual;
+ HARD_FAIL("Unknown operator: %s", s);
+}
+
+inline std::shared_ptr<core::Filter> Filter(absl::string_view key,
+ absl::string_view op,
+ model::FieldValue value) {
+ return core::Filter::Create(Field(key), OperatorFromString(op),
+ std::move(value));
+}
+
+inline std::shared_ptr<core::Filter> Filter(absl::string_view key,
+ absl::string_view op,
+ const std::string& value) {
+ return Filter(key, op, model::FieldValue::StringValue(value));
+}
+
// Add a non-inline function to make this library buildable.
// TODO(zxu123): remove once there is non-inline function.
void dummy();