diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index b008b7de78..8565b58141 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -518,6 +518,7 @@ if (IOS) ${FIREBASE_SOURCE_DIR}/firestore/src/include/firebase/firestore/document_snapshot.h ${FIREBASE_SOURCE_DIR}/firestore/src/include/firebase/firestore/field_path.h ${FIREBASE_SOURCE_DIR}/firestore/src/include/firebase/firestore/field_value.h + ${FIREBASE_SOURCE_DIR}/firestore/src/include/firebase/firestore/filter.h ${FIREBASE_SOURCE_DIR}/firestore/src/include/firebase/firestore/listener_registration.h ${FIREBASE_SOURCE_DIR}/firestore/src/include/firebase/firestore/load_bundle_task_progress.h ${FIREBASE_SOURCE_DIR}/firestore/src/include/firebase/firestore/map_field_value.h diff --git a/firestore/CMakeLists.txt b/firestore/CMakeLists.txt index 0cea8d1435..e3a31f3eb8 100644 --- a/firestore/CMakeLists.txt +++ b/firestore/CMakeLists.txt @@ -31,6 +31,7 @@ set(common_SRCS src/common/document_snapshot.cc src/common/exception_common.cc src/common/exception_common.h + src/common/filter.cc src/common/field_path.cc src/common/field_value.cc src/common/firestore.cc @@ -97,6 +98,8 @@ set(android_SRCS src/android/field_path_portable.h src/android/field_value_android.cc src/android/field_value_android.h + src/android/filter_android.cc + src/android/filter_android.h src/android/firestore_android.cc src/android/firestore_android.h src/android/firestore_exceptions_android.h @@ -201,6 +204,8 @@ set(main_SRCS src/main/aggregate_query_snapshot_main.h src/main/collection_reference_main.cc src/main/collection_reference_main.h + src/main/composite_filter_main.cc + src/main/composite_filter_main.h src/main/converter_main.h src/main/document_change_main.cc src/main/document_change_main.h @@ -208,6 +213,8 @@ set(main_SRCS src/main/document_reference_main.h src/main/document_snapshot_main.cc src/main/document_snapshot_main.h + src/main/filter_main.cc + src/main/filter_main.h src/main/field_value_main.cc src/main/field_value_main.h src/main/firestore_main.cc @@ -227,6 +234,8 @@ set(main_SRCS src/main/transaction_main.h src/main/user_data_converter_main.cc src/main/user_data_converter_main.h + src/main/unary_filter_main.cc + src/main/unary_filter_main.h src/main/util_main.h src/main/write_batch_main.cc src/main/write_batch_main.h) diff --git a/firestore/integration_test_internal/CMakeLists.txt b/firestore/integration_test_internal/CMakeLists.txt index 791500961a..8e651e75cd 100644 --- a/firestore/integration_test_internal/CMakeLists.txt +++ b/firestore/integration_test_internal/CMakeLists.txt @@ -91,6 +91,7 @@ set(FIREBASE_INTEGRATION_TEST_PORTABLE_TEST_SRCS # public API are performed. src/integration_test.cc # Internal tests below. + src/aggregate_count_test.cc src/aggregate_query_snapshot_test.cc src/aggregate_query_test.cc src/bundle_test.cc @@ -99,6 +100,7 @@ set(FIREBASE_INTEGRATION_TEST_PORTABLE_TEST_SRCS src/document_change_test.cc src/document_reference_test.cc src/document_snapshot_test.cc + src/filter_test.cc src/field_value_test.cc src/fields_test.cc src/firestore_test.cc diff --git a/firestore/integration_test_internal/integration_test.xcodeproj/project.pbxproj b/firestore/integration_test_internal/integration_test.xcodeproj/project.pbxproj index 4fc6d0ca75..415b4bcff8 100644 --- a/firestore/integration_test_internal/integration_test.xcodeproj/project.pbxproj +++ b/firestore/integration_test_internal/integration_test.xcodeproj/project.pbxproj @@ -21,6 +21,14 @@ 12CCF1E928FDBD9F00C24941 /* set_options_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 12CCF1DF28FDBD9F00C24941 /* set_options_test.cc */; }; 12D513142684C8C200A83FAA /* bundle_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 12D513132684C8C200A83FAA /* bundle_test.cc */; }; 12D5131A2684C8D100A83FAA /* bundle_builder.cc in Sources */ = {isa = PBXBuildFile; fileRef = 12D513182684C8D100A83FAA /* bundle_builder.cc */; }; + 1BAFACA32A449C2B00834979 /* aggregate_query_snapshot_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1BAFACA02A449C2B00834979 /* aggregate_query_snapshot_test.cc */; }; + 1BAFACA42A449C2B00834979 /* aggregate_query_snapshot_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1BAFACA02A449C2B00834979 /* aggregate_query_snapshot_test.cc */; }; + 1BAFACA52A449C2B00834979 /* aggregate_count_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1BAFACA12A449C2B00834979 /* aggregate_count_test.cc */; }; + 1BAFACA62A449C2B00834979 /* aggregate_count_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1BAFACA12A449C2B00834979 /* aggregate_count_test.cc */; }; + 1BAFACA72A449C2B00834979 /* aggregate_query_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1BAFACA22A449C2B00834979 /* aggregate_query_test.cc */; }; + 1BAFACA82A449C2B00834979 /* aggregate_query_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1BAFACA22A449C2B00834979 /* aggregate_query_test.cc */; }; + 1BAFACAA2A449CBD00834979 /* filter_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1BAFACA92A449CBD00834979 /* filter_test.cc */; }; + 1BAFACAB2A449CBD00834979 /* filter_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1BAFACA92A449CBD00834979 /* filter_test.cc */; }; 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */; }; 5270BB448DF5ECE860FDD68B /* firebase_firestore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EAFAF9474EC412ADCC65F2CC /* firebase_firestore.framework */; }; 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D51C85F68000C89379 /* Foundation.framework */; }; @@ -123,6 +131,10 @@ 12D513182684C8D100A83FAA /* bundle_builder.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = bundle_builder.cc; path = src/util/bundle_builder.cc; sourceTree = ""; }; 12D513192684C8D100A83FAA /* bundle_builder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = bundle_builder.h; path = src/util/bundle_builder.h; sourceTree = ""; }; 1B3D64B35A22073C76B376D5 /* libPods-integration_test_tvos.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-integration_test_tvos.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 1BAFACA02A449C2B00834979 /* aggregate_query_snapshot_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = aggregate_query_snapshot_test.cc; path = src/aggregate_query_snapshot_test.cc; sourceTree = ""; }; + 1BAFACA12A449C2B00834979 /* aggregate_count_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = aggregate_count_test.cc; path = src/aggregate_count_test.cc; sourceTree = ""; }; + 1BAFACA22A449C2B00834979 /* aggregate_query_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = aggregate_query_test.cc; path = src/aggregate_query_test.cc; sourceTree = ""; }; + 1BAFACA92A449CBD00834979 /* filter_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = filter_test.cc; path = src/filter_test.cc; sourceTree = ""; }; 3DE393E827F88B06CD3C39CD /* Pods-integration_test_tvos.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-integration_test_tvos.release.xcconfig"; path = "Target Support Files/Pods-integration_test_tvos/Pods-integration_test_tvos.release.xcconfig"; sourceTree = ""; }; 4AAFA3E3DA9641C2E3C46C9D /* Pods_integration_test.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_integration_test.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; @@ -281,6 +293,10 @@ 5292271D1C85FB5500C89379 /* src */ = { isa = PBXGroup; children = ( + 1BAFACA92A449CBD00834979 /* filter_test.cc */, + 1BAFACA12A449C2B00834979 /* aggregate_count_test.cc */, + 1BAFACA02A449C2B00834979 /* aggregate_query_snapshot_test.cc */, + 1BAFACA22A449C2B00834979 /* aggregate_query_test.cc */, 12CCF1DF28FDBD9F00C24941 /* set_options_test.cc */, 12CCF1DB28FDBD9E00C24941 /* settings_test.cc */, 12CCF1DC28FDBD9F00C24941 /* source_test.cc */, @@ -576,12 +592,14 @@ D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */, D61CFBC126091C3B0035CB2A /* integration_test.cc in Sources */, D6AAAD532606C22D0025C53B /* includes_test.cc in Sources */, + 1BAFACA52A449C2B00834979 /* aggregate_count_test.cc in Sources */, D6AAAD502606C22D0025C53B /* numeric_transforms_test.cc in Sources */, D6ED33BE2606CD890058CBF9 /* integration_test_util.cc in Sources */, D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */, 12CCF1E228FDBD9F00C24941 /* source_test.cc in Sources */, D6AAAD4C2606C22D0025C53B /* server_timestamp_test.cc in Sources */, D6AAAD4E2606C22D0025C53B /* firestore_test.cc in Sources */, + 1BAFACAA2A449CBD00834979 /* filter_test.cc in Sources */, D6AAAD452606C22D0025C53B /* document_change_test.cc in Sources */, D6AAAD472606C22D0025C53B /* document_snapshot_test.cc in Sources */, D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */, @@ -591,7 +609,9 @@ EDEEC7632800CD0000EFBAAF /* leveldb_snappy_test.cc in Sources */, 12CCF1E828FDBD9F00C24941 /* set_options_test.cc in Sources */, D6AAAD562606C22D0025C53B /* query_network_test.cc in Sources */, + 1BAFACA72A449C2B00834979 /* aggregate_query_test.cc in Sources */, D6AAAD552606C22D0025C53B /* listener_registration_test.cc in Sources */, + 1BAFACA32A449C2B00834979 /* aggregate_query_snapshot_test.cc in Sources */, 12D5131A2684C8D100A83FAA /* bundle_builder.cc in Sources */, D6AAAD4A2606C22D0025C53B /* fields_test.cc in Sources */, D6AAAD462606C22D0025C53B /* query_test.cc in Sources */, @@ -610,6 +630,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1BAFACAB2A449CBD00834979 /* filter_test.cc in Sources */, + 1BAFACA62A449C2B00834979 /* aggregate_count_test.cc in Sources */, BC1D6850267B00EB005DC2DA /* app_framework.cc in Sources */, BC1D6853267B00EB005DC2DA /* transaction_extra_test.cc in Sources */, BC1D683E267B00EB005DC2DA /* integration_test_util.cc in Sources */, @@ -623,6 +645,7 @@ BC1D6848267B00EB005DC2DA /* sanity_test.cc in Sources */, 12CCF1E728FDBD9F00C24941 /* write_batch_test.cc in Sources */, 12CCF1E128FDBD9F00C24941 /* settings_test.cc in Sources */, + 1BAFACA82A449C2B00834979 /* aggregate_query_test.cc in Sources */, BC1D6856267B00EE005DC2DA /* ios_app_framework.mm in Sources */, BC1D6843267B00EB005DC2DA /* numeric_transforms_test.cc in Sources */, BC1D6844267B00EB005DC2DA /* array_transform_test.cc in Sources */, @@ -632,6 +655,7 @@ BC1D684E267B00EB005DC2DA /* includes_test.cc in Sources */, BC1D684C267B00EB005DC2DA /* document_change_test.cc in Sources */, BC1D6851267B00EB005DC2DA /* firestore_integration_test.cc in Sources */, + 1BAFACA42A449C2B00834979 /* aggregate_query_snapshot_test.cc in Sources */, BC1D6838267B00EB005DC2DA /* future_test_util.cc in Sources */, 12CCF1E528FDBD9F00C24941 /* validation_test.cc in Sources */, BC1D6839267B00EB005DC2DA /* type_test.cc in Sources */, diff --git a/firestore/integration_test_internal/src/aggregate_count_test.cc b/firestore/integration_test_internal/src/aggregate_count_test.cc index dc58961fc3..7f3f462a1f 100644 --- a/firestore/integration_test_internal/src/aggregate_count_test.cc +++ b/firestore/integration_test_internal/src/aggregate_count_test.cc @@ -28,15 +28,9 @@ #include "firestore_integration_test.h" #include "util/event_accumulator.h" -#if defined(__ANDROID__) -#include "firestore/src/android/query_android.h" -#include "firestore/src/common/wrapper_assertions.h" -#endif // defined(__ANDROID__) - #include "Firestore/core/src/util/firestore_exceptions.h" #include "firebase/firestore/firestore_errors.h" #include "firebase_test_framework.h" -#include "gmock/gmock.h" #include "gtest/gtest.h" namespace firebase { @@ -757,15 +751,5 @@ TEST_F(AggregateCountTest, EXPECT_EQ(aggregate_query2, aggregate_snapshot2.query()); } -#if defined(__ANDROID__) -TEST(QueryTestAndroidStub, Construction) { - testutil::AssertWrapperConstructionContract(); -} - -TEST(QueryTestAndroidStub, Assignment) { - testutil::AssertWrapperAssignmentContract(); -} -#endif // defined(__ANDROID__) - } // namespace firestore } // namespace firebase diff --git a/firestore/integration_test_internal/src/aggregate_query_test.cc b/firestore/integration_test_internal/src/aggregate_query_test.cc index 4a52be9790..4810e5e96f 100644 --- a/firestore/integration_test_internal/src/aggregate_query_test.cc +++ b/firestore/integration_test_internal/src/aggregate_query_test.cc @@ -17,6 +17,11 @@ #include "firebase/firestore.h" #include "firestore_integration_test.h" +#if defined(__ANDROID__) +#include "firestore/src/android/aggregate_query_android.h" +#include "firestore/src/common/wrapper_assertions.h" +#endif // defined(__ANDROID__) + #include "gtest/gtest.h" namespace firebase { @@ -332,6 +337,16 @@ TEST_F(AggregateQueryTest, TestHashCode) { AggregateQueryHash(query1.Count())); } +#if defined(__ANDROID__) +TEST(QueryTestAndroidStub, Construction) { + testutil::AssertWrapperConstructionContract(); +} + +TEST(QueryTestAndroidStub, Assignment) { + testutil::AssertWrapperAssignmentContract(); +} +#endif // defined(__ANDROID__) + } // namespace } // namespace firestore } // namespace firebase diff --git a/firestore/integration_test_internal/src/filter_test.cc b/firestore/integration_test_internal/src/filter_test.cc new file mode 100644 index 0000000000..e3806943d0 --- /dev/null +++ b/firestore/integration_test_internal/src/filter_test.cc @@ -0,0 +1,550 @@ +/* + * Copyright 2023 Google LLC + * + * 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 "firebase/firestore.h" +#include "firestore_integration_test.h" + +namespace firebase { +namespace firestore { +namespace { + +using FilterTest = FirestoreIntegrationTest; + +TEST_F(FilterTest, CopyConstructorReturnsEqualObject) { + const Filter filter1a = Filter::EqualTo("foo", FieldValue::Integer(42)); + const Filter filter2a = Filter::ArrayContainsAny( + "bar", {FieldValue::Integer(4), FieldValue::Integer(2)}); + const Filter filter3a = Filter::And(filter1a, filter2a); + + const Filter filter1b(filter1a); + const Filter filter2b(filter2a); + const Filter filter3b(filter3a); + + EXPECT_EQ(filter1a, filter1b); + EXPECT_EQ(filter2a, filter2b); + EXPECT_EQ(filter3a, filter3b); +} + +TEST_F(FilterTest, CopyAssignementReturnsEqualObject) { + const Filter filter1 = Filter::EqualTo("foo", FieldValue::Integer(42)); + const Filter filter2 = Filter::ArrayContainsAny( + "bar", {FieldValue::Integer(4), FieldValue::Integer(2)}); + const Filter filter3 = Filter::And(filter1, filter2); + + Filter filter = Filter::And(); + + EXPECT_NE(filter, filter1); + EXPECT_NE(filter, filter2); + EXPECT_NE(filter, filter3); + + filter = filter1; + + EXPECT_EQ(filter, filter1); + EXPECT_NE(filter, filter2); + EXPECT_NE(filter, filter3); + + filter = filter2; + + EXPECT_NE(filter, filter1); + EXPECT_EQ(filter, filter2); + EXPECT_NE(filter, filter3); + + filter = filter3; + + EXPECT_NE(filter, filter1); + EXPECT_NE(filter, filter2); + EXPECT_EQ(filter, filter3); +} + +TEST_F(FilterTest, MoveConstructorReturnsEqualObject) { + Filter filter1a = Filter::EqualTo("foo", FieldValue::Integer(42)); + Filter filter2a = Filter::ArrayContainsAny( + "bar", {FieldValue::Integer(4), FieldValue::Integer(2)}); + Filter filter3a = Filter::And(filter1a, filter2a); + + Filter filter1b(std::move(filter1a)); + EXPECT_EQ(filter1b, Filter::EqualTo("foo", FieldValue::Integer(42))); + + Filter filter2b(std::move(filter2a)); + EXPECT_EQ(filter2b, + Filter::ArrayContainsAny( + "bar", {FieldValue::Integer(4), FieldValue::Integer(2)})); + + Filter filter3b(std::move(filter3a)); + EXPECT_EQ(filter3b, Filter::And(filter1b, filter2b)); +} + +TEST_F(FilterTest, MoveAssignmentReturnsEqualObject) { + Filter filter1a = Filter::EqualTo("foo", FieldValue::Integer(42)); + Filter filter2a = Filter::ArrayContainsAny( + "bar", {FieldValue::Integer(4), FieldValue::Integer(2)}); + Filter filter3a = Filter::And(filter1a, filter2a); + + Filter filter1b = std::move(filter1a); + EXPECT_EQ(filter1b, Filter::EqualTo("foo", FieldValue::Integer(42))); + + Filter filter2b = std::move(filter2a); + EXPECT_EQ(filter2b, + Filter::ArrayContainsAny( + "bar", {FieldValue::Integer(4), FieldValue::Integer(2)})); + + Filter filter3b = std::move(filter3a); + EXPECT_EQ(filter3b, Filter::And(filter1b, filter2b)); +} + +TEST_F(FilterTest, MoveAssignmentAppliedToSelfReturnsEqualObject) { + Filter filter1 = Filter::EqualTo("foo", FieldValue::Integer(42)); + Filter filter2 = Filter::ArrayContainsAny( + "bar", {FieldValue::Integer(4), FieldValue::Integer(2)}); + Filter filter3 = Filter::And(filter1, filter2); + + filter1 = std::move(filter1); + EXPECT_EQ(filter1, Filter::EqualTo("foo", FieldValue::Integer(42))); + + filter2 = std::move(filter2); + EXPECT_EQ(filter2, Filter::ArrayContainsAny("bar", {FieldValue::Integer(4), + FieldValue::Integer(2)})); + + filter3 = std::move(filter3); + EXPECT_EQ(filter3, Filter::And(filter1, filter2)); +} + +TEST_F(FilterTest, IdenticalFilterShouldBeEqual) { + FieldPath foo_path{std::vector{"foo"}}; + + Filter filter1a = Filter::ArrayContains("foo", FieldValue::Integer(42)); + Filter filter1b = Filter::ArrayContains(foo_path, FieldValue::Integer(42)); + + Filter filter2a = Filter::ArrayContainsAny("foo", {FieldValue::Integer(42)}); + Filter filter2b = + Filter::ArrayContainsAny(foo_path, {FieldValue::Integer(42)}); + + Filter filter3a = Filter::EqualTo("foo", FieldValue::Integer(42)); + Filter filter3b = Filter::EqualTo(foo_path, FieldValue::Integer(42)); + + Filter filter4a = Filter::NotEqualTo("foo", FieldValue::Integer(42)); + Filter filter4b = Filter::NotEqualTo(foo_path, FieldValue::Integer(42)); + + Filter filter5a = Filter::GreaterThan("foo", FieldValue::Integer(42)); + Filter filter5b = Filter::GreaterThan(foo_path, FieldValue::Integer(42)); + + Filter filter6a = + Filter::GreaterThanOrEqualTo("foo", FieldValue::Integer(42)); + Filter filter6b = + Filter::GreaterThanOrEqualTo(foo_path, FieldValue::Integer(42)); + + Filter filter7a = Filter::LessThan("foo", FieldValue::Integer(42)); + Filter filter7b = Filter::LessThan(foo_path, FieldValue::Integer(42)); + + Filter filter8a = Filter::LessThanOrEqualTo("foo", FieldValue::Integer(42)); + Filter filter8b = + Filter::LessThanOrEqualTo(foo_path, FieldValue::Integer(42)); + + Filter filter9a = Filter::In("foo", {FieldValue::Integer(42)}); + Filter filter9b = Filter::In(foo_path, {FieldValue::Integer(42)}); + + Filter filter10a = Filter::NotIn("foo", {FieldValue::Integer(42)}); + Filter filter10b = Filter::NotIn(foo_path, {FieldValue::Integer(42)}); + + Filter filter11a = Filter::And(filter1a, filter2a); + Filter filter11b = Filter::And(filter1b, filter2b); + + Filter filter12a = + Filter::Or(filter3a, filter4a, filter5a, filter6a, filter7a); + Filter filter12b = + Filter::Or(filter3b, filter4b, filter5b, filter6b, filter7b); + + EXPECT_TRUE(filter1a == filter1a); + EXPECT_TRUE(filter2a == filter2a); + EXPECT_TRUE(filter3a == filter3a); + EXPECT_TRUE(filter4a == filter4a); + EXPECT_TRUE(filter5a == filter5a); + EXPECT_TRUE(filter6a == filter6a); + EXPECT_TRUE(filter7a == filter7a); + EXPECT_TRUE(filter8a == filter8a); + EXPECT_TRUE(filter9a == filter9a); + EXPECT_TRUE(filter10a == filter10a); + EXPECT_TRUE(filter11a == filter11a); + EXPECT_TRUE(filter12a == filter12a); + + EXPECT_TRUE(filter1a == filter1b); + EXPECT_TRUE(filter2a == filter2b); + EXPECT_TRUE(filter3a == filter3b); + EXPECT_TRUE(filter4a == filter4b); + EXPECT_TRUE(filter5a == filter5b); + EXPECT_TRUE(filter6a == filter6b); + EXPECT_TRUE(filter7a == filter7b); + EXPECT_TRUE(filter8a == filter8b); + EXPECT_TRUE(filter9a == filter9b); + EXPECT_TRUE(filter10a == filter10b); + EXPECT_TRUE(filter11a == filter11b); + EXPECT_TRUE(filter12a == filter12b); + + EXPECT_FALSE(filter1a != filter1a); + EXPECT_FALSE(filter2a != filter2a); + EXPECT_FALSE(filter3a != filter3a); + EXPECT_FALSE(filter4a != filter4a); + EXPECT_FALSE(filter5a != filter5a); + EXPECT_FALSE(filter6a != filter6a); + EXPECT_FALSE(filter7a != filter7a); + EXPECT_FALSE(filter8a != filter8a); + EXPECT_FALSE(filter9a != filter9a); + EXPECT_FALSE(filter10a != filter10a); + EXPECT_FALSE(filter11a != filter11a); + EXPECT_FALSE(filter12a != filter12a); + + EXPECT_FALSE(filter1a != filter1b); + EXPECT_FALSE(filter2a != filter2b); + EXPECT_FALSE(filter3a != filter3b); + EXPECT_FALSE(filter4a != filter4b); + EXPECT_FALSE(filter5a != filter5b); + EXPECT_FALSE(filter6a != filter6b); + EXPECT_FALSE(filter7a != filter7b); + EXPECT_FALSE(filter8a != filter8b); + EXPECT_FALSE(filter9a != filter9b); + EXPECT_FALSE(filter10a != filter10b); + EXPECT_FALSE(filter11a != filter11b); + EXPECT_FALSE(filter12a != filter12b); + + EXPECT_TRUE(filter1a != filter2a); + EXPECT_TRUE(filter1a != filter3a); + EXPECT_TRUE(filter1a != filter4a); + EXPECT_TRUE(filter1a != filter5a); + EXPECT_TRUE(filter1a != filter6a); + EXPECT_TRUE(filter1a != filter7a); + EXPECT_TRUE(filter1a != filter8a); + EXPECT_TRUE(filter1a != filter9a); + EXPECT_TRUE(filter1a != filter10a); + EXPECT_TRUE(filter1a != filter11a); + EXPECT_TRUE(filter1a != filter12a); + EXPECT_TRUE(filter2a != filter3a); + EXPECT_TRUE(filter2a != filter4a); + EXPECT_TRUE(filter2a != filter5a); + EXPECT_TRUE(filter2a != filter6a); + EXPECT_TRUE(filter2a != filter7a); + EXPECT_TRUE(filter2a != filter8a); + EXPECT_TRUE(filter2a != filter9a); + EXPECT_TRUE(filter2a != filter10a); + EXPECT_TRUE(filter2a != filter11a); + EXPECT_TRUE(filter2a != filter12a); + EXPECT_TRUE(filter3a != filter4a); + EXPECT_TRUE(filter3a != filter5a); + EXPECT_TRUE(filter3a != filter6a); + EXPECT_TRUE(filter3a != filter7a); + EXPECT_TRUE(filter3a != filter8a); + EXPECT_TRUE(filter3a != filter9a); + EXPECT_TRUE(filter3a != filter10a); + EXPECT_TRUE(filter3a != filter11a); + EXPECT_TRUE(filter3a != filter12a); + EXPECT_TRUE(filter4a != filter5a); + EXPECT_TRUE(filter4a != filter6a); + EXPECT_TRUE(filter4a != filter7a); + EXPECT_TRUE(filter4a != filter8a); + EXPECT_TRUE(filter4a != filter9a); + EXPECT_TRUE(filter4a != filter10a); + EXPECT_TRUE(filter4a != filter11a); + EXPECT_TRUE(filter4a != filter12a); + EXPECT_TRUE(filter5a != filter6a); + EXPECT_TRUE(filter5a != filter7a); + EXPECT_TRUE(filter5a != filter8a); + EXPECT_TRUE(filter5a != filter9a); + EXPECT_TRUE(filter5a != filter10a); + EXPECT_TRUE(filter5a != filter11a); + EXPECT_TRUE(filter5a != filter12a); + EXPECT_TRUE(filter6a != filter7a); + EXPECT_TRUE(filter6a != filter8a); + EXPECT_TRUE(filter6a != filter9a); + EXPECT_TRUE(filter6a != filter10a); + EXPECT_TRUE(filter6a != filter11a); + EXPECT_TRUE(filter6a != filter12a); + EXPECT_TRUE(filter7a != filter8a); + EXPECT_TRUE(filter7a != filter9a); + EXPECT_TRUE(filter7a != filter10a); + EXPECT_TRUE(filter7a != filter11a); + EXPECT_TRUE(filter7a != filter12a); + EXPECT_TRUE(filter8a != filter9a); + EXPECT_TRUE(filter8a != filter10a); + EXPECT_TRUE(filter8a != filter11a); + EXPECT_TRUE(filter8a != filter12a); + EXPECT_TRUE(filter9a != filter10a); + EXPECT_TRUE(filter9a != filter11a); + EXPECT_TRUE(filter9a != filter12a); + EXPECT_TRUE(filter10a != filter11a); + EXPECT_TRUE(filter10a != filter12a); + EXPECT_TRUE(filter11a != filter12a); +} + +TEST_F(FilterTest, DifferentValuesAreNotEqual) { + Filter filter1a = Filter::ArrayContains("foo", FieldValue::Integer(24)); + Filter filter1b = Filter::ArrayContains("foo", FieldValue::Integer(42)); + Filter filter1c = Filter::ArrayContains("bar", FieldValue::Integer(42)); + + Filter filter2a = Filter::EqualTo("foo", FieldValue::Integer(24)); + Filter filter2b = Filter::EqualTo("foo", FieldValue::Integer(42)); + Filter filter2c = Filter::EqualTo("bar", FieldValue::Integer(42)); + + Filter filter3a = Filter::NotEqualTo("foo", FieldValue::Integer(24)); + Filter filter3b = Filter::NotEqualTo("foo", FieldValue::Integer(42)); + Filter filter3c = Filter::NotEqualTo("bar", FieldValue::Integer(42)); + + Filter filter4a = Filter::GreaterThan("foo", FieldValue::Integer(24)); + Filter filter4b = Filter::GreaterThan("foo", FieldValue::Integer(42)); + Filter filter4c = Filter::GreaterThan("bar", FieldValue::Integer(42)); + + Filter filter5a = + Filter::GreaterThanOrEqualTo("foo", FieldValue::Integer(24)); + Filter filter5b = + Filter::GreaterThanOrEqualTo("foo", FieldValue::Integer(42)); + Filter filter5c = + Filter::GreaterThanOrEqualTo("bar", FieldValue::Integer(42)); + + Filter filter6a = Filter::LessThan("foo", FieldValue::Integer(24)); + Filter filter6b = Filter::LessThan("foo", FieldValue::Integer(42)); + Filter filter6c = Filter::LessThan("bar", FieldValue::Integer(42)); + + Filter filter7a = Filter::LessThanOrEqualTo("foo", FieldValue::Integer(24)); + Filter filter7b = Filter::LessThanOrEqualTo("foo", FieldValue::Integer(42)); + Filter filter7c = Filter::LessThanOrEqualTo("bar", FieldValue::Integer(42)); + + EXPECT_FALSE(filter1a == filter1b); + EXPECT_FALSE(filter1b == filter1c); + EXPECT_FALSE(filter2a == filter2b); + EXPECT_FALSE(filter2b == filter2c); + EXPECT_FALSE(filter3a == filter3b); + EXPECT_FALSE(filter3b == filter3c); + EXPECT_FALSE(filter4a == filter4b); + EXPECT_FALSE(filter4b == filter4c); + EXPECT_FALSE(filter5a == filter5b); + EXPECT_FALSE(filter5b == filter5c); + EXPECT_FALSE(filter6a == filter6b); + EXPECT_FALSE(filter6b == filter6c); + EXPECT_FALSE(filter7a == filter7b); + EXPECT_FALSE(filter7b == filter7c); + + EXPECT_TRUE(filter1a != filter1b); + EXPECT_TRUE(filter1b != filter1c); + EXPECT_TRUE(filter2a != filter2b); + EXPECT_TRUE(filter2b != filter2c); + EXPECT_TRUE(filter3a != filter3b); + EXPECT_TRUE(filter3b != filter3c); + EXPECT_TRUE(filter4a != filter4b); + EXPECT_TRUE(filter4b != filter4c); + EXPECT_TRUE(filter5a != filter5b); + EXPECT_TRUE(filter5b != filter5c); + EXPECT_TRUE(filter6a != filter6b); + EXPECT_TRUE(filter6b != filter6c); + EXPECT_TRUE(filter7a != filter7b); + EXPECT_TRUE(filter7b != filter7c); +} + +TEST_F(FilterTest, CompositesWithOneFilterAreTheSameAsFilter) { + Filter filter1 = Filter::EqualTo("foo", FieldValue::Integer(42)); + Filter filter2 = Filter::Or(filter1); + Filter filter3 = Filter::And(filter1); + + EXPECT_TRUE(filter1 == filter2); + EXPECT_TRUE(filter1 == filter3); + + EXPECT_FALSE(filter1 != filter2); + EXPECT_FALSE(filter1 != filter3); +} + +TEST_F(FilterTest, EmptyCompositeIsIgnoredByCompositesAndQueries) { + Filter filter1 = Filter::And(); + Filter filter2 = Filter::And(Filter::And(), Filter::And()); + Filter filter3 = Filter::And(Filter::Or(), Filter::Or()); + Filter filter4 = Filter::Or(); + Filter filter5 = Filter::Or(Filter::Or(), Filter::Or()); + Filter filter6 = Filter::Or(Filter::And(), Filter::And()); + + EXPECT_EQ(filter1, filter2); + EXPECT_EQ(filter1, filter3); + EXPECT_EQ(filter4, filter5); + EXPECT_EQ(filter4, filter6); + + CollectionReference collection = Collection(); + + Query query1 = collection.Where(filter1); + Query query2 = collection.Where(filter2); + Query query3 = collection.Where(filter3); + Query query4 = collection.Where(filter4); + Query query5 = collection.Where(filter5); + Query query6 = collection.Where(filter6); + + EXPECT_EQ(collection, query1); + EXPECT_EQ(collection, query2); + EXPECT_EQ(collection, query3); + EXPECT_EQ(collection, query4); + EXPECT_EQ(collection, query5); + EXPECT_EQ(collection, query6); +} + +TEST_F(FilterTest, CompositeComparison) { + Filter filter1 = Filter::ArrayContains("foo", FieldValue::Integer(42)); + Filter filter2 = Filter::EqualTo("foo", FieldValue::Integer(42)); + Filter filter3 = Filter::NotEqualTo("foo", FieldValue::Integer(42)); + Filter filter4 = Filter::GreaterThan("foo", FieldValue::Integer(42)); + + Filter and1 = Filter::And(filter1); + Filter and2 = Filter::And(filter1, filter2); + Filter and3 = Filter::And(filter1, filter2, filter3); + Filter and4 = Filter::And(filter1, filter2, filter3, filter4); + + Filter or1 = Filter::Or(filter1); + Filter or2 = Filter::Or(filter1, filter2); + Filter or3 = Filter::Or(filter1, filter2, filter3); + Filter or4 = Filter::Or(filter1, filter2, filter3, filter4); + + EXPECT_EQ(and1, and1); + EXPECT_EQ(and2, and2); + EXPECT_EQ(and3, and3); + EXPECT_EQ(and4, and4); + + EXPECT_EQ(or1, or1); + EXPECT_EQ(or2, or2); + EXPECT_EQ(or3, or3); + EXPECT_EQ(or4, or4); + + // Is equal because single filter composite is same as filter itself. + EXPECT_EQ(and1, or1); + + EXPECT_NE(and2, or2); + EXPECT_NE(and3, or3); + EXPECT_NE(and4, or4); + + EXPECT_NE(and1, and2); + EXPECT_NE(and1, and3); + EXPECT_NE(and1, and4); + EXPECT_NE(and2, and3); + EXPECT_NE(and2, and4); + EXPECT_NE(and3, and4); + + EXPECT_NE(or1, or2); + EXPECT_NE(or1, or3); + EXPECT_NE(or1, or4); + EXPECT_NE(or2, or3); + EXPECT_NE(or2, or4); + EXPECT_NE(or3, or4); +} + +TEST_F(FilterTest, QueryWhereComposite) { + MapFieldValue doc_aaa = {{"x", FieldValue::String("a")}, + {"y", FieldValue::String("a")}, + {"z", FieldValue::String("a")}}; + MapFieldValue doc_aab = {{"x", FieldValue::String("a")}, + {"y", FieldValue::String("a")}, + {"z", FieldValue::String("b")}}; + MapFieldValue doc_aba = {{"x", FieldValue::String("a")}, + {"y", FieldValue::String("b")}, + {"z", FieldValue::String("a")}}; + MapFieldValue doc_abb = {{"x", FieldValue::String("a")}, + {"y", FieldValue::String("b")}, + {"z", FieldValue::String("b")}}; + MapFieldValue doc_baa = {{"x", FieldValue::String("b")}, + {"y", FieldValue::String("a")}, + {"z", FieldValue::String("a")}}; + MapFieldValue doc_bab = {{"x", FieldValue::String("b")}, + {"y", FieldValue::String("a")}, + {"z", FieldValue::String("b")}}; + MapFieldValue doc_bba = {{"x", FieldValue::String("b")}, + {"y", FieldValue::String("b")}, + {"z", FieldValue::String("a")}}; + MapFieldValue doc_bbb = {{"x", FieldValue::String("b")}, + {"y", FieldValue::String("b")}, + {"z", FieldValue::String("b")}}; + CollectionReference collection = Collection({{"aaa", doc_aaa}, + {"aab", doc_aab}, + {"aba", doc_aba}, + {"abb", doc_abb}, + {"baa", doc_baa}, + {"bab", doc_bab}, + {"bba", doc_bba}, + {"bbb", doc_bbb}}); + + Filter filter_xa = Filter::EqualTo("x", FieldValue::String("a")); + Filter filter_ya = Filter::EqualTo("y", FieldValue::String("a")); + Filter filter_yb = Filter::EqualTo("y", FieldValue::String("b")); + Filter filter_za = Filter::EqualTo("z", FieldValue::String("a")); + + // And(x=a) + QuerySnapshot snapshot1 = + ReadDocuments(collection.Where(Filter::And(filter_xa))); + EXPECT_EQ(std::vector({doc_aaa, doc_aab, doc_aba, doc_abb}), + QuerySnapshotToValues(snapshot1)); + + // And(x=a, y=b) + QuerySnapshot snapshot2 = + ReadDocuments(collection.Where(Filter::And(filter_xa, filter_yb))); + EXPECT_EQ(std::vector({doc_aba, doc_abb}), + QuerySnapshotToValues(snapshot2)); + + // And(Or(And(x=a)),Or(And(Or())) + QuerySnapshot snapshot3 = ReadDocuments( + collection.Where(Filter::And(Filter::Or(Filter::And(filter_xa)), + Filter::Or(Filter::And(Filter::Or()))))); + EXPECT_EQ(std::vector({doc_aaa, doc_aab, doc_aba, doc_abb}), + QuerySnapshotToValues(snapshot3)); + + // Or(x=a) + QuerySnapshot snapshot4 = + ReadDocuments(collection.Where(Filter::Or(filter_xa))); + EXPECT_EQ(std::vector({doc_aaa, doc_aab, doc_aba, doc_abb}), + QuerySnapshotToValues(snapshot4)); + + // Or(x=a, y=b) + QuerySnapshot snapshot5 = + ReadDocuments(collection.Where(Filter::Or(filter_xa, filter_yb))); + EXPECT_EQ(std::vector( + {doc_aaa, doc_aab, doc_aba, doc_abb, doc_bba, doc_bbb}), + QuerySnapshotToValues(snapshot5)); + + // Or(And(Or(x=a)),And(Or(And())) + QuerySnapshot snapshot6 = ReadDocuments( + collection.Where(Filter::Or(Filter::And(Filter::Or(filter_xa)), + Filter::And(Filter::Or(Filter::And()))))); + EXPECT_EQ(std::vector({doc_aaa, doc_aab, doc_aba, doc_abb}), + QuerySnapshotToValues(snapshot6)); + + // And(x=b, Or(y=a, And(y=b, z=a))) + QuerySnapshot snapshot7 = ReadDocuments(collection.Where(Filter::And( + filter_xa, Filter::Or(filter_ya, Filter::And(filter_yb, filter_za))))); + EXPECT_EQ(std::vector({doc_aaa, doc_aab, doc_aba}), + QuerySnapshotToValues(snapshot7)); +} + +TEST_F(FilterTest, QueryEmptyWhereComposite) { + MapFieldValue doc = {{"foo", FieldValue::String("bar")}}; + CollectionReference collection = Collection({{"x", doc}}); + + QuerySnapshot s1 = ReadDocuments(collection.Where(Filter::And())); + EXPECT_EQ(std::vector({doc}), QuerySnapshotToValues(s1)); + + QuerySnapshot s2 = + ReadDocuments(collection.Where(Filter::And(Filter::Or(), Filter::Or()))); + EXPECT_EQ(std::vector({doc}), QuerySnapshotToValues(s2)); + + QuerySnapshot s3 = ReadDocuments(collection.Where(Filter::Or())); + EXPECT_EQ(std::vector({doc}), QuerySnapshotToValues(s3)); + + QuerySnapshot s4 = + ReadDocuments(collection.Where(Filter::Or(Filter::And(), Filter::And()))); + EXPECT_EQ(std::vector({doc}), QuerySnapshotToValues(s4)); +} + +} // namespace + +} // namespace firestore +} // namespace firebase diff --git a/firestore/src/android/filter_android.cc b/firestore/src/android/filter_android.cc new file mode 100644 index 0000000000..e81cd3f926 --- /dev/null +++ b/firestore/src/android/filter_android.cc @@ -0,0 +1,209 @@ +/* + * Copyright 2023 Google LLC + * + * 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 + +#include "firestore/src/android/filter_android.h" + +#include "firestore/src/android/converter_android.h" +#include "firestore/src/android/field_path_android.h" +#include "firestore/src/android/field_value_android.h" +#include "firestore/src/android/firestore_android.h" + +#include "firestore/src/jni/array.h" +#include "firestore/src/jni/array_list.h" +#include "firestore/src/jni/compare.h" +#include "firestore/src/jni/env.h" +#include "firestore/src/jni/loader.h" + +namespace firebase { +namespace firestore { +namespace { + +using jni::Array; +using jni::ArrayList; +using jni::Env; +using jni::Local; +using jni::Object; +using jni::StaticMethod; + +constexpr char kClassName[] = + PROGUARD_KEEP_CLASS "com/google/firebase/firestore/Filter"; +StaticMethod kEqualTo( + "equalTo", + "(Lcom/google/firebase/firestore/FieldPath;Ljava/lang/Object;)" + "Lcom/google/firebase/firestore/Filter;"); +StaticMethod kNotEqualTo( + "notEqualTo", + "(Lcom/google/firebase/firestore/FieldPath;Ljava/lang/Object;)" + "Lcom/google/firebase/firestore/Filter;"); +StaticMethod kLessThan( + "lessThan", + "(Lcom/google/firebase/firestore/FieldPath;Ljava/lang/Object;)" + "Lcom/google/firebase/firestore/Filter;"); +StaticMethod kLessThanOrEqualTo( + "lessThanOrEqualTo", + "(Lcom/google/firebase/firestore/FieldPath;Ljava/lang/Object;)" + "Lcom/google/firebase/firestore/Filter;"); +StaticMethod kGreaterThan( + "greaterThan", + "(Lcom/google/firebase/firestore/FieldPath;Ljava/lang/Object;)" + "Lcom/google/firebase/firestore/Filter;"); +StaticMethod kGreaterThanOrEqualTo( + "greaterThanOrEqualTo", + "(Lcom/google/firebase/firestore/FieldPath;Ljava/lang/Object;)" + "Lcom/google/firebase/firestore/Filter;"); +StaticMethod kArrayContains( + "arrayContains", + "(Lcom/google/firebase/firestore/FieldPath;Ljava/lang/Object;)" + "Lcom/google/firebase/firestore/Filter;"); +StaticMethod kArrayContainsAny( + "arrayContainsAny", + "(Lcom/google/firebase/firestore/FieldPath;Ljava/util/List;)" + "Lcom/google/firebase/firestore/Filter;"); +StaticMethod kIn( + "in", + "(Lcom/google/firebase/firestore/FieldPath;Ljava/util/List;)" + "Lcom/google/firebase/firestore/Filter;"); +StaticMethod kNotIn( + "notIn", + "(Lcom/google/firebase/firestore/FieldPath;Ljava/util/List;)" + "Lcom/google/firebase/firestore/Filter;"); +StaticMethod kAnd("and", + "([Lcom/google/firebase/firestore/Filter;)" + "Lcom/google/firebase/firestore/Filter;"); +StaticMethod kOr("or", + "([Lcom/google/firebase/firestore/Filter;)" + "Lcom/google/firebase/firestore/Filter;"); +} // namespace + +void FilterInternal::Initialize(jni::Loader& loader) { + loader.LoadClass(kClassName, kEqualTo, kNotEqualTo, kLessThan, + kLessThanOrEqualTo, kGreaterThan, kGreaterThanOrEqualTo, + kArrayContains, kArrayContainsAny, kIn, kNotIn, kAnd, kOr); +} + +FilterInternal::FilterInternal(jni::Object&& object, bool is_empty) + : object_(object), is_empty_(is_empty) {} + +Filter FilterInternal::EqualTo(const FieldPath& field, + const FieldValue& value) { + return Where(field, kEqualTo, value); +} + +Filter FilterInternal::NotEqualTo(const FieldPath& field, + const FieldValue& value) { + return Where(field, kNotEqualTo, value); +} + +Filter FilterInternal::LessThan(const FieldPath& field, + const FieldValue& value) { + return Where(field, kLessThan, value); +} + +Filter FilterInternal::LessThanOrEqualTo(const FieldPath& field, + const FieldValue& value) { + return Where(field, kLessThanOrEqualTo, value); +} + +Filter FilterInternal::GreaterThan(const FieldPath& field, + const FieldValue& value) { + return Where(field, kGreaterThan, value); +} + +Filter FilterInternal::GreaterThanOrEqualTo(const FieldPath& field, + const FieldValue& value) { + return Where(field, kGreaterThanOrEqualTo, value); +} + +Filter FilterInternal::ArrayContains(const FieldPath& field, + const FieldValue& value) { + return Where(field, kArrayContains, value); +} + +Filter FilterInternal::ArrayContainsAny(const FieldPath& field, + const std::vector& values) { + return Where(field, kArrayContainsAny, values); +} + +Filter FilterInternal::In(const FieldPath& field, + const std::vector& values) { + return Where(field, kIn, values); +} + +Filter FilterInternal::NotIn(const FieldPath& field, + const std::vector& values) { + return Where(field, kNotIn, values); +} + +Filter FilterInternal::And(const std::vector& filters) { + return Where(kAnd, filters); +} + +Filter FilterInternal::Or(const std::vector& filters) { + return Where(kOr, filters); +} + +Env FilterInternal::GetEnv() { return FirestoreInternal::GetEnv(); } + +Filter FilterInternal::Where(const FieldPath& field, + const StaticMethod& method, + const FieldValue& value) { + Env env = GetEnv(); + Local java_field = FieldPathConverter::Create(env, field); + Object filter = + env.Call(method, java_field, FieldValueInternal::ToJava(value)); + return Filter(new FilterInternal(std::move(filter), false)); +} + +Filter FilterInternal::Where(const FieldPath& field, + const jni::StaticMethod& method, + const std::vector& values) { + Env env = GetEnv(); + size_t size = values.size(); + Local java_values = ArrayList::Create(env, size); + for (size_t i = 0; i < size; ++i) { + java_values.Add(env, FieldValueInternal::ToJava(values[i])); + } + + Local java_field = FieldPathConverter::Create(env, field); + Object filter = env.Call(method, java_field, java_values); + return Filter(new FilterInternal(std::move(filter), false)); +} + +Filter FilterInternal::Where(const StaticMethod& method, + const std::vector& filters) { + Env env = GetEnv(); + size_t size = filters.size(); + Local> java_filters = env.NewArray(size, Object::GetClass()); + bool is_empty = true; + for (int i = 0; i < size; ++i) { + FilterInternal* internal_filter = filters[i].internal_; + if (!internal_filter->IsEmpty()) { + is_empty = false; + } + java_filters.Set(env, i, internal_filter->object_); + } + Object filter = env.Call(method, java_filters); + return Filter(new FilterInternal(std::move(filter), is_empty)); +} + +bool operator==(const FilterInternal& lhs, const FilterInternal& rhs) { + return jni::EqualityCompareJni(lhs, rhs); +} + +} // namespace firestore +} // namespace firebase diff --git a/firestore/src/android/filter_android.h b/firestore/src/android/filter_android.h new file mode 100644 index 0000000000..c69984c537 --- /dev/null +++ b/firestore/src/android/filter_android.h @@ -0,0 +1,89 @@ +/* + * Copyright 2023 Google LLC + * + * 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 FIREBASE_FIRESTORE_SRC_ANDROID_FILTER_ANDROID_H_ +#define FIREBASE_FIRESTORE_SRC_ANDROID_FILTER_ANDROID_H_ + +#include + +#include "firestore/src/android/wrapper.h" +#include "firestore/src/include/firebase/firestore/field_path.h" +#include "firestore/src/include/firebase/firestore/field_value.h" +#include "firestore/src/include/firebase/firestore/filter.h" + +namespace firebase { +namespace firestore { + +class FilterInternal final { + public: + static void Initialize(jni::Loader& loader); + + FilterInternal(jni::Object&& object, bool is_empty); + + static Filter ArrayContains(const FieldPath& field, const FieldValue& value); + static Filter ArrayContainsAny(const FieldPath& field, + const std::vector& values); + static Filter EqualTo(const FieldPath& field, const FieldValue& value); + static Filter NotEqualTo(const FieldPath& field, const FieldValue& value); + static Filter GreaterThan(const FieldPath& field, const FieldValue& value); + static Filter GreaterThanOrEqualTo(const FieldPath& field, + const FieldValue& value); + static Filter LessThan(const FieldPath& field, const FieldValue& value); + static Filter LessThanOrEqualTo(const FieldPath& field, + const FieldValue& value); + static Filter In(const FieldPath& field, + const std::vector& values); + static Filter NotIn(const FieldPath& field, + const std::vector& values); + static Filter Or(const std::vector& filters); + static Filter And(const std::vector& filters); + + const jni::Global& ToJava() const { return object_; } + + private: + friend class Filter; + friend class FirestoreInternal; + + FilterInternal* clone() { return new FilterInternal(*this); } + + bool IsEmpty() const { return is_empty_; } + + static jni::Env GetEnv(); + + jni::Global object_; + const bool is_empty_; + + // A generalized function for all WhereFoo calls. + static Filter Where(const FieldPath& field, + const jni::StaticMethod& method, + const FieldValue& value); + static Filter Where(const FieldPath& field, + const jni::StaticMethod& method, + const std::vector& values); + static Filter Where(const jni::StaticMethod& method, + const std::vector& filters); +}; + +bool operator==(const FilterInternal& lhs, const FilterInternal& rhs); + +inline bool operator!=(const FilterInternal& lhs, const FilterInternal& rhs) { + return !(lhs == rhs); +} + +} // namespace firestore +} // namespace firebase + +#endif // FIREBASE_FIRESTORE_SRC_ANDROID_FILTER_ANDROID_H_ diff --git a/firestore/src/android/query_android.cc b/firestore/src/android/query_android.cc index 27a1dd2e38..436e12c0a2 100644 --- a/firestore/src/android/query_android.cc +++ b/firestore/src/android/query_android.cc @@ -25,6 +25,7 @@ #include "firestore/src/android/event_listener_android.h" #include "firestore/src/android/field_path_android.h" #include "firestore/src/android/field_value_android.h" +#include "firestore/src/android/filter_android.h" #include "firestore/src/android/firestore_android.h" #include "firestore/src/android/lambda_event_listener.h" #include "firestore/src/android/listener_registration_android.h" @@ -55,6 +56,9 @@ constexpr char kClassName[] = PROGUARD_KEEP_CLASS "com/google/firebase/firestore/Query"; Method kCount("count", "()Lcom/google/firebase/firestore/AggregateQuery;"); +Method kWhere("where", + "(Lcom/google/firebase/firestore/Filter;)" + "Lcom/google/firebase/firestore/Query;"); Method kEqualTo( "whereEqualTo", "(Lcom/google/firebase/firestore/FieldPath;Ljava/lang/Object;)" @@ -144,7 +148,7 @@ void QueryInternal::Initialize(jni::Loader& loader) { kGreaterThan, kGreaterThanOrEqualTo, kArrayContains, kArrayContainsAny, kIn, kNotIn, kOrderBy, kLimit, kLimitToLast, kStartAtSnapshot, kStartAt, kStartAfterSnapshot, kStartAfter, kEndBeforeSnapshot, kEndBefore, - kEndAtSnapshot, kEndAt, kGet, kAddSnapshotListener, kHashCode); + kEndAtSnapshot, kEndAt, kGet, kAddSnapshotListener, kHashCode, kWhere); } Firestore* QueryInternal::firestore() { @@ -158,6 +162,12 @@ AggregateQuery QueryInternal::Count() const { return firestore_->NewAggregateQuery(env, aggregate_query); } +Query QueryInternal::Where(const firebase::firestore::Filter& filter) const { + Env env = GetEnv(); + Local query = env.Call(obj_, kWhere, filter.internal_->ToJava()); + return firestore_->NewQuery(env, query); +} + Query QueryInternal::WhereEqualTo(const FieldPath& field, const FieldValue& value) const { return Where(field, kEqualTo, value); diff --git a/firestore/src/android/query_android.h b/firestore/src/android/query_android.h index abb3dea8e3..a325e561e4 100644 --- a/firestore/src/android/query_android.h +++ b/firestore/src/android/query_android.h @@ -71,6 +71,14 @@ class QueryInternal : public Wrapper { */ virtual AggregateQuery Count() const; + /** + * @brief Creates and returns a new Query with the additional filter. + * + * @param filter The new filter to apply to the existing query. + * @return The created Query. + */ + Query Where(const Filter& filter) const; + /** * @brief Creates and returns a new Query with the additional filter that * documents must contain the specified field and the value should be equal to diff --git a/firestore/src/common/filter.cc b/firestore/src/common/filter.cc new file mode 100644 index 0000000000..77ae208beb --- /dev/null +++ b/firestore/src/common/filter.cc @@ -0,0 +1,174 @@ +/* + * Copyright 2023 Google LLC + * + * 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 "firebase/firestore/filter.h" + +#include "firestore/src/common/cleanup.h" +#include "firestore/src/common/hard_assert_common.h" + +#if defined(__ANDROID__) +#include "firestore/src/android/filter_android.h" +#else +#include "firestore/src/main/filter_main.h" +#endif // defined(__ANDROID__) + +#include "firestore/src/common/util.h" + +namespace firebase { +namespace firestore { + +Filter::Filter(const Filter& other) { internal_ = other.internal_->clone(); } + +Filter::Filter(Filter&& other) noexcept { + std::swap(internal_, other.internal_); +} + +Filter::Filter(FilterInternal* internal) : internal_(internal) { + SIMPLE_HARD_ASSERT(internal != nullptr); +} + +Filter::~Filter() { + delete internal_; + internal_ = nullptr; +} + +Filter& Filter::operator=(const Filter& other) { + if (this == &other) { + return *this; + } + delete internal_; + internal_ = other.internal_->clone(); + return *this; +} + +Filter& Filter::operator=(Filter&& other) noexcept { + if (this == &other) { + return *this; + } + delete internal_; + internal_ = other.internal_; + other.internal_ = nullptr; + return *this; +} + +Filter Filter::ArrayContains(const std::string& field, + const FieldValue& value) { + return ArrayContains(FieldPath::FromDotSeparatedString(field), value); +} + +Filter Filter::ArrayContainsAny(const std::string& field, + const std::vector& values) { + return ArrayContainsAny(FieldPath::FromDotSeparatedString(field), values); +} + +Filter Filter::EqualTo(const std::string& field, + const firebase::firestore::FieldValue& value) { + return EqualTo(FieldPath::FromDotSeparatedString(field), value); +} + +Filter Filter::NotEqualTo(const std::string& field, const FieldValue& value) { + return NotEqualTo(FieldPath::FromDotSeparatedString(field), value); +} + +Filter Filter::GreaterThan(const std::string& field, const FieldValue& value) { + return GreaterThan(FieldPath::FromDotSeparatedString(field), value); +} + +Filter Filter::GreaterThanOrEqualTo(const std::string& field, + const FieldValue& value) { + return GreaterThanOrEqualTo(FieldPath::FromDotSeparatedString(field), value); +} + +Filter Filter::LessThan(const std::string& field, const FieldValue& value) { + return LessThan(FieldPath::FromDotSeparatedString(field), value); +} + +Filter Filter::LessThanOrEqualTo(const std::string& field, + const FieldValue& value) { + return LessThanOrEqualTo(FieldPath::FromDotSeparatedString(field), value); +} + +Filter Filter::In(const std::string& field, + const std::vector& values) { + return In(FieldPath::FromDotSeparatedString(field), values); +} + +Filter Filter::NotIn(const std::string& field, + const std::vector& values) { + return NotIn(FieldPath::FromDotSeparatedString(field), values); +} + +Filter Filter::ArrayContains(const FieldPath& field, const FieldValue& value) { + return FilterInternal::ArrayContains(field, value); +} + +Filter Filter::ArrayContainsAny(const FieldPath& field, + const std::vector& values) { + return FilterInternal::ArrayContainsAny(field, values); +} + +Filter Filter::EqualTo(const FieldPath& field, const FieldValue& value) { + return FilterInternal::EqualTo(field, value); +} + +Filter Filter::NotEqualTo(const FieldPath& field, const FieldValue& value) { + return FilterInternal::NotEqualTo(field, value); +} + +Filter Filter::GreaterThan(const FieldPath& field, const FieldValue& value) { + return FilterInternal::GreaterThan(field, value); +} + +Filter Filter::GreaterThanOrEqualTo(const FieldPath& field, + const FieldValue& value) { + return FilterInternal::GreaterThanOrEqualTo(field, value); +} + +Filter Filter::LessThan(const FieldPath& field, const FieldValue& value) { + return FilterInternal::LessThan(field, value); +} + +Filter Filter::LessThanOrEqualTo(const FieldPath& field, + const FieldValue& value) { + return FilterInternal::LessThanOrEqualTo(field, value); +} + +Filter Filter::In(const FieldPath& field, + const std::vector& values) { + return FilterInternal::In(field, values); +} + +Filter Filter::NotIn(const FieldPath& field, + const std::vector& values) { + return FilterInternal::NotIn(field, values); +} + +Filter Filter::And(const std::vector& filters) { + return FilterInternal::And(filters); +} + +Filter Filter::Or(const std::vector& filters) { + return FilterInternal::Or(filters); +} + +bool operator==(const Filter& lhs, const Filter& rhs) { + return EqualityCompare(lhs.internal_, rhs.internal_); +} + +bool Filter::IsEmpty() const { return internal_->IsEmpty(); } + +} // namespace firestore +} // namespace firebase diff --git a/firestore/src/common/query.cc b/firestore/src/common/query.cc index 4e12cb9041..089fc9375f 100644 --- a/firestore/src/common/query.cc +++ b/firestore/src/common/query.cc @@ -27,6 +27,7 @@ #include "firestore/src/include/firebase/firestore/document_snapshot.h" #include "firestore/src/include/firebase/firestore/field_path.h" #include "firestore/src/include/firebase/firestore/field_value.h" +#include "firestore/src/include/firebase/firestore/filter.h" #include "firestore/src/include/firebase/firestore/listener_registration.h" #include "firestore/src/include/firebase/firestore/query_snapshot.h" #if defined(__ANDROID__) @@ -112,6 +113,16 @@ AggregateQuery Query::Count() const { return internal_->Count(); } +Query Query::Where(const Filter& filter) const { + if (!internal_) return {}; + if (filter.IsEmpty()) { + // Return the existing query if not adding any more filters (e.g. an empty + // composite filter). + return *this; + } + return internal_->Where(filter); +} + Query Query::WhereEqualTo(const std::string& field, const FieldValue& value) const { return WhereEqualTo(FieldPath::FromDotSeparatedString(field), value); diff --git a/firestore/src/common/type_mapping.h b/firestore/src/common/type_mapping.h index 269b92a410..5501181da3 100644 --- a/firestore/src/common/type_mapping.h +++ b/firestore/src/common/type_mapping.h @@ -34,6 +34,8 @@ class DocumentReference; class DocumentReferenceInternal; class DocumentSnapshot; class DocumentSnapshotInternal; +class Filter; +class FilterInternal; class FieldValue; class FieldValueInternal; class Firestore; @@ -83,6 +85,10 @@ struct InternalTypeMap { using type = DocumentSnapshotInternal; }; template <> +struct InternalTypeMap { + using type = FilterInternal; +}; +template <> struct InternalTypeMap { using type = FieldValueInternal; }; diff --git a/firestore/src/include/firebase/firestore.h b/firestore/src/include/firebase/firestore.h index 7cc0274812..c3cf04e07f 100644 --- a/firestore/src/include/firebase/firestore.h +++ b/firestore/src/include/firebase/firestore.h @@ -36,6 +36,7 @@ #include "firebase/firestore/document_snapshot.h" #include "firebase/firestore/field_path.h" #include "firebase/firestore/field_value.h" +#include "firebase/firestore/filter.h" #include "firebase/firestore/firestore_errors.h" #include "firebase/firestore/geo_point.h" #include "firebase/firestore/listener_registration.h" diff --git a/firestore/src/include/firebase/firestore/field_path.h b/firestore/src/include/firebase/firestore/field_path.h index d09dec6411..81c8cb5739 100644 --- a/firestore/src/include/firebase/firestore/field_path.h +++ b/firestore/src/include/firebase/firestore/field_path.h @@ -164,6 +164,7 @@ class FieldPath final { friend bool operator!=(const FieldPath& lhs, const FieldPath& rhs); friend struct std::hash; + friend class Filter; friend class DocumentSnapshot; // For access to `FromDotSeparatedString` friend class Query; friend class QueryInternal; diff --git a/firestore/src/include/firebase/firestore/filter.h b/firestore/src/include/firebase/firestore/filter.h new file mode 100644 index 0000000000..ff1471acc3 --- /dev/null +++ b/firestore/src/include/firebase/firestore/filter.h @@ -0,0 +1,411 @@ +/* + * Copyright 2023 Google LLC + * + * 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 FIREBASE_FIRESTORE_SRC_INCLUDE_FIREBASE_FIRESTORE_FILTER_H_ +#define FIREBASE_FIRESTORE_SRC_INCLUDE_FIREBASE_FIRESTORE_FILTER_H_ + +#include +#include + +#include "firebase/firestore/field_value.h" + +namespace firebase { +namespace firestore { + +class FilterInternal; + +/** + * @brief A Filter represents a restriction on one or more field values and can + * be used to refine the results of a Query. + */ +class Filter { + public: + /** + * @brief Creates a new filter for checking that the given array field + * contains the given value. + * + * @param[in] field The name of the field containing an array to search. + * @param[in] value The value that must be contained in the array. + * + * @return The newly created filter. + */ + static Filter ArrayContains(const std::string& field, + const FieldValue& value); + + /** + * @brief Creates a new filter for checking that the given array field + * contains any of the given values. + * + * @param[in] field The name of the field containing an array to search. + * @param[in] values The list of values to match. + * + * @return The newly created filter. + */ + static Filter ArrayContainsAny(const std::string& field, + const std::vector& values); + + /** + * @brief Creates a new filter for checking that the given field is equal to + * the given value. + * + * @param[in] field The name of the field to compare. + * @param[in] value The value for comparison + * + * @return The newly created filter. + */ + static Filter EqualTo(const std::string& field, const FieldValue& value); + + /** + * @brief Creates a new filter for checking that the given field is not equal + * to the given value. + * + * @param[in] field The name of the field to compare. + * @param[in] value The value for comparison + * + * @return The newly created filter. + */ + static Filter NotEqualTo(const std::string& field, const FieldValue& value); + + /** + * @brief Creates a new filter for checking that the given field is greater + * than the given value. + * + * @param[in] field The name of the field to compare. + * @param[in] value The value for comparison + * + * @return The newly created filter. + */ + static Filter GreaterThan(const std::string& field, const FieldValue& value); + + /** + * @brief Creates a new filter for checking that the given field is greater + * than or equal to the given value. + * + * @param[in] field The name of the field to compare. + * @param[in] value The value for comparison + * + * @return The newly created filter. + */ + static Filter GreaterThanOrEqualTo(const std::string& field, + const FieldValue& value); + + /** + * @brief Creates a new filter for checking that the given field is less than + * the given value. + * + * @param[in] field The name of the field to compare. + * @param[in] value The value for comparison + * + * @return The newly created filter. + */ + static Filter LessThan(const std::string& field, const FieldValue& value); + + /** + * @brief Creates a new filter for checking that the given field is less than + * or equal to the given value. + * + * @param[in] field The name of the field to compare. + * @param[in] value The value for comparison + * + * @return The newly created filter. + */ + static Filter LessThanOrEqualTo(const std::string& field, + const FieldValue& value); + + /** + * @brief Creates a new filter for checking that the given field equals any of + * the given values. + * + * @param[in] field The name of the field to compare. + * @param[in] values The list of values to match. + * + * @return The newly created filter. + */ + static Filter In(const std::string& field, + const std::vector& values); + + /** + * @brief Creates a new filter for checking that the given field does not + * equal any of the given values. + * + * @param[in] field The name of the field to compare. + * @param[in] values The list of values to match. + * + * @return The newly created filter. + */ + static Filter NotIn(const std::string& field, + const std::vector& values); + + /** + * @brief Creates a new filter for checking that the given array field + * contains the given value. + * + * @param[in] field The path of the field containing an array to search. + * @param[in] value The value that must be contained in the array. + * + * @return The newly created filter. + */ + static Filter ArrayContains(const FieldPath& field, const FieldValue& value); + + /** + * @brief Creates a new filter for checking that the given array field + * contains any of the given values. + * + * @param[in] field The path of the field containing an array to search. + * @param[in] values The list of values to match. + * + * @return The newly created filter. + */ + static Filter ArrayContainsAny(const FieldPath& field, + const std::vector& values); + + /** + * @brief Creates a new filter for checking that the given field is equal to + * the given value. + * + * @param[in] field The path of the field to compare. + * @param[in] value The value for comparison + * + * @return The newly created filter. + */ + static Filter EqualTo(const FieldPath& field, const FieldValue& value); + + /** + * @brief Creates a new filter for checking that the given field is not equal + * to the given value. + * + * @param[in] field The path of the field to compare. + * @param[in] value The value for comparison + * + * @return The newly created filter. + */ + static Filter NotEqualTo(const FieldPath& field, const FieldValue& value); + + /** + * @brief Creates a new filter for checking that the given field is greater + * than the given value. + * + * @param[in] field The path of the field to compare. + * @param[in] value The value for comparison + * + * @return The newly created filter. + */ + static Filter GreaterThan(const FieldPath& field, const FieldValue& value); + + /** + * @brief Creates a new filter for checking that the given field is greater + * than or equal to the given value. + * + * @param[in] field The path of the field to compare. + * @param[in] value The value for comparison + * + * @return The newly created filter. + */ + static Filter GreaterThanOrEqualTo(const FieldPath& field, + const FieldValue& value); + + /** + * @brief Creates a new filter for checking that the given field is less than + * the given value. + * + * @param[in] field The path of the field to compare. + * @param[in] value The value for comparison + * + * @return The newly created filter. + */ + static Filter LessThan(const FieldPath& field, const FieldValue& value); + + /** + * @brief Creates a new filter for checking that the given field is less than + * or equal to the given value. + * + * @param[in] field The path of the field to compare. + * @param[in] value The value for comparison + * + * @return The newly created filter. + */ + static Filter LessThanOrEqualTo(const FieldPath& field, + const FieldValue& value); + + /** + * @brief Creates a new filter for checking that the given field equals any of + * the given values. + * + * @param[in] field The path of the field to compare. + * @param[in] values The list of values to match. + * + * @return The newly created filter. + */ + static Filter In(const FieldPath& field, + const std::vector& values); + + /** + * @brief Creates a new filter for checking that the given field does not + * equal any of the given values. + * + * @param[in] field The path of the field to compare. + * @param[in] values The list of values to match. + * + * @return The newly created filter. + */ + static Filter NotIn(const FieldPath& field, + const std::vector& values); + + /** + * @brief Creates a new filter that is a conjunction of the given filters. A + * conjunction filter includes a document if it satisfies all of the given + * filters. + * + * If no filter is given, the composite filter is a no-op, and if only one + * filter is given, the composite filter has the same behavior as the + * underlying filter. + * + * @param[in] filters The filters to perform a conjunction for. + * + * @return The newly created filter. + */ + template + static Filter And(const Filters&... filters) { + return AndInternal(filters...); + } + + /** + * @brief Creates a new filter that is a conjunction of the given filters. A + * conjunction filter includes a document if it satisfies all of the given + * filters. + * + * If no filter is given, the composite filter is a no-op, and if only one + * filter is given, the composite filter has the same behavior as the + * underlying filter. + * + * @param[in] filters The list that contains filters to perform a conjunction + * for. + * + * @return The newly created filter. + */ + static Filter And(const std::vector& filters); + + /** + * @brief Creates a new filter that is a disjunction of the given filters. A + * disjunction filter includes a document if it satisfies any of the + * given filters. + * + * If no filter is given, the composite filter is a no-op, and if only one + * filter is given, the composite filter has the same behavior as the + * underlying filter. + * + * @param[in] filters The filters to perform a disjunction for. + * + * @return The newly created filter. + */ + template + static Filter Or(const Filters&... filters) { + return OrInternal(filters...); + } + + /** + * @brief Creates a new filter that is a disjunction of the given filters. A + * disjunction filter includes a document if it satisfies any of the + * given filters. + * + * If no filter is given, the composite filter is a no-op, and if only one + * filter is given, the composite filter has the same behavior as the + * underlying filter. + * + * @param[in] filters The list that contains filters to perform a disjunction + * for. + * + * @return The newly created filter. + */ + static Filter Or(const std::vector& filters); + + /** + * @brief Copy constructor. + * + * `Filter` is immutable and can be efficiently copied. + * + * @param[in] other `Filter` to copy from. + */ + Filter(const Filter& other); + + /** + * @brief Move constructor. + * + * @param[in] other `Filter` to move data from. + */ + Filter(Filter&& other) noexcept; + + /** + * @brief Copy assignment operator. + * + * `Filter` is immutable and can be efficiently copied. + * + * @param[in] other `Filter` to copy from. + * + * @return Reference to the destination `Filter`. + */ + Filter& operator=(const Filter& other); + + /** + * @brief Move assignment operator. + * + * @param[in] other `Filter` to move data from. + * + * @return Reference to the destination `Filter`. + */ + Filter& operator=(Filter&& other) noexcept; + + ~Filter(); + + private: + friend class Query; + friend class QueryInternal; + friend class FilterInternal; + friend bool operator==(const Filter& lhs, const Filter& rhs); + friend struct ConverterImpl; + + static inline Filter AndInternal(const Filter& filter) { return filter; } + + template + static inline Filter AndInternal(const Filters&... filters) { + return And(std::vector({filters...})); + } + + static inline Filter OrInternal(const Filter& filter) { return filter; } + + template + static inline Filter OrInternal(const Filters&... filters) { + return Or(std::vector({filters...})); + } + + bool IsEmpty() const; + + explicit Filter(FilterInternal* internal); + FilterInternal* internal_ = nullptr; +}; + +/** Checks `lhs` and `rhs` for equality. */ +bool operator==(const Filter& lhs, const Filter& rhs); + +/** Checks `lhs` and `rhs` for inequality. */ +inline bool operator!=(const Filter& lhs, const Filter& rhs) { + return !(lhs == rhs); +} + +} // namespace firestore +} // namespace firebase + +#endif // FIREBASE_FIRESTORE_SRC_INCLUDE_FIREBASE_FIRESTORE_FILTER_H_ diff --git a/firestore/src/include/firebase/firestore/query.h b/firestore/src/include/firebase/firestore/query.h index 0586756854..d696492146 100644 --- a/firestore/src/include/firebase/firestore/query.h +++ b/firestore/src/include/firebase/firestore/query.h @@ -41,6 +41,7 @@ class AggregateQuery; class DocumentSnapshot; template class EventListener; +class Filter; class FieldPath; class FieldValue; class ListenerRegistration; @@ -83,8 +84,7 @@ class Query { /** * @brief Copy constructor. * - * `Query` is immutable and can be efficiently copied (no deep copy is - * performed). + * `Query` is immutable and can be efficiently copied. * * @param[in] other `Query` to copy from. */ @@ -105,8 +105,7 @@ class Query { /** * @brief Copy assignment operator. * - * `Query` is immutable and can be efficiently copied (no deep copy is - * performed). + * `Query` is immutable and can be efficiently copied. * * @param[in] other `Query` to copy from. * @@ -161,6 +160,14 @@ class Query { */ virtual AggregateQuery Count() const; + /** + * @brief Creates and returns a new Query with the additional filter. + * + * @param filter The new filter to apply to the existing query. + * @return The created Query. + */ + virtual Query Where(const Filter& filter) const; + /** * @brief Creates and returns a new Query with the additional filter that * documents must contain the specified field and the value should be equal to @@ -395,7 +402,7 @@ class Query { * A Query can have only one `WhereIn()` filter and it cannot be * combined with `WhereArrayContainsAny()`. * - * @param[in] field The name of the field containing an array to search. + * @param[in] field The name of the field to compare. * @param[in] values The list that contains the values to match. * * @return The created Query. @@ -411,7 +418,7 @@ class Query { * A Query can have only one `WhereIn()` filter and it cannot be * combined with `WhereArrayContainsAny()`. * - * @param[in] field The path of the field containing an array to search. + * @param[in] field The path of the field to compare. * @param[in] values The list that contains the values to match. * * @return The created Query. @@ -433,7 +440,7 @@ class Query { * combined with `WhereArrayContains()`, `WhereArrayContainsAny()`, * `WhereIn()`, or `WhereNotEqualTo()`. * - * @param[in] field The name of the field containing an array to search. + * @param[in] field The name of the field to compare. * @param[in] values The list that contains the values to match. * * @return The created Query. @@ -455,7 +462,7 @@ class Query { * combined with `WhereArrayContains()`, `WhereArrayContainsAny()`, * `WhereIn()`, or `WhereNotEqualTo()`. * - * @param[in] field The path of the field containing an array to search. + * @param[in] field The path of the field to compare. * @param[in] values The list that contains the values to match. * * @return The created Query. diff --git a/firestore/src/main/composite_filter_main.cc b/firestore/src/main/composite_filter_main.cc new file mode 100644 index 0000000000..82666c528b --- /dev/null +++ b/firestore/src/main/composite_filter_main.cc @@ -0,0 +1,70 @@ +/* + * Copyright 2023 Google LLC + * + * 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. + */ + +#if defined(__ANDROID__) +#error "This header should not be used on Android." +#endif + +#include +#include + +#include "Firestore/core/src/core/composite_filter.h" +#include "absl/algorithm/container.h" +#include "firestore/src/common/util.h" +#include "firestore/src/main/composite_filter_main.h" +#include "firestore/src/main/converter_main.h" + +namespace firebase { +namespace firestore { + +CompositeFilterInternal::CompositeFilterInternal( + core::CompositeFilter::Operator op, std::vector& filters) + : FilterInternal(FilterType::Composite), op_(op) { + for (FilterInternal* filter_internal : filters) { + filters_.emplace_back(std::shared_ptr(filter_internal)); + } +} + +CompositeFilterInternal* CompositeFilterInternal::clone() { + return new CompositeFilterInternal(*this); +} + +bool CompositeFilterInternal::IsEmpty() const { return filters_.empty(); } + +core::Filter CompositeFilterInternal::ToCoreFilter( + const api::Query& query, + const firebase::firestore::UserDataConverter& user_data_converter) const { + std::vector core_filters{}; + for (auto& filter : filters_) { + core_filters.push_back(filter->ToCoreFilter(query, user_data_converter)); + } + return core::CompositeFilter::Create(std::move(core_filters), op_); +} + +bool operator==(const CompositeFilterInternal& lhs, + const CompositeFilterInternal& rhs) { + return lhs.op_ == rhs.op_ && lhs.filters_.size() == rhs.filters_.size() && + std::equal(lhs.filters_.begin(), lhs.filters_.end(), + rhs.filters_.begin(), rhs.filters_.end(), + [](const std::shared_ptr& lhs_filter, + const std::shared_ptr& rhs_filter) { + return EqualityCompare(lhs_filter.get(), + rhs_filter.get()); + }); +} + +} // namespace firestore +} // namespace firebase diff --git a/firestore/src/main/composite_filter_main.h b/firestore/src/main/composite_filter_main.h new file mode 100644 index 0000000000..4eb1ee5af8 --- /dev/null +++ b/firestore/src/main/composite_filter_main.h @@ -0,0 +1,66 @@ +/* + * Copyright 2023 Google LLC + * + * 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 FIREBASE_FIRESTORE_SRC_MAIN_COMPOSITE_FILTER_MAIN_H_ +#define FIREBASE_FIRESTORE_SRC_MAIN_COMPOSITE_FILTER_MAIN_H_ + +#if defined(__ANDROID__) +#error "This header should not be used on Android." +#endif + +#include +#include + +#include "Firestore/core/src/api/query_core.h" +#include "firestore/src/main/filter_main.h" + +namespace firebase { +namespace firestore { + +class CompositeFilterInternal : public FilterInternal { + public: + CompositeFilterInternal(core::CompositeFilter::Operator op, + std::vector& filters); + + core::Filter ToCoreFilter(const api::Query& query, + const firebase::firestore::UserDataConverter& + user_data_converter) const override; + + friend bool operator==(const CompositeFilterInternal& lhs, + const CompositeFilterInternal& rhs); + + protected: + bool IsEmpty() const override; + + private: + CompositeFilterInternal* clone() override; + + const core::CompositeFilter::Operator op_; + std::vector> filters_; +}; + +bool operator==(const CompositeFilterInternal& lhs, + const CompositeFilterInternal& rhs); + +inline bool operator!=(const CompositeFilterInternal& lhs, + const CompositeFilterInternal& rhs) { + return !(lhs == rhs); +} + +} // namespace firestore +} // namespace firebase + +#endif // FIREBASE_FIRESTORE_SRC_MAIN_COMPOSITE_FILTER_MAIN_H_ diff --git a/firestore/src/main/converter_main.h b/firestore/src/main/converter_main.h index 14dac38a59..a4f1146e3c 100644 --- a/firestore/src/main/converter_main.h +++ b/firestore/src/main/converter_main.h @@ -38,6 +38,7 @@ #include "firestore/src/main/aggregate_query_main.h" #include "firestore/src/main/aggregate_query_snapshot_main.h" #include "firestore/src/main/collection_reference_main.h" +#include "firestore/src/main/composite_filter_main.h" #include "firestore/src/main/document_change_main.h" #include "firestore/src/main/document_reference_main.h" #include "firestore/src/main/document_snapshot_main.h" @@ -46,6 +47,7 @@ #include "firestore/src/main/query_main.h" #include "firestore/src/main/query_snapshot_main.h" #include "firestore/src/main/transaction_main.h" +#include "firestore/src/main/unary_filter_main.h" #include "firestore/src/main/write_batch_main.h" #if defined(__ANDROID__) @@ -108,6 +110,14 @@ inline DocumentSnapshot MakePublic(api::DocumentSnapshot&& from) { return ConverterImpl::MakePublicFromCore(std::move(from)); } +inline Filter MakePublic(UnaryFilterInternal&& from) { + return ConverterImpl::MakePublicFromInternal(std::move(from)); +} + +inline Filter MakePublic(CompositeFilterInternal&& from) { + return ConverterImpl::MakePublicFromInternal(std::move(from)); +} + inline FieldValue MakePublic(FieldValueInternal&& from) { return ConverterImpl::MakePublicFromInternal(std::move(from)); } diff --git a/firestore/src/main/filter_main.cc b/firestore/src/main/filter_main.cc new file mode 100644 index 0000000000..bbc3afbcb1 --- /dev/null +++ b/firestore/src/main/filter_main.cc @@ -0,0 +1,130 @@ +/* + * Copyright 2023 Google LLC + * + * 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 + +#include "firestore/src/main/composite_filter_main.h" +#include "firestore/src/main/converter_main.h" +#include "firestore/src/main/filter_main.h" +#include "firestore/src/main/unary_filter_main.h" + +namespace firebase { +namespace firestore { + +Filter FilterInternal::ArrayContains(const FieldPath& field, + const FieldValue& value) { + return UnaryFilter(field, FieldFilterOperator::ArrayContains, value); +} + +Filter FilterInternal::ArrayContainsAny(const FieldPath& field, + const std::vector& values) { + return UnaryFilter(field, FieldFilterOperator::ArrayContainsAny, values); +} + +Filter FilterInternal::EqualTo(const FieldPath& field, + const FieldValue& value) { + return UnaryFilter(field, FieldFilterOperator::Equal, value); +} + +Filter FilterInternal::NotEqualTo(const FieldPath& field, + const FieldValue& value) { + return UnaryFilter(field, FieldFilterOperator::NotEqual, value); +} + +Filter FilterInternal::GreaterThan(const FieldPath& field, + const FieldValue& value) { + return UnaryFilter(field, FieldFilterOperator::GreaterThan, value); +} + +Filter FilterInternal::GreaterThanOrEqualTo(const FieldPath& field, + const FieldValue& value) { + return UnaryFilter(field, FieldFilterOperator::GreaterThanOrEqual, value); +} + +Filter FilterInternal::LessThan(const FieldPath& field, + const FieldValue& value) { + return UnaryFilter(field, FieldFilterOperator::LessThan, value); +} + +Filter FilterInternal::LessThanOrEqualTo(const FieldPath& field, + const FieldValue& value) { + return UnaryFilter(field, FieldFilterOperator::LessThanOrEqual, value); +} + +Filter FilterInternal::In(const FieldPath& field, + const std::vector& values) { + return UnaryFilter(field, FieldFilterOperator::In, values); +} + +Filter FilterInternal::NotIn(const FieldPath& field, + const std::vector& values) { + return UnaryFilter(field, FieldFilterOperator::NotIn, values); +} + +Filter FilterInternal::Or(const std::vector& filters) { + return CompositeFilter(CompositeOperator::Or, filters); +} + +Filter FilterInternal::And(const std::vector& filters) { + return CompositeFilter(CompositeOperator::And, filters); +} + +FilterInternal::FilterInternal(FilterInternal::FilterType filter_type) + : filter_type_(filter_type) {} + +Filter FilterInternal::UnaryFilter(const FieldPath& field_path, + FieldFilterOperator op, + const FieldValue& value) { + return MakePublic(UnaryFilterInternal(field_path, op, value)); +} + +Filter FilterInternal::UnaryFilter(const FieldPath& field_path, + FieldFilterOperator op, + const std::vector& values) { + return MakePublic(UnaryFilterInternal(field_path, op, values)); +} + +Filter FilterInternal::CompositeFilter(core::CompositeFilter::Operator op, + const std::vector& filters) { + std::vector nonEmptyFilters{}; + for (const Filter& filter : filters) { + FilterInternal* filterInternal = GetInternal(&filter); + if (!filterInternal->IsEmpty()) { + nonEmptyFilters.push_back(filterInternal->clone()); + } + } + if (nonEmptyFilters.size() == 1) { + return Filter(nonEmptyFilters[0]); + } + return MakePublic(CompositeFilterInternal(op, nonEmptyFilters)); +} + +bool operator==(const FilterInternal& lhs, const FilterInternal& rhs) { + if (lhs.filter_type_ == rhs.filter_type_) { + switch (lhs.filter_type_) { + case FilterInternal::Composite: + return *static_cast(&lhs) == + *static_cast(&rhs); + case FilterInternal::Unary: + return *static_cast(&lhs) == + *static_cast(&rhs); + } + } + return false; +} + +} // namespace firestore +} // namespace firebase diff --git a/firestore/src/main/filter_main.h b/firestore/src/main/filter_main.h new file mode 100644 index 0000000000..fbe1f4b728 --- /dev/null +++ b/firestore/src/main/filter_main.h @@ -0,0 +1,105 @@ +/* + * Copyright 2023 Google LLC + * + * 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 FIREBASE_FIRESTORE_SRC_MAIN_FILTER_MAIN_H_ +#define FIREBASE_FIRESTORE_SRC_MAIN_FILTER_MAIN_H_ + +#if defined(__ANDROID__) +#error "This header should not be used on Android." +#endif + +#include + +#include "Firestore/core/src/api/query_core.h" +#include "Firestore/core/src/core/composite_filter.h" +#include "Firestore/core/src/core/filter.h" +#include "Firestore/core/src/model/field_path.h" +#include "firestore/src/include/firebase/firestore/filter.h" +#include "firestore/src/main/user_data_converter_main.h" + +namespace firebase { +namespace firestore { + +class Filter; + +class FilterInternal { + public: + static Filter ArrayContains(const FieldPath& field, const FieldValue& value); + static Filter ArrayContainsAny(const FieldPath& field, + const std::vector& values); + static Filter EqualTo(const FieldPath& field, const FieldValue& value); + static Filter NotEqualTo(const FieldPath& field, const FieldValue& value); + static Filter GreaterThan(const FieldPath& field, const FieldValue& value); + static Filter GreaterThanOrEqualTo(const FieldPath& field, + const FieldValue& value); + static Filter LessThan(const FieldPath& field, const FieldValue& value); + static Filter LessThanOrEqualTo(const FieldPath& field, + const FieldValue& value); + static Filter In(const FieldPath& field, + const std::vector& values); + static Filter NotIn(const FieldPath& field, + const std::vector& values); + static Filter Or(const std::vector& filters); + static Filter And(const std::vector& filters); + + virtual core::Filter ToCoreFilter( + const api::Query& query, + const firebase::firestore::UserDataConverter& user_data_converter) + const = 0; + + virtual ~FilterInternal() = default; + + friend bool operator==(const FilterInternal& lhs, const FilterInternal& rhs); + + protected: + enum FilterType { Unary, Composite }; + + explicit FilterInternal(FilterType filterType); + + const FilterType filter_type_; + + virtual bool IsEmpty() const = 0; + + private: + friend class Filter; + friend class QueryInternal; + + virtual FilterInternal* clone() = 0; + + using FieldFilterOperator = core::FieldFilter::Operator; + using CompositeOperator = core::CompositeFilter::Operator; + + static Filter UnaryFilter(const FieldPath& field_path, + FieldFilterOperator op, + const FieldValue& value); + static Filter UnaryFilter(const FieldPath& field_path, + FieldFilterOperator op, + const std::vector& values); + + static Filter CompositeFilter(CompositeOperator op, + const std::vector& filters); +}; + +bool operator==(const FilterInternal& lhs, const FilterInternal& rhs); + +inline bool operator!=(const FilterInternal& lhs, const FilterInternal& rhs) { + return !(lhs == rhs); +} + +} // namespace firestore +} // namespace firebase + +#endif // FIREBASE_FIRESTORE_SRC_MAIN_FILTER_MAIN_H_ diff --git a/firestore/src/main/query_main.cc b/firestore/src/main/query_main.cc index ffed058c9e..6dab91109a 100644 --- a/firestore/src/main/query_main.cc +++ b/firestore/src/main/query_main.cc @@ -37,6 +37,7 @@ #include "firestore/src/main/aggregate_query_main.h" #include "firestore/src/main/converter_main.h" #include "firestore/src/main/document_snapshot_main.h" +#include "firestore/src/main/filter_main.h" #include "firestore/src/main/listener_main.h" #include "firestore/src/main/promise_main.h" #include "firestore/src/main/set_options_main.h" @@ -98,31 +99,24 @@ Future QueryInternal::Get(Source source) { AggregateQuery QueryInternal::Count() { return MakePublic(query_.Count()); } +Query QueryInternal::Where(const Filter& filter) const { + SIMPLE_HARD_ASSERT(!filter.IsEmpty()); + core::Filter core_filter = + GetInternal(&filter)->ToCoreFilter(query_, user_data_converter_); + api::Query decorated = query_.AddNewFilter(std::move(core_filter)); + return MakePublic(std::move(decorated)); +} + Query QueryInternal::Where(const FieldPath& field_path, Operator op, const FieldValue& value) const { - const model::FieldPath& path = GetInternal(field_path); - Message parsed = - user_data_converter_.ParseQueryValue(value); - auto describer = [&value] { return Describe(value.type()); }; - - api::Query decorated = query_.AddNewFilter( - query_.ParseFieldFilter(path, op, std::move(parsed), describer)); - return MakePublic(std::move(decorated)); + return Where(UnaryFilterInternal::UnaryFilter(field_path, op, value)); } Query QueryInternal::Where(const FieldPath& field_path, Operator op, const std::vector& values) const { - const model::FieldPath& path = GetInternal(field_path); - auto array_value = FieldValue::Array(values); - Message parsed = - user_data_converter_.ParseQueryValue(array_value, true); - auto describer = [&array_value] { return Describe(array_value.type()); }; - - api::Query decorated = query_.AddNewFilter( - query_.ParseFieldFilter(path, op, std::move(parsed), describer)); - return MakePublic(std::move(decorated)); + return Where(UnaryFilterInternal::UnaryFilter(field_path, op, values)); } Query QueryInternal::WithBound(BoundPosition bound_pos, diff --git a/firestore/src/main/query_main.h b/firestore/src/main/query_main.h index 958df81f03..ddf773f846 100644 --- a/firestore/src/main/query_main.h +++ b/firestore/src/main/query_main.h @@ -28,6 +28,7 @@ #include "Firestore/core/src/core/query.h" #include "Firestore/core/src/nanopb/message.h" #include "firestore/src/include/firebase/firestore/field_path.h" +#include "firestore/src/include/firebase/firestore/filter.h" #include "firestore/src/include/firebase/firestore/query.h" #include "firestore/src/main/firestore_main.h" #include "firestore/src/main/promise_factory_main.h" @@ -68,6 +69,7 @@ class QueryInternal { callback); // Delegating methods + Query Where(const Filter& filter) const; Query WhereEqualTo(const FieldPath& field, const FieldValue& value) const { return Where(field, Operator::Equal, value); diff --git a/firestore/src/main/unary_filter_main.cc b/firestore/src/main/unary_filter_main.cc new file mode 100644 index 0000000000..3561ba6f60 --- /dev/null +++ b/firestore/src/main/unary_filter_main.cc @@ -0,0 +1,70 @@ +/* + * Copyright 2023 Google LLC + * + * 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 +#include + +#include "firestore/src/main/unary_filter_main.h" + +#include "Firestore/core/src/nanopb/message.h" +#include "firestore/src/main/converter_main.h" + +namespace firebase { +namespace firestore { + +using nanopb::Message; + +UnaryFilterInternal::UnaryFilterInternal(FieldPath field_path, + core::FieldFilter::Operator op, + FieldValue value) + : FilterInternal(FilterType::Unary), + allow_arrays_(false), + path_(std::move(field_path)), + op_(op), + value_(std::move(value)) {} + +UnaryFilterInternal::UnaryFilterInternal(FieldPath field_path, + core::FieldFilter::Operator op, + const std::vector& values) + : FilterInternal(FilterType::Unary), + allow_arrays_(true), + path_(std::move(field_path)), + op_(op), + value_(FieldValue::Array(values)) {} + +UnaryFilterInternal* UnaryFilterInternal::clone() { + return new UnaryFilterInternal(*this); +} + +core::Filter UnaryFilterInternal::ToCoreFilter( + const api::Query& query, + const firebase::firestore::UserDataConverter& user_data_converter) const { + const model::FieldPath& path = GetInternal(path_); + Message parsed = + user_data_converter.ParseQueryValue(value_, allow_arrays_); + auto describer = [this] { return Describe(value_.type()); }; + + return query.ParseFieldFilter(path, op_, std::move(parsed), describer); +} + +bool operator==(const UnaryFilterInternal& lhs, + const UnaryFilterInternal& rhs) { + return lhs.op_ == rhs.op_ && lhs.path_ == rhs.path_ && + lhs.value_ == rhs.value_; +} + +} // namespace firestore +} // namespace firebase diff --git a/firestore/src/main/unary_filter_main.h b/firestore/src/main/unary_filter_main.h new file mode 100644 index 0000000000..e23d0c9481 --- /dev/null +++ b/firestore/src/main/unary_filter_main.h @@ -0,0 +1,70 @@ +/* + * Copyright 2023 Google LLC + * + * 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 FIREBASE_FIRESTORE_SRC_MAIN_UNARY_FILTER_MAIN_H_ +#define FIREBASE_FIRESTORE_SRC_MAIN_UNARY_FILTER_MAIN_H_ + +#if defined(__ANDROID__) +#error "This header should not be used on Android." +#endif + +#include + +#include "Firestore/core/src/api/query_core.h" +#include "firestore/src/main/filter_main.h" + +namespace firebase { +namespace firestore { + +class UnaryFilterInternal final : public FilterInternal { + public: + UnaryFilterInternal(FieldPath field_path, + core::FieldFilter::Operator op, + FieldValue value); + UnaryFilterInternal(FieldPath field_path, + core::FieldFilter::Operator op, + const std::vector& values); + + core::Filter ToCoreFilter(const api::Query& query, + const firebase::firestore::UserDataConverter& + user_data_converter) const override; + + friend bool operator==(const UnaryFilterInternal& lhs, + const UnaryFilterInternal& rhs); + + protected: + bool IsEmpty() const override { return false; } + + private: + UnaryFilterInternal* clone() override; + + const bool allow_arrays_ = false; + const FieldPath path_; + const core::FieldFilter::Operator op_; + const FieldValue value_; +}; + +bool operator==(const UnaryFilterInternal& lhs, const UnaryFilterInternal& rhs); + +inline bool operator!=(const UnaryFilterInternal& lhs, + const UnaryFilterInternal& rhs) { + return !(lhs == rhs); +} + +} // namespace firestore +} // namespace firebase + +#endif // FIREBASE_FIRESTORE_SRC_MAIN_UNARY_FILTER_MAIN_H_ diff --git a/release_build_files/readme.md b/release_build_files/readme.md index 1ef72ab88d..f91854911f 100644 --- a/release_build_files/readme.md +++ b/release_build_files/readme.md @@ -627,6 +627,11 @@ workflow use only during the development of your app, not for publicly shipping code. ## Release Notes +### Upcoming Release +- Changes + - Firestore: Add support for disjunctions in queries (OR queries) + ([#1335](https://github.com/firebase/firebase-cpp-sdk/pull/1335)). + ### 11.3.0 - Changes - General (Android): Update to Firebase Android BoM version 32.2.0.