adding pods method of package managing
This commit is contained in:
+116
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2018 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_FIRESTORE_ERRORS_H_
|
||||
#define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_FIRESTORE_ERRORS_H_
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
|
||||
/**
|
||||
* Error codes used by Cloud Firestore.
|
||||
*
|
||||
* The codes are in sync across Firestore SDKs on various platforms.
|
||||
*/
|
||||
enum Error {
|
||||
/** The operation completed successfully. */
|
||||
// Note: NSError objects will never have a code with this value.
|
||||
kErrorOk = 0,
|
||||
|
||||
kErrorNone = 0,
|
||||
|
||||
/** The operation was cancelled (typically by the caller). */
|
||||
kErrorCancelled = 1,
|
||||
|
||||
/** Unknown error or an error from a different error domain. */
|
||||
kErrorUnknown = 2,
|
||||
|
||||
/**
|
||||
* Client specified an invalid argument. Note that this differs from
|
||||
* FailedPrecondition. InvalidArgument indicates arguments that are
|
||||
* problematic regardless of the state of the system (e.g., an invalid field
|
||||
* name).
|
||||
*/
|
||||
kErrorInvalidArgument = 3,
|
||||
|
||||
/**
|
||||
* Deadline expired before operation could complete. For operations that
|
||||
* change the state of the system, this error may be returned even if the
|
||||
* operation has completed successfully. For example, a successful response
|
||||
* from a server could have been delayed long enough for the deadline to
|
||||
* expire.
|
||||
*/
|
||||
kErrorDeadlineExceeded = 4,
|
||||
|
||||
/** Some requested document was not found. */
|
||||
kErrorNotFound = 5,
|
||||
|
||||
/** Some document that we attempted to create already exists. */
|
||||
kErrorAlreadyExists = 6,
|
||||
|
||||
/** The caller does not have permission to execute the specified operation. */
|
||||
kErrorPermissionDenied = 7,
|
||||
|
||||
/**
|
||||
* Some resource has been exhausted, perhaps a per-user quota, or perhaps the
|
||||
* entire file system is out of space.
|
||||
*/
|
||||
kErrorResourceExhausted = 8,
|
||||
|
||||
/**
|
||||
* Operation was rejected because the system is not in a state required for
|
||||
* the operation's execution.
|
||||
*/
|
||||
kErrorFailedPrecondition = 9,
|
||||
|
||||
/**
|
||||
* The operation was aborted, typically due to a concurrency issue like
|
||||
* transaction aborts, etc.
|
||||
*/
|
||||
kErrorAborted = 10,
|
||||
|
||||
/** Operation was attempted past the valid range. */
|
||||
kErrorOutOfRange = 11,
|
||||
|
||||
/** Operation is not implemented or not supported/enabled. */
|
||||
kErrorUnimplemented = 12,
|
||||
|
||||
/**
|
||||
* Internal errors. Means some invariants expected by underlying system has
|
||||
* been broken. If you see one of these errors, something is very broken.
|
||||
*/
|
||||
kErrorInternal = 13,
|
||||
|
||||
/**
|
||||
* The service is currently unavailable. This is a most likely a transient
|
||||
* condition and may be corrected by retrying with a backoff.
|
||||
*/
|
||||
kErrorUnavailable = 14,
|
||||
|
||||
/** Unrecoverable data loss or corruption. */
|
||||
kErrorDataLoss = 15,
|
||||
|
||||
/**
|
||||
* The request does not have valid authentication credentials for the
|
||||
* operation.
|
||||
*/
|
||||
kErrorUnauthenticated = 16
|
||||
};
|
||||
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_FIRESTORE_ERRORS_H_
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2018 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_FIRESTORE_VERSION_H_
|
||||
#define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_FIRESTORE_VERSION_H_
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
|
||||
/** Version string for the Firebase Firestore SDK. */
|
||||
extern const char* const kFirestoreVersionString;
|
||||
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_FIRESTORE_VERSION_H_
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2018 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_GEO_POINT_H_
|
||||
#define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_GEO_POINT_H_
|
||||
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
|
||||
/**
|
||||
* An immutable object representing a geographical point in Firestore. The point
|
||||
* is represented as a latitude/longitude pair.
|
||||
*
|
||||
* Latitude values are in the range of [-90, 90].
|
||||
* Longitude values are in the range of [-180, 180].
|
||||
*/
|
||||
class GeoPoint {
|
||||
public:
|
||||
/** Creates a `GeoPoint` with both latitude and longitude set to 0. */
|
||||
GeoPoint() = default;
|
||||
|
||||
/**
|
||||
* Creates a `GeoPoint` from the provided latitude and longitude values.
|
||||
*
|
||||
* @param latitude The latitude as number of degrees between -90 and 90.
|
||||
* @param longitude The longitude as number of degrees between -180 and 180.
|
||||
*/
|
||||
GeoPoint(double latitude, double longitude);
|
||||
|
||||
/** Copy constructor, `GeoPoint` is trivially copyable. */
|
||||
GeoPoint(const GeoPoint& other) = default;
|
||||
|
||||
/** Move constructor, equivalent to copying. */
|
||||
GeoPoint(GeoPoint&& other) = default;
|
||||
|
||||
/** Copy assignment operator, `GeoPoint` is trivially copyable. */
|
||||
GeoPoint& operator=(const GeoPoint& other) = default;
|
||||
|
||||
/** Move assignment operator, equivalent to copying. */
|
||||
GeoPoint& operator=(GeoPoint&& other) = default;
|
||||
|
||||
/** Returns the latitude value of this `GeoPoint`. */
|
||||
double latitude() const {
|
||||
return latitude_;
|
||||
}
|
||||
|
||||
/** Returns the latitude value of this `GeoPoint`. */
|
||||
double longitude() const {
|
||||
return longitude_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of this `GeoPoint` for logging/debugging
|
||||
* purposes.
|
||||
*
|
||||
* @note: the exact string representation is unspecified and subject to
|
||||
* change; don't rely on the format of the string.
|
||||
*/
|
||||
std::string ToString() const;
|
||||
|
||||
/**
|
||||
* Outputs the string representation of this `GeoPoint` to the given stream.
|
||||
*
|
||||
* @see `ToString()` for comments on the representation format.
|
||||
*/
|
||||
friend std::ostream& operator<<(std::ostream& out, const GeoPoint& geo_point);
|
||||
|
||||
private:
|
||||
double latitude_ = 0.0;
|
||||
double longitude_ = 0.0;
|
||||
};
|
||||
|
||||
/** Checks whether `lhs` and `rhs` are in ascending order. */
|
||||
bool operator<(const GeoPoint& lhs, const GeoPoint& rhs);
|
||||
|
||||
/** Checks whether `lhs` and `rhs` are in descending order. */
|
||||
inline bool operator>(const GeoPoint& lhs, const GeoPoint& rhs) {
|
||||
return rhs < lhs;
|
||||
}
|
||||
|
||||
/** Checks whether `lhs` and `rhs` are in non-ascending order. */
|
||||
inline bool operator>=(const GeoPoint& lhs, const GeoPoint& rhs) {
|
||||
return !(lhs < rhs);
|
||||
}
|
||||
|
||||
/** Checks whether `lhs` and `rhs` are in non-descending order. */
|
||||
inline bool operator<=(const GeoPoint& lhs, const GeoPoint& rhs) {
|
||||
return !(lhs > rhs);
|
||||
}
|
||||
|
||||
/** Checks `lhs` and `rhs` for inequality. */
|
||||
inline bool operator!=(const GeoPoint& lhs, const GeoPoint& rhs) {
|
||||
return lhs < rhs || lhs > rhs;
|
||||
}
|
||||
|
||||
/** Checks `lhs` and `rhs` for equality. */
|
||||
inline bool operator==(const GeoPoint& lhs, const GeoPoint& rhs) {
|
||||
return !(lhs != rhs);
|
||||
}
|
||||
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_GEO_POINT_H_
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* Copyright 2018 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_TIMESTAMP_H_
|
||||
#define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_TIMESTAMP_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <ctime>
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
|
||||
#if !defined(_STLPORT_VERSION)
|
||||
#include <chrono> // NOLINT(build/c++11)
|
||||
#endif // !defined(_STLPORT_VERSION)
|
||||
|
||||
namespace firebase {
|
||||
|
||||
/**
|
||||
* A Timestamp represents a point in time independent of any time zone or
|
||||
* calendar, represented as seconds and fractions of seconds at nanosecond
|
||||
* resolution in UTC Epoch time. It is encoded using the Proleptic Gregorian
|
||||
* Calendar which extends the Gregorian calendar backwards to year one. It is
|
||||
* encoded assuming all minutes are 60 seconds long, i.e. leap seconds are
|
||||
* "smeared" so that no leap second table is needed for interpretation. Range is
|
||||
* from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z.
|
||||
*
|
||||
* @see
|
||||
* https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto
|
||||
*/
|
||||
class Timestamp {
|
||||
public:
|
||||
/**
|
||||
* Creates a new timestamp representing the epoch (with seconds and
|
||||
* nanoseconds set to 0).
|
||||
*/
|
||||
Timestamp() = default;
|
||||
|
||||
/**
|
||||
* Creates a new timestamp.
|
||||
*
|
||||
* @param seconds The number of seconds of UTC time since Unix epoch
|
||||
* 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
|
||||
* 9999-12-31T23:59:59Z inclusive; otherwise, assertion failure will be
|
||||
* triggered.
|
||||
* @param nanoseconds The non-negative fractions of a second at nanosecond
|
||||
* resolution. Negative second values with fractions must still have
|
||||
* non-negative nanoseconds values that count forward in time. Must be
|
||||
* from 0 to 999,999,999 inclusive; otherwise, assertion failure will be
|
||||
* triggered.
|
||||
*/
|
||||
Timestamp(int64_t seconds, int32_t nanoseconds);
|
||||
|
||||
/** Copy constructor, `Timestamp` is trivially copyable. */
|
||||
Timestamp(const Timestamp& other) = default;
|
||||
|
||||
/** Move constructor, equivalent to copying. */
|
||||
Timestamp(Timestamp&& other) = default;
|
||||
|
||||
/** Copy assignment operator, `Timestamp` is trivially copyable. */
|
||||
Timestamp& operator=(const Timestamp& other) = default;
|
||||
|
||||
/** Move assignment operator, equivalent to copying. */
|
||||
Timestamp& operator=(Timestamp&& other) = default;
|
||||
|
||||
/**
|
||||
* Creates a new timestamp with the current date.
|
||||
*
|
||||
* The precision is up to nanoseconds, depending on the system clock.
|
||||
*
|
||||
* @return a new timestamp representing the current date.
|
||||
*/
|
||||
static Timestamp Now();
|
||||
|
||||
/**
|
||||
* The number of seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z.
|
||||
*/
|
||||
int64_t seconds() const {
|
||||
return seconds_;
|
||||
}
|
||||
|
||||
/**
|
||||
* The non-negative fractions of a second at nanosecond resolution. Negative
|
||||
* second values with fractions still have non-negative nanoseconds values
|
||||
* that count forward in time.
|
||||
*/
|
||||
int32_t nanoseconds() const {
|
||||
return nanoseconds_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts `time_t` to a `Timestamp`.
|
||||
*
|
||||
* @param seconds_since_unix_epoch
|
||||
* @parblock
|
||||
* The number of seconds of UTC time since Unix epoch
|
||||
* 1970-01-01T00:00:00Z. Can be negative to represent dates before the
|
||||
* epoch. Must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z
|
||||
* inclusive; otherwise, assertion failure will be triggered.
|
||||
*
|
||||
* Note that while the epoch of `time_t` is unspecified, it's usually Unix
|
||||
* epoch. If this assumption is broken, this function will produce
|
||||
* incorrect results.
|
||||
* @endparblock
|
||||
*
|
||||
* @return a new timestamp with the given number of seconds and zero
|
||||
* nanoseconds.
|
||||
*/
|
||||
static Timestamp FromTimeT(time_t seconds_since_unix_epoch);
|
||||
|
||||
#if !defined(_STLPORT_VERSION)
|
||||
/**
|
||||
* Converts `std::chrono::time_point` to a `Timestamp`.
|
||||
*
|
||||
* @param time_point
|
||||
* @parblock
|
||||
* The time point with system clock's epoch, which is
|
||||
* presumed to be Unix epoch 1970-01-01T00:00:00Z. Can be negative to
|
||||
* represent dates before the epoch. Must be from 0001-01-01T00:00:00Z to
|
||||
* 9999-12-31T23:59:59Z inclusive; otherwise, assertion failure will be
|
||||
* triggered.
|
||||
*
|
||||
* Note that while the epoch of `std::chrono::system_clock` is
|
||||
* unspecified, it's usually Unix epoch. If this assumption is broken,
|
||||
* this constructor will produce incorrect results.
|
||||
* @endparblock
|
||||
*/
|
||||
static Timestamp FromTimePoint(
|
||||
std::chrono::time_point<std::chrono::system_clock> time_point);
|
||||
|
||||
/**
|
||||
* Converts this `Timestamp` to a `time_point`.
|
||||
*
|
||||
* Important: if overflow would occur, the returned value will be the maximum
|
||||
* or minimum value that `Duration` can hold. Note in particular that `long
|
||||
* long` is insufficient to hold the full range of `Timestamp` values with
|
||||
* nanosecond precision (which is why `Duration` defaults to `microseconds`).
|
||||
*/
|
||||
template <typename Clock = std::chrono::system_clock,
|
||||
typename Duration = std::chrono::microseconds>
|
||||
std::chrono::time_point<Clock, Duration> ToTimePoint() const;
|
||||
#endif // !defined(_STLPORT_VERSION)
|
||||
|
||||
/**
|
||||
* Returns a string representation of this `Timestamp` for logging/debugging
|
||||
* purposes.
|
||||
*
|
||||
* @note: the exact string representation is unspecified and subject to
|
||||
* change; don't rely on the format of the string.
|
||||
*/
|
||||
std::string ToString() const;
|
||||
|
||||
/**
|
||||
* Outputs the string representation of this `Timestamp` to the given stream.
|
||||
*
|
||||
* @see `ToString()` for comments on the representation format.
|
||||
*/
|
||||
friend std::ostream& operator<<(std::ostream& out,
|
||||
const Timestamp& timestamp);
|
||||
|
||||
private:
|
||||
// Checks that the number of seconds is within the supported date range, and
|
||||
// that nanoseconds satisfy 0 <= ns <= 1second.
|
||||
void ValidateBounds() const;
|
||||
|
||||
int64_t seconds_ = 0;
|
||||
int32_t nanoseconds_ = 0;
|
||||
};
|
||||
|
||||
/** Checks whether `lhs` and `rhs` are in ascending order. */
|
||||
inline bool operator<(const Timestamp& lhs, const Timestamp& rhs) {
|
||||
return lhs.seconds() < rhs.seconds() ||
|
||||
(lhs.seconds() == rhs.seconds() &&
|
||||
lhs.nanoseconds() < rhs.nanoseconds());
|
||||
}
|
||||
|
||||
/** Checks whether `lhs` and `rhs` are in descending order. */
|
||||
inline bool operator>(const Timestamp& lhs, const Timestamp& rhs) {
|
||||
return rhs < lhs;
|
||||
}
|
||||
|
||||
/** Checks whether `lhs` and `rhs` are in non-ascending order. */
|
||||
inline bool operator>=(const Timestamp& lhs, const Timestamp& rhs) {
|
||||
return !(lhs < rhs);
|
||||
}
|
||||
|
||||
/** Checks whether `lhs` and `rhs` are in non-descending order. */
|
||||
inline bool operator<=(const Timestamp& lhs, const Timestamp& rhs) {
|
||||
return !(lhs > rhs);
|
||||
}
|
||||
|
||||
/** Checks `lhs` and `rhs` for inequality. */
|
||||
inline bool operator!=(const Timestamp& lhs, const Timestamp& rhs) {
|
||||
return lhs < rhs || lhs > rhs;
|
||||
}
|
||||
|
||||
/** Checks `lhs` and `rhs` for equality. */
|
||||
inline bool operator==(const Timestamp& lhs, const Timestamp& rhs) {
|
||||
return !(lhs != rhs);
|
||||
}
|
||||
|
||||
#if !defined(_STLPORT_VERSION)
|
||||
|
||||
// Make sure the header compiles even when included after `<windows.h>` without
|
||||
// `NOMINMAX` defined. `push/pop_macro` pragmas are supported by Visual Studio
|
||||
// as well as Clang and GCC.
|
||||
#pragma push_macro("min")
|
||||
#pragma push_macro("max")
|
||||
#undef min
|
||||
#undef max
|
||||
|
||||
template <typename Clock, typename Duration>
|
||||
std::chrono::time_point<Clock, Duration> Timestamp::ToTimePoint() const {
|
||||
namespace chr = std::chrono;
|
||||
using TimePoint = chr::time_point<Clock, Duration>;
|
||||
|
||||
// Saturate on overflow
|
||||
const auto max_seconds = chr::duration_cast<chr::seconds>(Duration::max());
|
||||
if (seconds_ > 0 && max_seconds.count() <= seconds_) {
|
||||
return TimePoint{Duration::max()};
|
||||
}
|
||||
const auto min_seconds = chr::duration_cast<chr::seconds>(Duration::min());
|
||||
if (seconds_ < 0 && min_seconds.count() >= seconds_) {
|
||||
return TimePoint{Duration::min()};
|
||||
}
|
||||
|
||||
const auto seconds = chr::duration_cast<Duration>(chr::seconds(seconds_));
|
||||
const auto nanoseconds =
|
||||
chr::duration_cast<Duration>(chr::nanoseconds(nanoseconds_));
|
||||
return TimePoint{seconds + nanoseconds};
|
||||
}
|
||||
|
||||
#pragma pop_macro("max")
|
||||
#pragma pop_macro("min")
|
||||
|
||||
#endif // !defined(_STLPORT_VERSION)
|
||||
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_TIMESTAMP_H_
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2020 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 FIRESTORE_CORE_SRC_API_API_FWD_H_
|
||||
#define FIRESTORE_CORE_SRC_API_API_FWD_H_
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
|
||||
namespace core {
|
||||
template <typename T>
|
||||
class EventListener;
|
||||
class Query;
|
||||
} // namespace core
|
||||
|
||||
namespace api {
|
||||
|
||||
class CollectionReference;
|
||||
class DocumentChange;
|
||||
class DocumentReference;
|
||||
class DocumentSnapshot;
|
||||
class Firestore;
|
||||
class ListenerRegistration;
|
||||
class Query;
|
||||
class QuerySnapshot;
|
||||
class Settings;
|
||||
class SnapshotMetadata;
|
||||
class WriteBatch;
|
||||
|
||||
enum class Source;
|
||||
|
||||
using DocumentSnapshotListener =
|
||||
std::unique_ptr<core::EventListener<DocumentSnapshot>>;
|
||||
|
||||
using QuerySnapshotListener =
|
||||
std::unique_ptr<core::EventListener<QuerySnapshot>>;
|
||||
|
||||
using QueryCallback = std::function<void(core::Query, bool)>;
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_API_API_FWD_H_
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/api/collection_reference.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/api/document_reference.h"
|
||||
#include "Firestore/core/src/model/document_key.h"
|
||||
#include "Firestore/core/src/model/resource_path.h"
|
||||
#include "Firestore/core/src/util/autoid.h"
|
||||
#include "Firestore/core/src/util/exception.h"
|
||||
#include "Firestore/core/src/util/hashing.h"
|
||||
#include "Firestore/core/src/util/string_apple.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace api {
|
||||
namespace {
|
||||
|
||||
using core::Query;
|
||||
using model::DocumentKey;
|
||||
using model::ResourcePath;
|
||||
using util::ThrowInvalidArgument;
|
||||
|
||||
Query MakeQuery(model::ResourcePath path) {
|
||||
if (path.size() % 2 != 1) {
|
||||
ThrowInvalidArgument(
|
||||
"Invalid collection reference. Collection references "
|
||||
"must have an odd number of segments, but %s has %s",
|
||||
path.CanonicalString(), path.size());
|
||||
}
|
||||
|
||||
return Query(std::move(path));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CollectionReference::CollectionReference(model::ResourcePath path,
|
||||
std::shared_ptr<Firestore> firestore)
|
||||
: Query(MakeQuery(std::move(path)), std::move(firestore)) {
|
||||
}
|
||||
|
||||
bool operator==(const CollectionReference& lhs,
|
||||
const CollectionReference& rhs) {
|
||||
return lhs.firestore() == rhs.firestore() && lhs.query() == rhs.query();
|
||||
}
|
||||
|
||||
size_t CollectionReference::Hash() const {
|
||||
return util::Hash(firestore().get(), query());
|
||||
}
|
||||
|
||||
const std::string& CollectionReference::collection_id() const {
|
||||
return query().path().last_segment();
|
||||
}
|
||||
|
||||
absl::optional<DocumentReference> CollectionReference::parent() const {
|
||||
ResourcePath parent_path = query().path().PopLast();
|
||||
if (parent_path.empty()) {
|
||||
return absl::nullopt;
|
||||
} else {
|
||||
return DocumentReference(DocumentKey(std::move(parent_path)), firestore());
|
||||
}
|
||||
}
|
||||
|
||||
std::string CollectionReference::path() const {
|
||||
return query().path().CanonicalString();
|
||||
}
|
||||
|
||||
DocumentReference CollectionReference::Document(
|
||||
const std::string& document_path) const {
|
||||
ResourcePath sub_path = ResourcePath::FromString(document_path);
|
||||
ResourcePath path = query().path().Append(sub_path);
|
||||
return DocumentReference(std::move(path), firestore());
|
||||
}
|
||||
|
||||
DocumentReference CollectionReference::AddDocument(
|
||||
core::ParsedSetData&& data, util::StatusCallback callback) const {
|
||||
DocumentReference doc_ref = Document();
|
||||
doc_ref.SetData(std::move(data), std::move(callback));
|
||||
return doc_ref;
|
||||
}
|
||||
|
||||
DocumentReference CollectionReference::Document() const {
|
||||
DocumentKey key(query().path().Append(util::CreateAutoId()));
|
||||
return DocumentReference(std::move(key), firestore());
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_API_COLLECTION_REFERENCE_H_
|
||||
#define FIRESTORE_CORE_SRC_API_COLLECTION_REFERENCE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "Firestore/core/src/api/api_fwd.h"
|
||||
#include "Firestore/core/src/api/query_core.h"
|
||||
#include "Firestore/core/src/core/core_fwd.h"
|
||||
#include "absl/types/optional.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
|
||||
namespace model {
|
||||
class ResourcePath;
|
||||
} // namespace model
|
||||
|
||||
namespace api {
|
||||
|
||||
/**
|
||||
* A `CollectionReference` object can be used for adding documents, getting
|
||||
* document references, and querying for documents (using the methods inherited
|
||||
* from `Query`).
|
||||
*/
|
||||
class CollectionReference : public Query {
|
||||
public:
|
||||
CollectionReference() = default;
|
||||
CollectionReference(model::ResourcePath path,
|
||||
std::shared_ptr<Firestore> firestore);
|
||||
|
||||
/** ID of the referenced collection. */
|
||||
const std::string& collection_id() const;
|
||||
|
||||
/**
|
||||
* For subcollections, `parent` returns the containing `DocumentReference`.
|
||||
* For root collections, nullopt is returned.
|
||||
*/
|
||||
absl::optional<DocumentReference> parent() const;
|
||||
|
||||
/**
|
||||
* A string containing the slash-separated path to this `CollectionReference`
|
||||
* (relative to the root of the database).
|
||||
*/
|
||||
std::string path() const;
|
||||
|
||||
/**
|
||||
* Returns a `DocumentReference` pointing to a new document with an
|
||||
* auto-generated ID.
|
||||
*/
|
||||
DocumentReference Document() const;
|
||||
|
||||
/**
|
||||
* Gets a `DocumentReference` referring to the document at the specified path,
|
||||
* relative to this collection's own path.
|
||||
*
|
||||
* @param document_path The slash-separated relative path of the document for
|
||||
* which to get a `DocumentReference`.
|
||||
*
|
||||
* @return The `DocumentReference` for the specified document path.
|
||||
*/
|
||||
DocumentReference Document(const std::string& document_path) const;
|
||||
|
||||
/**
|
||||
* Add a new document to this collection with the specified data, assigning it
|
||||
* a document ID automatically.
|
||||
*
|
||||
* @param data A `ParsedSetData` containing the data for the new document.
|
||||
* @param callback A callback to execute once the document has been
|
||||
* successfully written to the server. This callback will not be called
|
||||
* while the client is offline, though local changes will be visible
|
||||
* immediately.
|
||||
*
|
||||
* @return A `DocumentReference` pointing to the newly created document.
|
||||
*/
|
||||
DocumentReference AddDocument(core::ParsedSetData&& data,
|
||||
util::StatusCallback callback) const;
|
||||
|
||||
size_t Hash() const;
|
||||
};
|
||||
|
||||
bool operator==(const CollectionReference& lhs, const CollectionReference& rhs);
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_API_COLLECTION_REFERENCE_H_
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/api/document_change.h"
|
||||
|
||||
#include "Firestore/core/src/util/hashing.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace api {
|
||||
|
||||
size_t DocumentChange::Hash() const {
|
||||
return util::Hash(type_, document_, old_index_, new_index_);
|
||||
}
|
||||
|
||||
bool operator==(const DocumentChange& lhs, const DocumentChange& rhs) {
|
||||
return lhs.type() == rhs.type() && lhs.document() == rhs.document() &&
|
||||
lhs.old_index() == rhs.old_index() &&
|
||||
lhs.new_index() == rhs.new_index();
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_API_DOCUMENT_CHANGE_H_
|
||||
#define FIRESTORE_CORE_SRC_API_DOCUMENT_CHANGE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/api/document_snapshot.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace api {
|
||||
|
||||
class DocumentChange {
|
||||
public:
|
||||
enum class Type { Added, Modified, Removed };
|
||||
|
||||
DocumentChange() = default;
|
||||
DocumentChange(Type type,
|
||||
DocumentSnapshot document,
|
||||
size_t old_index,
|
||||
size_t new_index)
|
||||
: type_(type),
|
||||
document_(std::move(document)),
|
||||
old_index_(old_index),
|
||||
new_index_(new_index) {
|
||||
}
|
||||
|
||||
size_t Hash() const;
|
||||
|
||||
Type type() const {
|
||||
return type_;
|
||||
}
|
||||
|
||||
DocumentSnapshot document() const {
|
||||
return document_;
|
||||
}
|
||||
|
||||
size_t old_index() const {
|
||||
return old_index_;
|
||||
}
|
||||
|
||||
size_t new_index() const {
|
||||
return new_index_;
|
||||
}
|
||||
|
||||
const std::shared_ptr<Firestore>& firestore() const {
|
||||
return document_.firestore();
|
||||
}
|
||||
|
||||
/**
|
||||
* A sentinel return value for old_index() and new_index() indicating that
|
||||
* there's no relevant index to return because the document was newly added
|
||||
* or removed respectively.
|
||||
*/
|
||||
static constexpr size_t npos = static_cast<size_t>(-1);
|
||||
|
||||
private:
|
||||
Type type_;
|
||||
DocumentSnapshot document_;
|
||||
size_t old_index_;
|
||||
size_t new_index_;
|
||||
};
|
||||
|
||||
bool operator==(const DocumentChange& lhs, const DocumentChange& rhs);
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_API_DOCUMENT_CHANGE_H_
|
||||
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* Copyright 2019 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 "Firestore/core/src/api/document_reference.h"
|
||||
|
||||
#include <future> // NOLINT(build/c++11)
|
||||
#include <memory>
|
||||
|
||||
#include "Firestore/core/src/api/collection_reference.h"
|
||||
#include "Firestore/core/src/api/document_snapshot.h"
|
||||
#include "Firestore/core/src/api/firestore.h"
|
||||
#include "Firestore/core/src/api/query_listener_registration.h"
|
||||
#include "Firestore/core/src/api/source.h"
|
||||
#include "Firestore/core/src/core/firestore_client.h"
|
||||
#include "Firestore/core/src/core/listen_options.h"
|
||||
#include "Firestore/core/src/core/user_data.h"
|
||||
#include "Firestore/core/src/core/view_snapshot.h"
|
||||
#include "Firestore/core/src/model/delete_mutation.h"
|
||||
#include "Firestore/core/src/model/document_key.h"
|
||||
#include "Firestore/core/src/model/document_set.h"
|
||||
#include "Firestore/core/src/model/precondition.h"
|
||||
#include "Firestore/core/src/model/resource_path.h"
|
||||
#include "Firestore/core/src/util/error_apple.h"
|
||||
#include "Firestore/core/src/util/firestore_exceptions.h"
|
||||
#include "Firestore/core/src/util/hard_assert.h"
|
||||
#include "Firestore/core/src/util/hashing.h"
|
||||
#include "Firestore/core/src/util/status.h"
|
||||
#include "Firestore/core/src/util/statusor.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace api {
|
||||
|
||||
using core::AsyncEventListener;
|
||||
using core::EventListener;
|
||||
using core::ListenOptions;
|
||||
using core::QueryListener;
|
||||
using core::ViewSnapshot;
|
||||
using model::DeleteMutation;
|
||||
using model::Document;
|
||||
using model::DocumentKey;
|
||||
using model::Precondition;
|
||||
using model::ResourcePath;
|
||||
using util::Status;
|
||||
using util::StatusOr;
|
||||
using util::StatusOrCallback;
|
||||
|
||||
DocumentReference::DocumentReference(model::ResourcePath path,
|
||||
std::shared_ptr<Firestore> firestore)
|
||||
: firestore_{std::move(firestore)} {
|
||||
if (path.size() % 2 != 0) {
|
||||
util::ThrowInvalidArgument(
|
||||
"Invalid document reference. Document references must have an even "
|
||||
"number of segments, but %s has %s",
|
||||
path.CanonicalString(), path.size());
|
||||
}
|
||||
key_ = DocumentKey{std::move(path)};
|
||||
}
|
||||
|
||||
size_t DocumentReference::Hash() const {
|
||||
return util::Hash(firestore_.get(), key_);
|
||||
}
|
||||
|
||||
const std::string& DocumentReference::document_id() const {
|
||||
return key_.path().last_segment();
|
||||
}
|
||||
|
||||
CollectionReference DocumentReference::Parent() const {
|
||||
return CollectionReference{key_.path().PopLast(), firestore_};
|
||||
}
|
||||
|
||||
std::string DocumentReference::Path() const {
|
||||
return key_.path().CanonicalString();
|
||||
}
|
||||
|
||||
CollectionReference DocumentReference::GetCollectionReference(
|
||||
const std::string& collection_path) const {
|
||||
ResourcePath sub_path = ResourcePath::FromString(collection_path);
|
||||
ResourcePath path = key_.path().Append(sub_path);
|
||||
return CollectionReference{path, firestore_};
|
||||
}
|
||||
|
||||
void DocumentReference::SetData(core::ParsedSetData&& set_data,
|
||||
util::StatusCallback callback) {
|
||||
firestore_->client()->WriteMutations(
|
||||
{std::move(set_data).ToMutation(key(), Precondition::None())},
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
void DocumentReference::UpdateData(core::ParsedUpdateData&& update_data,
|
||||
util::StatusCallback callback) {
|
||||
firestore_->client()->WriteMutations(
|
||||
{std::move(update_data).ToMutation(key(), Precondition::Exists(true))},
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
void DocumentReference::DeleteDocument(util::StatusCallback callback) {
|
||||
DeleteMutation mutation(key_, Precondition::None());
|
||||
firestore_->client()->WriteMutations({mutation}, std::move(callback));
|
||||
}
|
||||
|
||||
void DocumentReference::GetDocument(Source source,
|
||||
DocumentSnapshotListener&& callback) {
|
||||
if (source == Source::Cache) {
|
||||
firestore_->client()->GetDocumentFromLocalCache(*this, std::move(callback));
|
||||
return;
|
||||
}
|
||||
|
||||
ListenOptions options(
|
||||
/*include_query_metadata_changes=*/true,
|
||||
/*include_document_metadata_changes=*/true,
|
||||
/*wait_for_sync_when_online=*/true);
|
||||
|
||||
class ListenOnce : public EventListener<DocumentSnapshot> {
|
||||
public:
|
||||
ListenOnce(Source source, DocumentSnapshotListener&& listener)
|
||||
: source_(source), listener_(std::move(listener)) {
|
||||
}
|
||||
|
||||
void OnEvent(StatusOr<DocumentSnapshot> maybe_snapshot) override {
|
||||
if (!maybe_snapshot.ok()) {
|
||||
listener_->OnEvent(std::move(maybe_snapshot));
|
||||
return;
|
||||
}
|
||||
|
||||
DocumentSnapshot snapshot = std::move(maybe_snapshot).ValueOrDie();
|
||||
|
||||
// Remove query first before passing event to user to avoid user actions
|
||||
// affecting the now stale query.
|
||||
std::unique_ptr<ListenerRegistration> registration =
|
||||
registration_promise_.get_future().get();
|
||||
registration->Remove();
|
||||
|
||||
if (!snapshot.exists() && snapshot.metadata().from_cache()) {
|
||||
// TODO(dimond): Reconsider how to raise missing documents when
|
||||
// offline. If we're online and the document doesn't exist then we
|
||||
// call the callback with a document with document.exists set to
|
||||
// false. If we're offline however, we call the callback
|
||||
// with an error. Two options: 1) Cache the negative response from the
|
||||
// server so we can deliver that even when you're offline.
|
||||
// 2) Actually call the callback with an error if the
|
||||
// document doesn't exist when you are offline.
|
||||
listener_->OnEvent(
|
||||
Status{Error::kErrorUnavailable,
|
||||
"Failed to get document because the client is offline."});
|
||||
} else if (snapshot.exists() && snapshot.metadata().from_cache() &&
|
||||
source_ == Source::Server) {
|
||||
listener_->OnEvent(
|
||||
Status{Error::kErrorUnavailable,
|
||||
"Failed to get document from server. (However, "
|
||||
"this document does exist in the local cache. Run "
|
||||
"again without setting source to "
|
||||
"FirestoreSourceServer to retrieve the cached "
|
||||
"document.)"});
|
||||
} else {
|
||||
listener_->OnEvent(std::move(snapshot));
|
||||
}
|
||||
}
|
||||
|
||||
void Resolve(std::unique_ptr<ListenerRegistration> registration) {
|
||||
registration_promise_.set_value(std::move(registration));
|
||||
}
|
||||
|
||||
private:
|
||||
Source source_;
|
||||
DocumentSnapshotListener listener_;
|
||||
|
||||
std::promise<std::unique_ptr<ListenerRegistration>> registration_promise_;
|
||||
};
|
||||
auto listener = absl::make_unique<ListenOnce>(source, std::move(callback));
|
||||
auto listener_unowned = listener.get();
|
||||
|
||||
std::unique_ptr<ListenerRegistration> registration =
|
||||
AddSnapshotListener(std::move(options), std::move(listener));
|
||||
|
||||
listener_unowned->Resolve(std::move(registration));
|
||||
}
|
||||
|
||||
std::unique_ptr<ListenerRegistration> DocumentReference::AddSnapshotListener(
|
||||
ListenOptions options, DocumentSnapshotListener&& user_listener) {
|
||||
// Convert from ViewSnapshots to DocumentSnapshots.
|
||||
class Converter : public EventListener<ViewSnapshot> {
|
||||
public:
|
||||
Converter(DocumentReference* parent,
|
||||
DocumentSnapshotListener&& user_listener)
|
||||
: firestore_(parent->firestore_),
|
||||
key_(parent->key_),
|
||||
user_listener_(std::move(user_listener)) {
|
||||
}
|
||||
|
||||
void OnEvent(StatusOr<ViewSnapshot> maybe_snapshot) override {
|
||||
if (!maybe_snapshot.ok()) {
|
||||
user_listener_->OnEvent(maybe_snapshot.status());
|
||||
return;
|
||||
}
|
||||
|
||||
ViewSnapshot snapshot = std::move(maybe_snapshot).ValueOrDie();
|
||||
HARD_ASSERT(snapshot.documents().size() <= 1,
|
||||
"Too many documents returned on a document query");
|
||||
absl::optional<Document> document =
|
||||
snapshot.documents().GetDocument(key_);
|
||||
|
||||
bool has_pending_writes =
|
||||
document ? snapshot.mutated_keys().contains(key_)
|
||||
// We don't raise `has_pending_writes` for deleted documents.
|
||||
: false;
|
||||
|
||||
DocumentSnapshot result{
|
||||
firestore_, key_, document,
|
||||
SnapshotMetadata{has_pending_writes, snapshot.from_cache()}};
|
||||
user_listener_->OnEvent(std::move(result));
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Firestore> firestore_;
|
||||
DocumentKey key_;
|
||||
DocumentSnapshotListener user_listener_;
|
||||
};
|
||||
auto view_listener =
|
||||
absl::make_unique<Converter>(this, std::move(user_listener));
|
||||
|
||||
// Call the view_listener on the user Executor.
|
||||
auto async_listener = AsyncEventListener<ViewSnapshot>::Create(
|
||||
firestore_->client()->user_executor(), std::move(view_listener));
|
||||
|
||||
core::Query query(key_.path());
|
||||
std::shared_ptr<QueryListener> query_listener =
|
||||
firestore_->client()->ListenToQuery(std::move(query), options,
|
||||
async_listener);
|
||||
|
||||
return absl::make_unique<QueryListenerRegistration>(
|
||||
firestore_->client(), std::move(async_listener),
|
||||
std::move(query_listener));
|
||||
}
|
||||
|
||||
bool operator==(const DocumentReference& lhs, const DocumentReference& rhs) {
|
||||
return lhs.firestore() == rhs.firestore() && lhs.key() == rhs.key();
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2019 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 FIRESTORE_CORE_SRC_API_DOCUMENT_REFERENCE_H_
|
||||
#define FIRESTORE_CORE_SRC_API_DOCUMENT_REFERENCE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/api/api_fwd.h"
|
||||
#include "Firestore/core/src/core/core_fwd.h"
|
||||
#include "Firestore/core/src/model/document_key.h"
|
||||
#include "Firestore/core/src/util/status_fwd.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
|
||||
namespace model {
|
||||
class ResourcePath;
|
||||
} // namespace model
|
||||
|
||||
namespace api {
|
||||
|
||||
class DocumentReference {
|
||||
public:
|
||||
DocumentReference() = default;
|
||||
DocumentReference(model::ResourcePath path,
|
||||
std::shared_ptr<Firestore> firestore);
|
||||
DocumentReference(model::DocumentKey document_key,
|
||||
std::shared_ptr<Firestore> firestore)
|
||||
: firestore_{std::move(firestore)}, key_{std::move(document_key)} {
|
||||
}
|
||||
|
||||
size_t Hash() const;
|
||||
|
||||
const std::shared_ptr<Firestore>& firestore() const {
|
||||
return firestore_;
|
||||
}
|
||||
const model::DocumentKey& key() const {
|
||||
return key_;
|
||||
}
|
||||
|
||||
const std::string& document_id() const;
|
||||
|
||||
CollectionReference Parent() const;
|
||||
|
||||
std::string Path() const;
|
||||
|
||||
CollectionReference GetCollectionReference(
|
||||
const std::string& collection_path) const;
|
||||
|
||||
void SetData(core::ParsedSetData&& set_data, util::StatusCallback callback);
|
||||
|
||||
void UpdateData(core::ParsedUpdateData&& update_data,
|
||||
util::StatusCallback callback);
|
||||
|
||||
void DeleteDocument(util::StatusCallback callback);
|
||||
|
||||
void GetDocument(Source source, DocumentSnapshotListener&& callback);
|
||||
|
||||
std::unique_ptr<ListenerRegistration> AddSnapshotListener(
|
||||
core::ListenOptions options, DocumentSnapshotListener&& listener);
|
||||
|
||||
private:
|
||||
std::shared_ptr<Firestore> firestore_;
|
||||
model::DocumentKey key_;
|
||||
};
|
||||
|
||||
bool operator==(const DocumentReference& lhs, const DocumentReference& rhs);
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_API_DOCUMENT_REFERENCE_H_
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/api/document_snapshot.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/api/document_reference.h"
|
||||
#include "Firestore/core/src/model/resource_path.h"
|
||||
#include "Firestore/core/src/util/hashing.h"
|
||||
#include "absl/types/optional.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace api {
|
||||
|
||||
using model::Document;
|
||||
using model::DocumentKey;
|
||||
using model::FieldPath;
|
||||
using model::ObjectValue;
|
||||
|
||||
DocumentSnapshot DocumentSnapshot::FromDocument(
|
||||
std::shared_ptr<Firestore> firestore,
|
||||
model::Document document,
|
||||
SnapshotMetadata metadata) {
|
||||
return DocumentSnapshot{std::move(firestore), document->key(), document,
|
||||
std::move(metadata)};
|
||||
}
|
||||
|
||||
DocumentSnapshot DocumentSnapshot::FromNoDocument(
|
||||
std::shared_ptr<Firestore> firestore,
|
||||
model::DocumentKey key,
|
||||
SnapshotMetadata metadata) {
|
||||
return DocumentSnapshot{std::move(firestore), std::move(key), absl::nullopt,
|
||||
std::move(metadata)};
|
||||
}
|
||||
|
||||
DocumentSnapshot::DocumentSnapshot(std::shared_ptr<Firestore> firestore,
|
||||
model::DocumentKey document_key,
|
||||
absl::optional<Document> document,
|
||||
SnapshotMetadata metadata)
|
||||
: firestore_{std::move(firestore)},
|
||||
internal_key_{std::move(document_key)},
|
||||
internal_document_{std::move(document)},
|
||||
metadata_{std::move(metadata)} {
|
||||
}
|
||||
|
||||
size_t DocumentSnapshot::Hash() const {
|
||||
return util::Hash(firestore_.get(), internal_key_, internal_document_,
|
||||
metadata_);
|
||||
}
|
||||
|
||||
bool DocumentSnapshot::exists() const {
|
||||
return internal_document_.has_value();
|
||||
}
|
||||
|
||||
const absl::optional<Document>& DocumentSnapshot::internal_document() const {
|
||||
return internal_document_;
|
||||
}
|
||||
|
||||
DocumentReference DocumentSnapshot::CreateReference() const {
|
||||
return DocumentReference{internal_key_, firestore_};
|
||||
}
|
||||
|
||||
const std::string& DocumentSnapshot::document_id() const {
|
||||
return internal_key_.path().last_segment();
|
||||
}
|
||||
|
||||
absl::optional<google_firestore_v1_Value> DocumentSnapshot::GetValue(
|
||||
const FieldPath& field_path) const {
|
||||
return internal_document_ ? (*internal_document_)->field(field_path)
|
||||
: absl::nullopt;
|
||||
}
|
||||
|
||||
bool operator==(const DocumentSnapshot& lhs, const DocumentSnapshot& rhs) {
|
||||
return lhs.firestore_ == rhs.firestore_ &&
|
||||
lhs.internal_key_ == rhs.internal_key_ &&
|
||||
lhs.exists() == rhs.exists() &&
|
||||
(lhs.exists() ? lhs.internal_document_->get().data() ==
|
||||
rhs.internal_document_->get().data()
|
||||
: true) &&
|
||||
lhs.metadata_ == rhs.metadata_;
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2019 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 FIRESTORE_CORE_SRC_API_DOCUMENT_SNAPSHOT_H_
|
||||
#define FIRESTORE_CORE_SRC_API_DOCUMENT_SNAPSHOT_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/api/snapshot_metadata.h"
|
||||
#include "Firestore/core/src/core/event_listener.h"
|
||||
#include "Firestore/core/src/model/document.h"
|
||||
#include "Firestore/core/src/model/document_key.h"
|
||||
#include "Firestore/core/src/model/model_fwd.h"
|
||||
#include "absl/types/optional.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace api {
|
||||
|
||||
class DocumentReference;
|
||||
class Firestore;
|
||||
|
||||
class DocumentSnapshot {
|
||||
public:
|
||||
DocumentSnapshot() = default;
|
||||
|
||||
static DocumentSnapshot FromDocument(std::shared_ptr<Firestore> firestore,
|
||||
model::Document document,
|
||||
SnapshotMetadata metadata);
|
||||
|
||||
static DocumentSnapshot FromNoDocument(std::shared_ptr<Firestore> firestore,
|
||||
model::DocumentKey key,
|
||||
SnapshotMetadata metadata);
|
||||
|
||||
size_t Hash() const;
|
||||
|
||||
bool exists() const;
|
||||
const absl::optional<model::Document>& internal_document() const;
|
||||
const std::string& document_id() const;
|
||||
|
||||
const SnapshotMetadata& metadata() const {
|
||||
return metadata_;
|
||||
}
|
||||
|
||||
DocumentReference CreateReference() const;
|
||||
|
||||
absl::optional<google_firestore_v1_Value> GetValue(
|
||||
const model::FieldPath& field_path) const;
|
||||
|
||||
const std::shared_ptr<Firestore>& firestore() const {
|
||||
return firestore_;
|
||||
}
|
||||
|
||||
friend bool operator==(const DocumentSnapshot& lhs,
|
||||
const DocumentSnapshot& rhs);
|
||||
|
||||
private:
|
||||
// TODO(b/146372592): Make this public once we can use Abseil across
|
||||
// iOS/public C++ library boundaries.
|
||||
friend class DocumentReference;
|
||||
|
||||
DocumentSnapshot(std::shared_ptr<Firestore> firestore,
|
||||
model::DocumentKey document_key,
|
||||
absl::optional<model::Document> document,
|
||||
SnapshotMetadata metadata);
|
||||
|
||||
private:
|
||||
std::shared_ptr<Firestore> firestore_;
|
||||
model::DocumentKey internal_key_;
|
||||
absl::optional<model::Document> internal_document_;
|
||||
SnapshotMetadata metadata_;
|
||||
};
|
||||
|
||||
using DocumentSnapshotListener =
|
||||
std::unique_ptr<core::EventListener<DocumentSnapshot>>;
|
||||
|
||||
inline bool operator!=(const DocumentSnapshot& lhs,
|
||||
const DocumentSnapshot& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_API_DOCUMENT_SNAPSHOT_H_
|
||||
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* Copyright 2019 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 "Firestore/core/src/api/firestore.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/api/collection_reference.h"
|
||||
#include "Firestore/core/src/api/document_reference.h"
|
||||
#include "Firestore/core/src/api/listener_registration.h"
|
||||
#include "Firestore/core/src/api/settings.h"
|
||||
#include "Firestore/core/src/api/snapshots_in_sync_listener_registration.h"
|
||||
#include "Firestore/core/src/api/write_batch.h"
|
||||
#include "Firestore/core/src/core/event_listener.h"
|
||||
#include "Firestore/core/src/core/firestore_client.h"
|
||||
#include "Firestore/core/src/core/query.h"
|
||||
#include "Firestore/core/src/core/transaction.h"
|
||||
#include "Firestore/core/src/credentials/empty_credentials_provider.h"
|
||||
#include "Firestore/core/src/local/leveldb_persistence.h"
|
||||
#include "Firestore/core/src/model/document_key.h"
|
||||
#include "Firestore/core/src/model/resource_path.h"
|
||||
#include "Firestore/core/src/remote/firebase_metadata_provider.h"
|
||||
#include "Firestore/core/src/remote/grpc_connection.h"
|
||||
#include "Firestore/core/src/util/async_queue.h"
|
||||
#include "Firestore/core/src/util/executor.h"
|
||||
#include "Firestore/core/src/util/hard_assert.h"
|
||||
#include "Firestore/core/src/util/status.h"
|
||||
#include "absl/memory/memory.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace api {
|
||||
|
||||
using core::AsyncEventListener;
|
||||
using core::DatabaseInfo;
|
||||
using core::FirestoreClient;
|
||||
using credentials::AuthCredentialsProvider;
|
||||
using local::LevelDbPersistence;
|
||||
using model::ResourcePath;
|
||||
using remote::FirebaseMetadataProvider;
|
||||
using remote::GrpcConnection;
|
||||
using util::AsyncQueue;
|
||||
using util::Empty;
|
||||
using util::Executor;
|
||||
using util::Status;
|
||||
|
||||
Firestore::Firestore(
|
||||
model::DatabaseId database_id,
|
||||
std::string persistence_key,
|
||||
std::shared_ptr<credentials::AuthCredentialsProvider>
|
||||
auth_credentials_provider,
|
||||
std::shared_ptr<credentials::AppCheckCredentialsProvider>
|
||||
app_check_credentials_provider,
|
||||
std::shared_ptr<AsyncQueue> worker_queue,
|
||||
std::unique_ptr<FirebaseMetadataProvider> firebase_metadata_provider,
|
||||
void* extension)
|
||||
: database_id_{std::move(database_id)},
|
||||
app_check_credentials_provider_{
|
||||
std::move(app_check_credentials_provider)},
|
||||
auth_credentials_provider_{std::move(auth_credentials_provider)},
|
||||
persistence_key_{std::move(persistence_key)},
|
||||
worker_queue_{std::move(worker_queue)},
|
||||
firebase_metadata_provider_{std::move(firebase_metadata_provider)},
|
||||
extension_{extension} {
|
||||
}
|
||||
|
||||
Firestore::~Firestore() {
|
||||
Dispose();
|
||||
}
|
||||
|
||||
void Firestore::Dispose() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
// If the client hasn't been configured yet we don't need to create it just
|
||||
// to tear it down.
|
||||
if (!client_) return;
|
||||
|
||||
client_->Dispose();
|
||||
}
|
||||
|
||||
const std::shared_ptr<FirestoreClient>& Firestore::client() {
|
||||
HARD_ASSERT(client_, "Client is not yet configured.");
|
||||
return client_;
|
||||
}
|
||||
|
||||
const std::shared_ptr<AsyncQueue>& Firestore::worker_queue() {
|
||||
return worker_queue_;
|
||||
}
|
||||
|
||||
const Settings& Firestore::settings() const {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
return settings_;
|
||||
}
|
||||
|
||||
void Firestore::set_settings(const Settings& settings) {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
if (client_) {
|
||||
util::ThrowIllegalState(
|
||||
"Firestore instance has already been started and its settings can "
|
||||
"no longer be changed. You can only set settings before calling any "
|
||||
"other methods on a Firestore instance.");
|
||||
}
|
||||
if (!settings.ssl_enabled() && settings.host() == Settings::DefaultHost) {
|
||||
util::ThrowIllegalState(
|
||||
"You can't set the 'sslEnabled' setting to false unless you also set a "
|
||||
"non-default 'host'.");
|
||||
}
|
||||
|
||||
settings_ = settings;
|
||||
}
|
||||
|
||||
void Firestore::set_user_executor(std::unique_ptr<Executor> user_executor) {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
HARD_ASSERT(!client_ && user_executor,
|
||||
"set_user_executor() must be called with a valid executor, "
|
||||
"before the client is initialized.");
|
||||
user_executor_ = std::move(user_executor);
|
||||
}
|
||||
|
||||
CollectionReference Firestore::GetCollection(
|
||||
const std::string& collection_path) {
|
||||
EnsureClientConfigured();
|
||||
ResourcePath path = ResourcePath::FromString(collection_path);
|
||||
return CollectionReference{std::move(path), shared_from_this()};
|
||||
}
|
||||
|
||||
DocumentReference Firestore::GetDocument(const std::string& document_path) {
|
||||
EnsureClientConfigured();
|
||||
return DocumentReference{ResourcePath::FromString(document_path),
|
||||
shared_from_this()};
|
||||
}
|
||||
|
||||
WriteBatch Firestore::GetBatch() {
|
||||
EnsureClientConfigured();
|
||||
return WriteBatch(shared_from_this());
|
||||
}
|
||||
|
||||
core::Query Firestore::GetCollectionGroup(std::string collection_id) {
|
||||
EnsureClientConfigured();
|
||||
|
||||
return core::Query(ResourcePath::Empty(), std::make_shared<const std::string>(
|
||||
std::move(collection_id)));
|
||||
}
|
||||
|
||||
void Firestore::RunTransaction(
|
||||
core::TransactionUpdateCallback update_callback,
|
||||
core::TransactionResultCallback result_callback) {
|
||||
EnsureClientConfigured();
|
||||
|
||||
client_->Transaction(5, std::move(update_callback),
|
||||
std::move(result_callback));
|
||||
}
|
||||
|
||||
void Firestore::Terminate(util::StatusCallback callback) {
|
||||
// The client must be initialized to ensure that all subsequent API usage
|
||||
// throws an exception.
|
||||
EnsureClientConfigured();
|
||||
client_->TerminateAsync(std::move(callback));
|
||||
}
|
||||
|
||||
void Firestore::WaitForPendingWrites(util::StatusCallback callback) {
|
||||
EnsureClientConfigured();
|
||||
client_->WaitForPendingWrites(std::move(callback));
|
||||
}
|
||||
|
||||
void Firestore::ClearPersistence(util::StatusCallback callback) {
|
||||
worker_queue()->EnqueueEvenWhileRestricted([this, callback] {
|
||||
auto MaybeCallback = [=](Status status) {
|
||||
if (callback) {
|
||||
user_executor_->Execute([=] { callback(status); });
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
if (client_ && !client_->is_terminated()) {
|
||||
MaybeCallback(util::Status(
|
||||
Error::kErrorFailedPrecondition,
|
||||
"Persistence cannot be cleared while the client is running."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
MaybeCallback(LevelDbPersistence::ClearPersistence(MakeDatabaseInfo()));
|
||||
});
|
||||
}
|
||||
|
||||
void Firestore::EnableNetwork(util::StatusCallback callback) {
|
||||
EnsureClientConfigured();
|
||||
client_->EnableNetwork(std::move(callback));
|
||||
}
|
||||
|
||||
void Firestore::DisableNetwork(util::StatusCallback callback) {
|
||||
EnsureClientConfigured();
|
||||
client_->DisableNetwork(std::move(callback));
|
||||
}
|
||||
|
||||
void Firestore::SetClientLanguage(std::string language_token) {
|
||||
GrpcConnection::SetClientLanguage(std::move(language_token));
|
||||
}
|
||||
|
||||
std::unique_ptr<ListenerRegistration> Firestore::AddSnapshotsInSyncListener(
|
||||
std::unique_ptr<core::EventListener<Empty>> listener) {
|
||||
EnsureClientConfigured();
|
||||
auto async_listener = AsyncEventListener<Empty>::Create(
|
||||
client_->user_executor(), std::move(listener));
|
||||
client_->AddSnapshotsInSyncListener(async_listener);
|
||||
return absl::make_unique<SnapshotsInSyncListenerRegistration>(
|
||||
client_, std::move(async_listener));
|
||||
}
|
||||
|
||||
void Firestore::EnsureClientConfigured() {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
|
||||
if (!client_) {
|
||||
HARD_ASSERT(worker_queue_, "Expected non-null worker queue");
|
||||
client_ = FirestoreClient::Create(
|
||||
MakeDatabaseInfo(), settings_, std::move(auth_credentials_provider_),
|
||||
std::move(app_check_credentials_provider_), user_executor_,
|
||||
worker_queue_, std::move(firebase_metadata_provider_));
|
||||
}
|
||||
}
|
||||
|
||||
DatabaseInfo Firestore::MakeDatabaseInfo() const {
|
||||
return DatabaseInfo(database_id_, persistence_key_, settings_.host(),
|
||||
settings_.ssl_enabled());
|
||||
}
|
||||
|
||||
std::shared_ptr<LoadBundleTask> Firestore::LoadBundle(
|
||||
std::unique_ptr<util::ByteStream> bundle_data) {
|
||||
EnsureClientConfigured();
|
||||
|
||||
auto task = std::make_shared<LoadBundleTask>(user_executor_);
|
||||
client_->LoadBundle(std::move(bundle_data), task);
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
void Firestore::GetNamedQuery(const std::string& name,
|
||||
api::QueryCallback callback) {
|
||||
EnsureClientConfigured();
|
||||
client_->GetNamedQuery(name, std::move(callback));
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2019 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 FIRESTORE_CORE_SRC_API_FIRESTORE_H_
|
||||
#define FIRESTORE_CORE_SRC_API_FIRESTORE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <mutex> // NOLINT(build/c++11)
|
||||
#include <string>
|
||||
|
||||
#include "Firestore/core/src/api/api_fwd.h"
|
||||
#include "Firestore/core/src/api/load_bundle_task.h"
|
||||
#include "Firestore/core/src/api/settings.h"
|
||||
#include "Firestore/core/src/core/core_fwd.h"
|
||||
#include "Firestore/core/src/credentials/credentials_fwd.h"
|
||||
#include "Firestore/core/src/model/database_id.h"
|
||||
#include "Firestore/core/src/util/byte_stream.h"
|
||||
#include "Firestore/core/src/util/status_fwd.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
|
||||
namespace remote {
|
||||
class FirebaseMetadataProvider;
|
||||
} // namespace remote
|
||||
|
||||
namespace util {
|
||||
class AsyncQueue;
|
||||
class Executor;
|
||||
|
||||
struct Empty;
|
||||
} // namespace util
|
||||
|
||||
namespace api {
|
||||
|
||||
class Firestore : public std::enable_shared_from_this<Firestore> {
|
||||
public:
|
||||
Firestore() = default;
|
||||
|
||||
Firestore(model::DatabaseId database_id,
|
||||
std::string persistence_key,
|
||||
std::shared_ptr<credentials::AuthCredentialsProvider>
|
||||
auth_credentials_provider,
|
||||
std::shared_ptr<credentials::AppCheckCredentialsProvider>
|
||||
app_check_credentials_provider,
|
||||
std::shared_ptr<util::AsyncQueue> worker_queue,
|
||||
std::unique_ptr<remote::FirebaseMetadataProvider>
|
||||
firebase_metadata_provider,
|
||||
void* extension);
|
||||
|
||||
~Firestore();
|
||||
|
||||
void Dispose();
|
||||
|
||||
const model::DatabaseId& database_id() const {
|
||||
return database_id_;
|
||||
}
|
||||
|
||||
const std::string& persistence_key() const {
|
||||
return persistence_key_;
|
||||
}
|
||||
|
||||
const std::shared_ptr<core::FirestoreClient>& client();
|
||||
|
||||
const std::shared_ptr<util::AsyncQueue>& worker_queue();
|
||||
|
||||
void* extension() {
|
||||
return extension_;
|
||||
}
|
||||
|
||||
const Settings& settings() const;
|
||||
void set_settings(const Settings& settings);
|
||||
|
||||
void set_user_executor(std::unique_ptr<util::Executor> user_executor);
|
||||
|
||||
CollectionReference GetCollection(const std::string& collection_path);
|
||||
DocumentReference GetDocument(const std::string& document_path);
|
||||
WriteBatch GetBatch();
|
||||
core::Query GetCollectionGroup(std::string collection_id);
|
||||
|
||||
void RunTransaction(core::TransactionUpdateCallback update_callback,
|
||||
core::TransactionResultCallback result_callback);
|
||||
|
||||
void Terminate(util::StatusCallback callback);
|
||||
void ClearPersistence(util::StatusCallback callback);
|
||||
void WaitForPendingWrites(util::StatusCallback callback);
|
||||
std::unique_ptr<ListenerRegistration> AddSnapshotsInSyncListener(
|
||||
std::unique_ptr<core::EventListener<util::Empty>> listener);
|
||||
|
||||
void EnableNetwork(util::StatusCallback callback);
|
||||
void DisableNetwork(util::StatusCallback callback);
|
||||
|
||||
std::shared_ptr<api::LoadBundleTask> LoadBundle(
|
||||
std::unique_ptr<util::ByteStream> bundle_data);
|
||||
void GetNamedQuery(const std::string& name, api::QueryCallback callback);
|
||||
|
||||
/**
|
||||
* Sets the language of the public API in the format of
|
||||
* "gl-<language>/<version>" where version might be blank, e.g. `gl-objc/`.
|
||||
*/
|
||||
static void SetClientLanguage(std::string language_token);
|
||||
|
||||
private:
|
||||
void EnsureClientConfigured();
|
||||
core::DatabaseInfo MakeDatabaseInfo() const;
|
||||
|
||||
model::DatabaseId database_id_;
|
||||
std::shared_ptr<credentials::AppCheckCredentialsProvider>
|
||||
app_check_credentials_provider_;
|
||||
std::shared_ptr<credentials::AuthCredentialsProvider>
|
||||
auth_credentials_provider_;
|
||||
std::string persistence_key_;
|
||||
|
||||
std::shared_ptr<util::Executor> user_executor_;
|
||||
std::shared_ptr<util::AsyncQueue> worker_queue_;
|
||||
|
||||
std::unique_ptr<remote::FirebaseMetadataProvider> firebase_metadata_provider_;
|
||||
|
||||
void* extension_ = nullptr;
|
||||
|
||||
Settings settings_;
|
||||
|
||||
mutable std::mutex mutex_;
|
||||
|
||||
std::shared_ptr<core::FirestoreClient> client_;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_API_FIRESTORE_H_
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_API_LISTENER_REGISTRATION_H_
|
||||
#define FIRESTORE_CORE_SRC_API_LISTENER_REGISTRATION_H_
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
|
||||
namespace core {
|
||||
class FirestoreClient;
|
||||
} // namespace core
|
||||
|
||||
namespace api {
|
||||
|
||||
/**
|
||||
* An internal handle that encapsulates a user's ability to request that we
|
||||
* stop listening to a listener. When a user calls Remove(),
|
||||
* ListenerRegistration will synchronously mute the listener and then send a
|
||||
* request to the FirestoreClient to actually unlisten.
|
||||
*
|
||||
* ListenerRegistration will not automatically stop listening if it is
|
||||
* destroyed. We allow users to fire and forget listens if they never want to
|
||||
* stop them.
|
||||
*
|
||||
* Getting shutdown code right is tricky so ListenerRegistration is very
|
||||
* forgiving. It will tolerate:
|
||||
*
|
||||
* * Multiple calls to Remove(),
|
||||
* * calls to Remove() after we send an error,
|
||||
* * calls to Remove() even after deleting the App in which the listener was
|
||||
* started.
|
||||
*/
|
||||
class ListenerRegistration {
|
||||
public:
|
||||
virtual ~ListenerRegistration() = default;
|
||||
|
||||
/**
|
||||
* Removes the listener being tracked in this ListenerRegistration. After
|
||||
* the initial call, subsequent calls have no effect.
|
||||
*/
|
||||
virtual void Remove() = 0;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_API_LISTENER_REGISTRATION_H_
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2021 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 "Firestore/core/src/api/load_bundle_task.h"
|
||||
|
||||
#include <mutex> // NOLINT(build/c++11)
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/util/autoid.h"
|
||||
#include "Firestore/core/src/util/hard_assert.h"
|
||||
#include "absl/algorithm/container.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace api {
|
||||
|
||||
LoadBundleTask::~LoadBundleTask() {
|
||||
// NOTE: this is needed because users might call to modify some fields from
|
||||
// user callback thread. With this lock guard, we could be destroying the
|
||||
// instance while those calls are still in flight.
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
}
|
||||
|
||||
LoadBundleTask::LoadBundleHandle LoadBundleTask::Observe(
|
||||
ProgressObserver observer) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
auto handle = next_handle_++;
|
||||
observers_.push_back({handle, std::move(observer)});
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
LoadBundleTask::LoadBundleHandle LoadBundleTask::SetLastObserver(
|
||||
ProgressObserver observer) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
auto handle = next_handle_++;
|
||||
last_observer_ = {handle, std::move(observer)};
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
void LoadBundleTask::RemoveObserver(const LoadBundleHandle& handle) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
auto found = absl::c_find_if(
|
||||
observers_, [&](const HandleObservers::value_type& observer) {
|
||||
return observer.first == handle;
|
||||
});
|
||||
if (found != observers_.end()) {
|
||||
observers_.erase(found);
|
||||
}
|
||||
|
||||
if (last_observer_.has_value() && last_observer_.value().first == handle) {
|
||||
last_observer_ = absl::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
void LoadBundleTask::RemoveAllObservers() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
observers_.clear();
|
||||
last_observer_ = absl::nullopt;
|
||||
}
|
||||
|
||||
void LoadBundleTask::SetSuccess(LoadBundleTaskProgress success_progress) {
|
||||
HARD_ASSERT(success_progress.state() == LoadBundleTaskState::kSuccess,
|
||||
"Calling SetSuccess() with a state that is not 'Success'");
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
progress_snapshot_ = success_progress;
|
||||
NotifyObservers();
|
||||
}
|
||||
|
||||
void LoadBundleTask::SetError(const util::Status& status) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
progress_snapshot_.set_state(LoadBundleTaskState::kError);
|
||||
progress_snapshot_.set_error_status(status);
|
||||
|
||||
NotifyObservers();
|
||||
}
|
||||
|
||||
void LoadBundleTask::UpdateProgress(LoadBundleTaskProgress progress) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
progress_snapshot_ = progress;
|
||||
NotifyObservers();
|
||||
}
|
||||
|
||||
void LoadBundleTask::NotifyObservers() {
|
||||
for (const auto& entry : observers_) {
|
||||
const auto& observer = entry.second;
|
||||
const auto& progress = progress_snapshot_;
|
||||
user_executor_->Execute([observer, progress] { observer(progress); });
|
||||
}
|
||||
|
||||
if (last_observer_.has_value()) {
|
||||
const auto& observer = last_observer_.value().second;
|
||||
const auto& progress = progress_snapshot_;
|
||||
user_executor_->Execute([observer, progress] { observer(progress); });
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* Copyright 2021 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 FIRESTORE_CORE_SRC_API_LOAD_BUNDLE_TASK_H_
|
||||
#define FIRESTORE_CORE_SRC_API_LOAD_BUNDLE_TASK_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex> // NOLINT(build/c++11)
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Firestore/core/src/util/executor.h"
|
||||
#include "Firestore/core/src/util/status.h"
|
||||
#include "absl/types/optional.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace api {
|
||||
|
||||
/**
|
||||
* Represents the state of bundle loading tasks.
|
||||
*
|
||||
* Both `kSuccess` and `kError` are final states: task will abort
|
||||
* or complete and there will be no more updates after they are reported.
|
||||
*/
|
||||
enum class LoadBundleTaskState { kError, kInProgress, kSuccess };
|
||||
|
||||
/** Represents a progress update or a final state from loading bundles. */
|
||||
class LoadBundleTaskProgress {
|
||||
public:
|
||||
LoadBundleTaskProgress() = default;
|
||||
LoadBundleTaskProgress(uint32_t documents_loaded,
|
||||
uint32_t total_documents,
|
||||
uint64_t bytes_loaded,
|
||||
uint64_t total_bytes,
|
||||
LoadBundleTaskState state)
|
||||
: documents_loaded_(documents_loaded),
|
||||
total_documents_(total_documents),
|
||||
bytes_loaded_(bytes_loaded),
|
||||
total_bytes_(total_bytes),
|
||||
state_(state) {
|
||||
}
|
||||
|
||||
LoadBundleTaskProgress(uint32_t documents_loaded,
|
||||
uint32_t total_documents,
|
||||
uint64_t bytes_loaded,
|
||||
uint64_t total_bytes,
|
||||
LoadBundleTaskState state,
|
||||
const util::Status& error_status)
|
||||
: documents_loaded_(documents_loaded),
|
||||
total_documents_(total_documents),
|
||||
bytes_loaded_(bytes_loaded),
|
||||
total_bytes_(total_bytes),
|
||||
state_(state),
|
||||
error_status_(error_status) {
|
||||
}
|
||||
|
||||
/** Returns how many documents have been loaded. */
|
||||
uint32_t documents_loaded() const {
|
||||
return documents_loaded_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of documents in the bundle. Returns 0 if the
|
||||
* bundle failed to parse.
|
||||
*/
|
||||
uint32_t total_documents() const {
|
||||
return total_documents_;
|
||||
}
|
||||
|
||||
/** Returns how many bytes have been loaded. */
|
||||
uint64_t bytes_loaded() const {
|
||||
return bytes_loaded_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of bytes in the bundle. Returns 0 if the bundle
|
||||
* failed to parse.
|
||||
*/
|
||||
uint64_t total_bytes() const {
|
||||
return total_bytes_;
|
||||
}
|
||||
|
||||
/** Returns the current state of the task. */
|
||||
LoadBundleTaskState state() const {
|
||||
return state_;
|
||||
}
|
||||
|
||||
void set_state(LoadBundleTaskState state) {
|
||||
state_ = state;
|
||||
}
|
||||
|
||||
const util::Status& error_status() const {
|
||||
return error_status_;
|
||||
}
|
||||
|
||||
void set_error_status(const util::Status& error_status) {
|
||||
error_status_.Update(error_status);
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t documents_loaded_ = 0;
|
||||
uint32_t total_documents_ = 0;
|
||||
uint64_t bytes_loaded_ = 0;
|
||||
uint64_t total_bytes_ = 0;
|
||||
|
||||
LoadBundleTaskState state_ = LoadBundleTaskState::kInProgress;
|
||||
util::Status error_status_;
|
||||
};
|
||||
|
||||
inline bool operator==(const LoadBundleTaskProgress lhs,
|
||||
const LoadBundleTaskProgress& rhs) {
|
||||
return lhs.state() == rhs.state() &&
|
||||
lhs.bytes_loaded() == rhs.bytes_loaded() &&
|
||||
lhs.documents_loaded() == rhs.documents_loaded() &&
|
||||
lhs.total_bytes() == rhs.total_bytes() &&
|
||||
lhs.total_documents() == rhs.total_documents() &&
|
||||
lhs.error_status() == rhs.error_status();
|
||||
}
|
||||
|
||||
inline bool operator!=(const LoadBundleTaskProgress lhs,
|
||||
const LoadBundleTaskProgress& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the task of loading a Firestore bundle. It provides progress of
|
||||
* bundle loading, as well as task completion and error events.
|
||||
*/
|
||||
class LoadBundleTask {
|
||||
public:
|
||||
/** A handle used to look up and remove observer from the task. */
|
||||
using LoadBundleHandle = int64_t;
|
||||
|
||||
/** Observer type that is called by the task when there is an update. */
|
||||
using ProgressObserver = std::function<void(LoadBundleTaskProgress)>;
|
||||
|
||||
explicit LoadBundleTask(std::shared_ptr<util::Executor> user_executor)
|
||||
: user_executor_(std::move(user_executor)) {
|
||||
}
|
||||
|
||||
// This class cannot be copied or moved, because it holds a mutex.
|
||||
LoadBundleTask(const LoadBundleTask& other) = delete;
|
||||
LoadBundleTask& operator=(LoadBundleTask& other) = delete;
|
||||
|
||||
~LoadBundleTask();
|
||||
|
||||
/**
|
||||
* Instructs the task to notify the specified observer when there is a
|
||||
* progress update.
|
||||
*
|
||||
* @return A handle that can be used to remove the callback from this task.
|
||||
*/
|
||||
LoadBundleHandle Observe(ProgressObserver observer);
|
||||
|
||||
/**
|
||||
* Instructs the task to notify the specified observer when there is a
|
||||
* progress update.
|
||||
*
|
||||
* For a given progress update, this observer is guaranteed to be called
|
||||
* after all other observers. Calling `SetLastObserver` a second time will
|
||||
* override the observer registered the first time.
|
||||
*
|
||||
* @return A handle that can be used to remove the callback from this task.
|
||||
*/
|
||||
LoadBundleHandle SetLastObserver(ProgressObserver observer);
|
||||
|
||||
/**
|
||||
* Removes the observer associated with the given handle, does nothing if the
|
||||
* callback cannot be found.
|
||||
*/
|
||||
void RemoveObserver(const LoadBundleHandle& handle);
|
||||
|
||||
/** Removes all observers. */
|
||||
void RemoveAllObservers();
|
||||
|
||||
/**
|
||||
* Notifies observers with a `Success` progress.
|
||||
*/
|
||||
void SetSuccess(LoadBundleTaskProgress success_progress);
|
||||
|
||||
/**
|
||||
* Notifies observers with a error progress, by changing the last progress
|
||||
* this instance has been with an `Error` state.
|
||||
*/
|
||||
void SetError(const util::Status& status);
|
||||
|
||||
/** Notifies observers with a `InProgress` progress. */
|
||||
void UpdateProgress(LoadBundleTaskProgress progress);
|
||||
|
||||
private:
|
||||
/** Holds the `LoadBundleHandle` to `ProgressObserver` mapping. */
|
||||
using HandleObservers =
|
||||
std::vector<std::pair<LoadBundleHandle, ProgressObserver>>;
|
||||
|
||||
/** Notifies all observers with current `progress_snapshot_`. */
|
||||
void NotifyObservers();
|
||||
|
||||
LoadBundleHandle next_handle_ = 1;
|
||||
|
||||
/** The executor to run all observers when notified. */
|
||||
std::shared_ptr<util::Executor> user_executor_;
|
||||
|
||||
/** Guard to all internal state mutation. */
|
||||
mutable std::mutex mutex_;
|
||||
|
||||
/** A vector holds observers. */
|
||||
HandleObservers observers_;
|
||||
|
||||
/** Observer guaranteed to be called the last. */
|
||||
absl::optional<std::pair<LoadBundleHandle, ProgressObserver>> last_observer_;
|
||||
|
||||
/** The last progress update. */
|
||||
LoadBundleTaskProgress progress_snapshot_;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_API_LOAD_BUNDLE_TASK_H_
|
||||
@@ -0,0 +1,480 @@
|
||||
/*
|
||||
* Copyright 2019 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 "Firestore/core/src/api/query_core.h"
|
||||
|
||||
#include <future> // NOLINT(build/c++11)
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Firestore/core/src/api/firestore.h"
|
||||
#include "Firestore/core/src/api/query_listener_registration.h"
|
||||
#include "Firestore/core/src/api/query_snapshot.h"
|
||||
#include "Firestore/core/src/api/source.h"
|
||||
#include "Firestore/core/src/core/bound.h"
|
||||
#include "Firestore/core/src/core/field_filter.h"
|
||||
#include "Firestore/core/src/core/filter.h"
|
||||
#include "Firestore/core/src/core/firestore_client.h"
|
||||
#include "Firestore/core/src/core/listen_options.h"
|
||||
#include "Firestore/core/src/core/operator.h"
|
||||
#include "Firestore/core/src/model/resource_path.h"
|
||||
#include "Firestore/core/src/model/value_util.h"
|
||||
#include "Firestore/core/src/nanopb/nanopb_util.h"
|
||||
#include "Firestore/core/src/util/exception.h"
|
||||
#include "absl/algorithm/container.h"
|
||||
#include "absl/strings/match.h"
|
||||
#include "absl/types/span.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace api {
|
||||
|
||||
using core::AsyncEventListener;
|
||||
using core::Bound;
|
||||
using core::Direction;
|
||||
using core::EventListener;
|
||||
using core::FieldFilter;
|
||||
using core::Filter;
|
||||
using core::IsArrayOperator;
|
||||
using core::IsDisjunctiveOperator;
|
||||
using core::ListenOptions;
|
||||
using core::QueryListener;
|
||||
using core::ViewSnapshot;
|
||||
using model::DocumentKey;
|
||||
using model::FieldPath;
|
||||
using model::GetTypeOrder;
|
||||
using model::IsArray;
|
||||
using model::RefValue;
|
||||
using model::ResourcePath;
|
||||
using model::TypeOrder;
|
||||
using nanopb::MakeSharedMessage;
|
||||
using nanopb::Message;
|
||||
using util::Status;
|
||||
using util::StatusOr;
|
||||
using util::ThrowInvalidArgument;
|
||||
|
||||
using Operator = Filter::Operator;
|
||||
|
||||
namespace {
|
||||
/**
|
||||
* Given an operator, returns the set of operators that cannot be used with
|
||||
* it.
|
||||
*
|
||||
* Operators in a query must adhere to the following set of rules:
|
||||
* 1. Only one array operator is allowed.
|
||||
* 2. Only one disjunctive operator is allowed.
|
||||
* 3. NOT_EQUAL cannot be used with another NOT_EQUAL operator.
|
||||
* 4. NOT_IN cannot be used with array, disjunctive, or NOT_EQUAL operators.
|
||||
*
|
||||
* Array operators: ARRAY_CONTAINS, ARRAY_CONTAINS_ANY
|
||||
* Disjunctive operators: IN, ARRAY_CONTAINS_ANY, NOT_IN
|
||||
*/
|
||||
static std::vector<Operator> ConflictingOps(Operator op) {
|
||||
switch (op) {
|
||||
case Operator::NotEqual:
|
||||
return {Operator::NotEqual, Operator::NotIn};
|
||||
case Operator::ArrayContains:
|
||||
return {Operator::ArrayContains, Operator::ArrayContainsAny,
|
||||
Operator::NotIn};
|
||||
case Operator::In:
|
||||
return {Operator::ArrayContainsAny, Operator::In, Operator::NotIn};
|
||||
case Operator::ArrayContainsAny:
|
||||
return {Operator::ArrayContains, Operator::ArrayContainsAny, Operator::In,
|
||||
Operator::NotIn};
|
||||
case Operator::NotIn:
|
||||
return {Operator::ArrayContains, Operator::ArrayContainsAny, Operator::In,
|
||||
Operator::NotIn, Operator::NotEqual};
|
||||
default:
|
||||
return std::vector<Operator>();
|
||||
}
|
||||
}
|
||||
} // unnamed namespace
|
||||
|
||||
Query::Query(core::Query query, std::shared_ptr<Firestore> firestore)
|
||||
: firestore_{std::move(firestore)}, query_{std::move(query)} {
|
||||
}
|
||||
|
||||
bool operator==(const Query& lhs, const Query& rhs) {
|
||||
return lhs.firestore() == rhs.firestore() && lhs.query() == rhs.query();
|
||||
}
|
||||
|
||||
size_t Query::Hash() const {
|
||||
return util::Hash(firestore_.get(), query());
|
||||
}
|
||||
|
||||
void Query::GetDocuments(Source source, QuerySnapshotListener&& callback) {
|
||||
ValidateHasExplicitOrderByForLimitToLast();
|
||||
if (source == Source::Cache) {
|
||||
firestore_->client()->GetDocumentsFromLocalCache(*this,
|
||||
std::move(callback));
|
||||
return;
|
||||
}
|
||||
|
||||
ListenOptions options(
|
||||
/*include_query_metadata_changes=*/true,
|
||||
/*include_document_metadata_changes=*/true,
|
||||
/*wait_for_sync_when_online=*/true);
|
||||
|
||||
class ListenOnce : public EventListener<QuerySnapshot> {
|
||||
public:
|
||||
ListenOnce(Source source, QuerySnapshotListener&& listener)
|
||||
: source_(source), listener_(std::move(listener)) {
|
||||
}
|
||||
|
||||
void OnEvent(StatusOr<QuerySnapshot> maybe_snapshot) override {
|
||||
if (!maybe_snapshot.ok()) {
|
||||
listener_->OnEvent(std::move(maybe_snapshot));
|
||||
return;
|
||||
}
|
||||
|
||||
QuerySnapshot snapshot = std::move(maybe_snapshot).ValueOrDie();
|
||||
|
||||
// Remove query first before passing event to user to avoid user actions
|
||||
// affecting the now stale query.
|
||||
std::unique_ptr<ListenerRegistration> registration =
|
||||
registration_promise_.get_future().get();
|
||||
registration->Remove();
|
||||
|
||||
if (snapshot.metadata().from_cache() && source_ == Source::Server) {
|
||||
listener_->OnEvent(Status{
|
||||
Error::kErrorUnavailable,
|
||||
"Failed to get documents from server. (However, these documents "
|
||||
"may exist in the local cache. Run again without setting source to "
|
||||
"FirestoreSourceServer to retrieve the cached documents.)"});
|
||||
} else {
|
||||
listener_->OnEvent(std::move(snapshot));
|
||||
}
|
||||
};
|
||||
|
||||
void Resolve(std::unique_ptr<ListenerRegistration> registration) {
|
||||
registration_promise_.set_value(std::move(registration));
|
||||
}
|
||||
|
||||
private:
|
||||
Source source_;
|
||||
QuerySnapshotListener listener_;
|
||||
|
||||
std::promise<std::unique_ptr<ListenerRegistration>> registration_promise_;
|
||||
};
|
||||
|
||||
auto listener = absl::make_unique<ListenOnce>(source, std::move(callback));
|
||||
auto listener_unowned = listener.get();
|
||||
|
||||
std::unique_ptr<ListenerRegistration> registration =
|
||||
AddSnapshotListener(std::move(options), std::move(listener));
|
||||
|
||||
listener_unowned->Resolve(std::move(registration));
|
||||
}
|
||||
|
||||
std::unique_ptr<ListenerRegistration> Query::AddSnapshotListener(
|
||||
ListenOptions options, QuerySnapshotListener&& user_listener) {
|
||||
ValidateHasExplicitOrderByForLimitToLast();
|
||||
// Convert from ViewSnapshots to QuerySnapshots.
|
||||
class Converter : public EventListener<ViewSnapshot> {
|
||||
public:
|
||||
Converter(Query* parent, QuerySnapshotListener&& user_listener)
|
||||
: firestore_(parent->firestore()),
|
||||
query_(parent->query()),
|
||||
user_listener_(std::move(user_listener)) {
|
||||
}
|
||||
|
||||
void OnEvent(StatusOr<ViewSnapshot> maybe_snapshot) override {
|
||||
if (!maybe_snapshot.status().ok()) {
|
||||
user_listener_->OnEvent(maybe_snapshot.status());
|
||||
return;
|
||||
}
|
||||
|
||||
ViewSnapshot snapshot = std::move(maybe_snapshot).ValueOrDie();
|
||||
SnapshotMetadata metadata(snapshot.has_pending_writes(),
|
||||
snapshot.from_cache());
|
||||
|
||||
QuerySnapshot result(firestore_, query_, std::move(snapshot),
|
||||
std::move(metadata));
|
||||
|
||||
user_listener_->OnEvent(std::move(result));
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Firestore> firestore_;
|
||||
core::Query query_;
|
||||
QuerySnapshotListener user_listener_;
|
||||
};
|
||||
auto view_listener =
|
||||
absl::make_unique<Converter>(this, std::move(user_listener));
|
||||
|
||||
// Call the view_listener on the user Executor.
|
||||
auto async_listener = AsyncEventListener<ViewSnapshot>::Create(
|
||||
firestore_->client()->user_executor(), std::move(view_listener));
|
||||
|
||||
std::shared_ptr<QueryListener> query_listener =
|
||||
firestore_->client()->ListenToQuery(this->query(), options,
|
||||
async_listener);
|
||||
|
||||
return absl::make_unique<QueryListenerRegistration>(
|
||||
firestore_->client(), std::move(async_listener),
|
||||
std::move(query_listener));
|
||||
}
|
||||
|
||||
Query Query::Filter(const FieldPath& field_path,
|
||||
Operator op,
|
||||
nanopb::SharedMessage<google_firestore_v1_Value> value,
|
||||
const std::function<std::string()>& type_describer) const {
|
||||
if (field_path.IsKeyFieldPath()) {
|
||||
if (IsArrayOperator(op)) {
|
||||
ThrowInvalidArgument(
|
||||
"Invalid query. You can't perform %s queries on document "
|
||||
"ID since document IDs are not arrays.",
|
||||
Describe(op));
|
||||
} else if (op == Operator::In || op == Operator::NotIn) {
|
||||
ValidateDisjunctiveFilterElements(*value, op);
|
||||
// TODO(mutabledocuments): See if we can remove this copy and modify the
|
||||
// input values directly.
|
||||
auto references = MakeSharedMessage<google_firestore_v1_Value>({});
|
||||
references->which_value_type = google_firestore_v1_Value_array_value_tag;
|
||||
nanopb::SetRepeatedField(
|
||||
&references->array_value.values,
|
||||
&references->array_value.values_count,
|
||||
absl::Span<google_firestore_v1_Value>(
|
||||
value->array_value.values, value->array_value.values_count),
|
||||
[&](const google_firestore_v1_Value& value) {
|
||||
return *ParseExpectedReferenceValue(value, type_describer)
|
||||
.release();
|
||||
});
|
||||
value = std::move(references);
|
||||
} else {
|
||||
value = ParseExpectedReferenceValue(*value, type_describer);
|
||||
}
|
||||
} else {
|
||||
if (IsDisjunctiveOperator(op)) {
|
||||
ValidateDisjunctiveFilterElements(*value, op);
|
||||
}
|
||||
}
|
||||
|
||||
FieldFilter filter = FieldFilter::Create(field_path, op, std::move(value));
|
||||
ValidateNewFilter(filter);
|
||||
|
||||
return Wrap(query_.AddingFilter(std::move(filter)));
|
||||
}
|
||||
|
||||
Query Query::OrderBy(FieldPath field_path, bool descending) const {
|
||||
return OrderBy(std::move(field_path), Direction::FromDescending(descending));
|
||||
}
|
||||
|
||||
Query Query::OrderBy(FieldPath field_path, Direction direction) const {
|
||||
ValidateNewOrderByPath(field_path);
|
||||
if (query_.start_at()) {
|
||||
ThrowInvalidArgument(
|
||||
"Invalid query. You must not specify a starting point "
|
||||
"before specifying the order by.");
|
||||
}
|
||||
if (query_.end_at()) {
|
||||
ThrowInvalidArgument(
|
||||
"Invalid query. You must not specify an ending point "
|
||||
"before specifying the order by.");
|
||||
}
|
||||
return Wrap(
|
||||
query_.AddingOrderBy(core::OrderBy(std::move(field_path), direction)));
|
||||
}
|
||||
|
||||
Query Query::LimitToFirst(int32_t limit) const {
|
||||
if (limit <= 0) {
|
||||
ThrowInvalidArgument(
|
||||
"Invalid Query. Query limit (%s) is invalid. Limit must be positive.",
|
||||
limit);
|
||||
}
|
||||
return Wrap(query_.WithLimitToFirst(limit));
|
||||
}
|
||||
|
||||
Query Query::LimitToLast(int32_t limit) const {
|
||||
if (limit <= 0) {
|
||||
ThrowInvalidArgument(
|
||||
"Invalid Query. Query limit (%s) is invalid. Limit must be positive.",
|
||||
limit);
|
||||
}
|
||||
return Wrap(query_.WithLimitToLast(limit));
|
||||
}
|
||||
|
||||
Query Query::StartAt(Bound bound) const {
|
||||
return Wrap(query_.StartingAt(std::move(bound)));
|
||||
}
|
||||
|
||||
Query Query::EndAt(Bound bound) const {
|
||||
return Wrap(query_.EndingAt(std::move(bound)));
|
||||
}
|
||||
|
||||
void Query::ValidateNewFilter(const class Filter& filter) const {
|
||||
if (filter.IsAFieldFilter()) {
|
||||
FieldFilter field_filter(filter);
|
||||
|
||||
if (field_filter.IsInequality()) {
|
||||
const FieldPath* existing_inequality = query_.InequalityFilterField();
|
||||
const FieldPath* new_inequality = &filter.field();
|
||||
|
||||
if (existing_inequality && *existing_inequality != *new_inequality) {
|
||||
ThrowInvalidArgument(
|
||||
"Invalid Query. All where filters with an inequality (notEqual, "
|
||||
"lessThan, lessThanOrEqual, greaterThan, or greaterThanOrEqual) "
|
||||
"must be on the same field. But you have inequality filters on "
|
||||
"'%s' and '%s'",
|
||||
existing_inequality->CanonicalString(),
|
||||
new_inequality->CanonicalString());
|
||||
}
|
||||
|
||||
const FieldPath* first_order_by_field = query_.FirstOrderByField();
|
||||
if (first_order_by_field) {
|
||||
ValidateOrderByField(*first_order_by_field, filter.field());
|
||||
}
|
||||
}
|
||||
Operator filter_op = field_filter.op();
|
||||
absl::optional<Operator> conflicting_op =
|
||||
query_.FindOperator(ConflictingOps(filter_op));
|
||||
|
||||
if (conflicting_op) {
|
||||
// We special case when it's a duplicate op to give a slightly clearer
|
||||
// error message.
|
||||
if (*conflicting_op == filter_op) {
|
||||
ThrowInvalidArgument(
|
||||
"Invalid Query. You cannot use more than one '%s' filter.",
|
||||
Describe(filter_op));
|
||||
} else {
|
||||
ThrowInvalidArgument(
|
||||
"Invalid Query. You cannot use '%s' filters with"
|
||||
" '%s' filters.",
|
||||
Describe(filter_op), Describe(conflicting_op.value()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Query::ValidateNewOrderByPath(const FieldPath& field_path) const {
|
||||
if (!query_.FirstOrderByField()) {
|
||||
// This is the first order by. It must match any inequality.
|
||||
const FieldPath* inequality_field = query_.InequalityFilterField();
|
||||
if (inequality_field) {
|
||||
ValidateOrderByField(field_path, *inequality_field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Query::ValidateOrderByField(const FieldPath& order_by_field,
|
||||
const FieldPath& inequality_field) const {
|
||||
if (order_by_field != inequality_field) {
|
||||
ThrowInvalidArgument(
|
||||
"Invalid query. You have a where filter with an inequality "
|
||||
"(notEqual, lessThan, lessThanOrEqual, greaterThan, or "
|
||||
"greaterThanOrEqual) on field '%s' and so you must also use '%s' as "
|
||||
"your first queryOrderedBy field, but your first queryOrderedBy is "
|
||||
"currently on field '%s' instead.",
|
||||
inequality_field.CanonicalString(), inequality_field.CanonicalString(),
|
||||
order_by_field.CanonicalString());
|
||||
}
|
||||
}
|
||||
|
||||
void Query::ValidateHasExplicitOrderByForLimitToLast() const {
|
||||
if (query_.has_limit_to_last() && query_.explicit_order_bys().empty()) {
|
||||
ThrowInvalidArgument(
|
||||
"limit(toLast:) queries require specifying at least one OrderBy() "
|
||||
"clause.");
|
||||
}
|
||||
}
|
||||
|
||||
void Query::ValidateDisjunctiveFilterElements(
|
||||
const google_firestore_v1_Value& value, Operator op) const {
|
||||
HARD_ASSERT(
|
||||
IsArray(value),
|
||||
"A FieldValue of Array type is required for disjunctive filters.");
|
||||
if (value.array_value.values_count == 0) {
|
||||
ThrowInvalidArgument(
|
||||
"Invalid Query. A non-empty array is required for '%s'"
|
||||
" filters.",
|
||||
Describe(op));
|
||||
}
|
||||
if (value.array_value.values_count > 10) {
|
||||
ThrowInvalidArgument(
|
||||
"Invalid Query. '%s' filters support a maximum of 10"
|
||||
" elements in the value array.",
|
||||
Describe(op));
|
||||
}
|
||||
}
|
||||
|
||||
Message<google_firestore_v1_Value> Query::ParseExpectedReferenceValue(
|
||||
const google_firestore_v1_Value& value,
|
||||
const std::function<std::string()>& type_describer) const {
|
||||
if (GetTypeOrder(value) == TypeOrder::kString) {
|
||||
std::string document_key = nanopb::MakeString(value.string_value);
|
||||
if (document_key.empty()) {
|
||||
ThrowInvalidArgument(
|
||||
"Invalid query. When querying by document ID you must provide a "
|
||||
"valid document ID, but it was an empty string.");
|
||||
}
|
||||
if (!query().IsCollectionGroupQuery() &&
|
||||
absl::StrContains(document_key, "/")) {
|
||||
ThrowInvalidArgument(
|
||||
"Invalid query. When querying a collection by document ID you must "
|
||||
"provide a plain document ID, but '%s' contains a '/' character.",
|
||||
document_key);
|
||||
}
|
||||
ResourcePath path =
|
||||
query().path().Append(ResourcePath::FromString(document_key));
|
||||
if (!DocumentKey::IsDocumentKey(path)) {
|
||||
ThrowInvalidArgument(
|
||||
"Invalid query. When querying a collection group by document ID, "
|
||||
"the value provided must result in a valid document path, but '%s' "
|
||||
"is not because it has an odd number of segments.",
|
||||
path.CanonicalString());
|
||||
}
|
||||
return RefValue(firestore_->database_id(), DocumentKey{path});
|
||||
} else if (GetTypeOrder(value) == TypeOrder::kReference) {
|
||||
return model::DeepClone(value);
|
||||
} else {
|
||||
ThrowInvalidArgument(
|
||||
"Invalid query. When querying by document ID you must provide a "
|
||||
"valid string or DocumentReference, but it was of type: %s",
|
||||
type_describer());
|
||||
}
|
||||
}
|
||||
|
||||
std::string Query::Describe(Operator op) const {
|
||||
switch (op) {
|
||||
case Operator::LessThan:
|
||||
return "lessThan";
|
||||
case Operator::LessThanOrEqual:
|
||||
return "lessThanOrEqual";
|
||||
case Operator::Equal:
|
||||
return "equal";
|
||||
case Operator::NotEqual:
|
||||
return "notEqual";
|
||||
case Operator::GreaterThanOrEqual:
|
||||
return "greaterThanOrEqual";
|
||||
case Operator::GreaterThan:
|
||||
return "greaterThan";
|
||||
case Operator::ArrayContains:
|
||||
return "arrayContains";
|
||||
case Operator::In:
|
||||
return "in";
|
||||
case Operator::ArrayContainsAny:
|
||||
return "arrayContainsAny";
|
||||
case Operator::NotIn:
|
||||
return "notIn";
|
||||
}
|
||||
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Copyright 2019 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 FIRESTORE_CORE_SRC_API_QUERY_CORE_H_
|
||||
#define FIRESTORE_CORE_SRC_API_QUERY_CORE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/api/api_fwd.h"
|
||||
#include "Firestore/core/src/core/core_fwd.h"
|
||||
#include "Firestore/core/src/core/filter.h"
|
||||
#include "Firestore/core/src/core/query.h"
|
||||
#include "Firestore/core/src/nanopb/message.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
|
||||
namespace model {
|
||||
class FieldValue;
|
||||
} // namespace model
|
||||
|
||||
namespace api {
|
||||
|
||||
/**
|
||||
* A `Query` refers to a Firestore Query which you can read or listen to. You
|
||||
* can also construct refined `Query` objects by adding filters and ordering.
|
||||
*/
|
||||
class Query {
|
||||
public:
|
||||
Query() = default;
|
||||
|
||||
Query(core::Query query, std::shared_ptr<Firestore> firestore);
|
||||
|
||||
size_t Hash() const;
|
||||
|
||||
const std::shared_ptr<Firestore>& firestore() const {
|
||||
return firestore_;
|
||||
}
|
||||
|
||||
const core::Query& query() const {
|
||||
return query_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the documents matching this query.
|
||||
*
|
||||
* @param source indicates whether the results should be fetched from the
|
||||
* cache only (`Source::Cache`), the server only (`Source::Server`), or to
|
||||
* attempt the server and fall back to the cache (`Source::Default`).
|
||||
* @param callback a callback to execute once the documents have been
|
||||
* successfully read.
|
||||
*/
|
||||
void GetDocuments(Source source, QuerySnapshotListener&& callback);
|
||||
|
||||
/**
|
||||
* Attaches a listener for QuerySnapshot events.
|
||||
*
|
||||
* @param options Whether metadata-only changes (i.e. only
|
||||
* `DocumentSnapshot::metadata()` changed) should trigger snapshot events.
|
||||
* @param listener The listener to attach.
|
||||
*
|
||||
* @return A ListenerRegistration that can be used to remove this listener.
|
||||
*/
|
||||
std::unique_ptr<ListenerRegistration> AddSnapshotListener(
|
||||
core::ListenOptions options, QuerySnapshotListener&& listener);
|
||||
|
||||
/**
|
||||
* Creates and returns a new `Query` with the additional filter that documents
|
||||
* must contain the specified field and the value must be equal to the
|
||||
* specified value.
|
||||
*
|
||||
* @param field_path The name of the field to compare.
|
||||
* @param op The operator to apply.
|
||||
* @param value The value against which to compare the field.
|
||||
* @param type_describer A function that will produce a description of the
|
||||
* type of field_value.
|
||||
*
|
||||
* @return The created `Query`.
|
||||
*/
|
||||
Query Filter(const model::FieldPath& field_path,
|
||||
core::Filter::Operator op,
|
||||
nanopb::SharedMessage<google_firestore_v1_Value> value,
|
||||
const std::function<std::string()>& type_describer) const;
|
||||
|
||||
/**
|
||||
* Creates and returns a new `Query` that's additionally sorted by the
|
||||
* specified field.
|
||||
*
|
||||
* @param field_path The field to sort by.
|
||||
* @param descending If true, sorts descending instead of ascending.
|
||||
*
|
||||
* @return The created `Query`.
|
||||
*/
|
||||
Query OrderBy(model::FieldPath field_path, bool descending) const;
|
||||
|
||||
/**
|
||||
* Creates and returns a new `Query` that's additionally sorted by the
|
||||
* specified field.
|
||||
*
|
||||
* @param field_path The field to sort by.
|
||||
* @param direction The direction in which to sort.
|
||||
*
|
||||
* @return The created `Query`.
|
||||
*/
|
||||
Query OrderBy(model::FieldPath field_path, core::Direction direction) const;
|
||||
|
||||
/**
|
||||
* Creates and returns a new `Query` that only returns the first matching
|
||||
* documents up to the specified number.
|
||||
*
|
||||
* @param limit The maximum number of items to return.
|
||||
*
|
||||
* @return The created `Query`.
|
||||
*/
|
||||
Query LimitToFirst(int32_t limit) const;
|
||||
|
||||
/**
|
||||
* Creates and returns a new `Query` that only returns the last matching
|
||||
* documents up to the specified number.
|
||||
*
|
||||
* You must specify at least one `OrderBy` clause for `LimitToLast` queries,
|
||||
* it is an error otherwise when the query is executed.
|
||||
*
|
||||
* @param limit The maximum number of items to return.
|
||||
*
|
||||
* @return The created `Query`.
|
||||
*/
|
||||
Query LimitToLast(int32_t limit) const;
|
||||
|
||||
/**
|
||||
* Creates and returns a new `Query` that starts at the given bound. The
|
||||
* starting position is relative to the order of the query. The bound must
|
||||
* contain all of the fields provided in the orderBy of this query.
|
||||
*
|
||||
* @param bound The bound of the query to start at.
|
||||
*
|
||||
* @return The created `Query`.
|
||||
*/
|
||||
Query StartAt(core::Bound bound) const;
|
||||
|
||||
/**
|
||||
* Creates and returns a new `Query` that ends at the given bound. The ending
|
||||
* position is relative to the order of the query. The bound must contain all
|
||||
* of the fields provided in the orderBy of this query.
|
||||
*
|
||||
* @param bound The bound of the query to end at.
|
||||
*
|
||||
* @return The created `Query`.
|
||||
*/
|
||||
Query EndAt(core::Bound bound) const;
|
||||
|
||||
/**
|
||||
* Creates a new `Query` with the given internal query.
|
||||
*/
|
||||
Query Wrap(core::Query chained_query) const {
|
||||
return Query(std::move(chained_query), firestore_);
|
||||
}
|
||||
|
||||
private:
|
||||
void ValidateNewFilter(const core::Filter& filter) const;
|
||||
void ValidateNewOrderByPath(const model::FieldPath& field_path) const;
|
||||
void ValidateOrderByField(const model::FieldPath& order_by_field,
|
||||
const model::FieldPath& inequality_field) const;
|
||||
void ValidateHasExplicitOrderByForLimitToLast() const;
|
||||
/**
|
||||
* Validates that the value passed into a disjunctive filter satisfies all
|
||||
* array requirements.
|
||||
*/
|
||||
void ValidateDisjunctiveFilterElements(const google_firestore_v1_Value& value,
|
||||
core::Filter::Operator op) const;
|
||||
|
||||
/**
|
||||
* Parses the given FieldValue into a Reference, throwing appropriate errors
|
||||
* if the value is anything other than a Reference or String, or if the string
|
||||
* is malformed.
|
||||
*/
|
||||
nanopb::Message<google_firestore_v1_Value> ParseExpectedReferenceValue(
|
||||
const google_firestore_v1_Value& value,
|
||||
const std::function<std::string()>& type_describer) const;
|
||||
|
||||
std::string Describe(core::Filter::Operator op) const;
|
||||
|
||||
std::shared_ptr<Firestore> firestore_;
|
||||
core::Query query_;
|
||||
};
|
||||
|
||||
bool operator==(const Query& lhs, const Query& rhs);
|
||||
|
||||
inline bool operator!=(const Query& lhs, const Query& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_API_QUERY_CORE_H_
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/api/query_listener_registration.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/core/event_listener.h"
|
||||
#include "Firestore/core/src/core/firestore_client.h"
|
||||
#include "Firestore/core/src/core/query_listener.h"
|
||||
#include "Firestore/core/src/core/view_snapshot.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace api {
|
||||
|
||||
QueryListenerRegistration::QueryListenerRegistration(
|
||||
std::shared_ptr<core::FirestoreClient> client,
|
||||
std::shared_ptr<core::AsyncEventListener<core::ViewSnapshot>>
|
||||
async_listener,
|
||||
std::shared_ptr<core::QueryListener> query_listener)
|
||||
: client_(std::move(client)),
|
||||
async_listener_(std::move(async_listener)),
|
||||
query_listener_(std::move(query_listener)) {
|
||||
}
|
||||
|
||||
void QueryListenerRegistration::Remove() {
|
||||
auto async_listener = async_listener_.lock();
|
||||
if (async_listener) {
|
||||
async_listener->Mute();
|
||||
async_listener_.reset();
|
||||
}
|
||||
|
||||
auto query_listener = query_listener_.lock();
|
||||
if (query_listener) {
|
||||
client_->RemoveListener(query_listener);
|
||||
query_listener_.reset();
|
||||
}
|
||||
|
||||
client_.reset();
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_API_QUERY_LISTENER_REGISTRATION_H_
|
||||
#define FIRESTORE_CORE_SRC_API_QUERY_LISTENER_REGISTRATION_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "Firestore/core/src/api/listener_registration.h"
|
||||
#include "Firestore/core/src/core/core_fwd.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace api {
|
||||
|
||||
/**
|
||||
* An internal handle that encapsulates a user's ability to request that we
|
||||
* stop listening to a query.
|
||||
*/
|
||||
class QueryListenerRegistration : public ListenerRegistration {
|
||||
public:
|
||||
QueryListenerRegistration(
|
||||
std::shared_ptr<core::FirestoreClient> client,
|
||||
std::shared_ptr<core::AsyncEventListener<core::ViewSnapshot>>
|
||||
async_listener,
|
||||
std::shared_ptr<core::QueryListener> query_listener);
|
||||
|
||||
/**
|
||||
* Removes the listener being tracked by this QueryListenerRegistration.
|
||||
*/
|
||||
void Remove() override;
|
||||
|
||||
private:
|
||||
/** The client that was used to register this listen. */
|
||||
std::shared_ptr<core::FirestoreClient> client_;
|
||||
|
||||
/** The async listener that is used to mute events synchronously. */
|
||||
std::weak_ptr<core::AsyncEventListener<core::ViewSnapshot>> async_listener_;
|
||||
|
||||
/** The internal QueryListener that can be used to unlisten the query. */
|
||||
std::weak_ptr<core::QueryListener> query_listener_;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_API_QUERY_LISTENER_REGISTRATION_H_
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/api/query_snapshot.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/api/document_change.h"
|
||||
#include "Firestore/core/src/api/document_snapshot.h"
|
||||
#include "Firestore/core/src/api/query_core.h"
|
||||
#include "Firestore/core/src/core/view_snapshot.h"
|
||||
#include "Firestore/core/src/model/document_set.h"
|
||||
#include "Firestore/core/src/util/exception.h"
|
||||
#include "Firestore/core/src/util/hard_assert.h"
|
||||
#include "absl/types/optional.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace api {
|
||||
|
||||
using api::Firestore;
|
||||
using core::DocumentViewChange;
|
||||
using core::ViewSnapshot;
|
||||
using model::Document;
|
||||
using model::DocumentComparator;
|
||||
using model::DocumentSet;
|
||||
using util::ThrowInvalidArgument;
|
||||
|
||||
QuerySnapshot::QuerySnapshot(std::shared_ptr<Firestore> firestore,
|
||||
core::Query query,
|
||||
core::ViewSnapshot&& snapshot,
|
||||
SnapshotMetadata metadata)
|
||||
: firestore_(std::move(firestore)),
|
||||
internal_query_(std::move(query)),
|
||||
snapshot_(std::move(snapshot)),
|
||||
metadata_(std::move(metadata)) {
|
||||
}
|
||||
|
||||
Query QuerySnapshot::query() const {
|
||||
return Query(internal_query_, firestore_);
|
||||
}
|
||||
|
||||
const core::Query& QuerySnapshot::internal_query() const {
|
||||
return internal_query_;
|
||||
}
|
||||
|
||||
bool operator==(const QuerySnapshot& lhs, const QuerySnapshot& rhs) {
|
||||
return lhs.firestore_ == rhs.firestore_ &&
|
||||
lhs.internal_query_ == rhs.internal_query_ &&
|
||||
lhs.snapshot_ == rhs.snapshot_ && lhs.metadata_ == rhs.metadata_;
|
||||
}
|
||||
|
||||
size_t QuerySnapshot::Hash() const {
|
||||
return util::Hash(firestore_.get(), internal_query_, snapshot_, metadata_);
|
||||
}
|
||||
|
||||
void QuerySnapshot::ForEachDocument(
|
||||
const std::function<void(DocumentSnapshot)>& callback) const {
|
||||
DocumentSet document_set = snapshot_.documents();
|
||||
bool from_cache = metadata_.from_cache();
|
||||
|
||||
for (const Document& document : document_set) {
|
||||
bool has_pending_writes =
|
||||
snapshot_.mutated_keys().contains(document->key());
|
||||
auto snap = DocumentSnapshot::FromDocument(
|
||||
firestore_, document, SnapshotMetadata(has_pending_writes, from_cache));
|
||||
callback(std::move(snap));
|
||||
}
|
||||
}
|
||||
|
||||
static DocumentChange::Type DocumentChangeTypeForChange(
|
||||
const DocumentViewChange& change) {
|
||||
switch (change.type()) {
|
||||
case DocumentViewChange::Type::Added:
|
||||
return DocumentChange::Type::Added;
|
||||
case DocumentViewChange::Type::Modified:
|
||||
case DocumentViewChange::Type::Metadata:
|
||||
return DocumentChange::Type::Modified;
|
||||
case DocumentViewChange::Type::Removed:
|
||||
return DocumentChange::Type::Removed;
|
||||
}
|
||||
|
||||
HARD_FAIL("Unknown DocumentViewChange::Type: %s", change.type());
|
||||
}
|
||||
|
||||
void QuerySnapshot::ForEachChange(
|
||||
bool include_metadata_changes,
|
||||
const std::function<void(DocumentChange)>& callback) const {
|
||||
if (include_metadata_changes && snapshot_.excludes_metadata_changes()) {
|
||||
ThrowInvalidArgument(
|
||||
"To include metadata changes with your document "
|
||||
"changes, you must call "
|
||||
"addSnapshotListener(includeMetadataChanges:true).");
|
||||
}
|
||||
|
||||
if (snapshot_.old_documents().empty()) {
|
||||
// Special case the first snapshot because index calculation is easy and
|
||||
// fast. Also all changes on the first snapshot are adds so there are also
|
||||
// no metadata-only changes to filter out.
|
||||
DocumentComparator doc_comparator = snapshot_.query().Comparator();
|
||||
absl::optional<Document> last_document;
|
||||
size_t index = 0;
|
||||
for (const DocumentViewChange& change : snapshot_.document_changes()) {
|
||||
const Document& doc = change.document();
|
||||
SnapshotMetadata metadata(
|
||||
/*pending_writes=*/snapshot_.mutated_keys().contains(doc->key()),
|
||||
/*from_cache=*/snapshot_.from_cache());
|
||||
auto document =
|
||||
DocumentSnapshot::FromDocument(firestore_, doc, std::move(metadata));
|
||||
|
||||
HARD_ASSERT(change.type() == DocumentViewChange::Type::Added,
|
||||
"Invalid event type for first snapshot");
|
||||
HARD_ASSERT(!last_document || util::Ascending(doc_comparator.Compare(
|
||||
*last_document, change.document())),
|
||||
"Got added events in wrong order");
|
||||
|
||||
callback(DocumentChange(DocumentChange::Type::Added, std::move(document),
|
||||
DocumentChange::npos, index++));
|
||||
last_document = doc;
|
||||
}
|
||||
|
||||
} else {
|
||||
// A DocumentSet that is updated incrementally as changes are applied to use
|
||||
// to lookup the index of a document.
|
||||
DocumentSet index_tracker = snapshot_.old_documents();
|
||||
for (const DocumentViewChange& change : snapshot_.document_changes()) {
|
||||
if (!include_metadata_changes &&
|
||||
change.type() == DocumentViewChange::Type::Metadata) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Document& doc = change.document();
|
||||
SnapshotMetadata metadata(
|
||||
/*pending_writes=*/snapshot_.mutated_keys().contains(doc->key()),
|
||||
/*from_cache=*/snapshot_.from_cache());
|
||||
auto document = DocumentSnapshot::FromDocument(firestore_, doc, metadata);
|
||||
|
||||
size_t old_index = DocumentChange::npos;
|
||||
size_t new_index = DocumentChange::npos;
|
||||
if (change.type() != DocumentViewChange::Type::Added) {
|
||||
old_index = index_tracker.IndexOf(change.document()->key());
|
||||
HARD_ASSERT(old_index != DocumentSet::npos,
|
||||
"Index for document not found");
|
||||
index_tracker = index_tracker.erase(change.document()->key());
|
||||
}
|
||||
if (change.type() != DocumentViewChange::Type::Removed) {
|
||||
index_tracker = index_tracker.insert(change.document());
|
||||
new_index = index_tracker.IndexOf(change.document()->key());
|
||||
}
|
||||
|
||||
DocumentChange::Type type = DocumentChangeTypeForChange(change);
|
||||
callback(DocumentChange(type, std::move(document), old_index, new_index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2019 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 FIRESTORE_CORE_SRC_API_QUERY_SNAPSHOT_H_
|
||||
#define FIRESTORE_CORE_SRC_API_QUERY_SNAPSHOT_H_
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/api/api_fwd.h"
|
||||
#include "Firestore/core/src/api/snapshot_metadata.h"
|
||||
#include "Firestore/core/src/core/event_listener.h"
|
||||
#include "Firestore/core/src/core/query.h"
|
||||
#include "Firestore/core/src/core/view_snapshot.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace api {
|
||||
|
||||
/**
|
||||
* A `QuerySnapshot` contains zero or more `DocumentSnapshot` objects.
|
||||
*/
|
||||
class QuerySnapshot {
|
||||
public:
|
||||
QuerySnapshot(std::shared_ptr<Firestore> firestore,
|
||||
core::Query query,
|
||||
core::ViewSnapshot&& snapshot,
|
||||
SnapshotMetadata metadata);
|
||||
|
||||
size_t Hash() const;
|
||||
|
||||
/**
|
||||
* Indicates whether this `QuerySnapshot` is empty (contains no documents).
|
||||
*/
|
||||
bool empty() const {
|
||||
return snapshot_.documents().empty();
|
||||
}
|
||||
|
||||
/** The count of documents in this `QuerySnapshot`. */
|
||||
size_t size() const {
|
||||
return snapshot_.documents().size();
|
||||
}
|
||||
|
||||
const std::shared_ptr<Firestore>& firestore() const {
|
||||
return firestore_;
|
||||
}
|
||||
|
||||
Query query() const;
|
||||
|
||||
const core::Query& internal_query() const;
|
||||
|
||||
/**
|
||||
* Metadata about this snapshot, concerning its source and if it has local
|
||||
* modifications.
|
||||
*/
|
||||
const SnapshotMetadata& metadata() const {
|
||||
return metadata_;
|
||||
}
|
||||
|
||||
/** Iterates over the `DocumentSnapshots` that make up this query snapshot. */
|
||||
void ForEachDocument(
|
||||
const std::function<void(DocumentSnapshot)>& callback) const;
|
||||
|
||||
/**
|
||||
* Iterates over the `DocumentChanges` representing the changes between
|
||||
* the prior snapshot and this one.
|
||||
*/
|
||||
void ForEachChange(bool include_metadata_changes,
|
||||
const std::function<void(DocumentChange)>& callback) const;
|
||||
|
||||
friend bool operator==(const QuerySnapshot& lhs, const QuerySnapshot& rhs);
|
||||
|
||||
private:
|
||||
std::shared_ptr<Firestore> firestore_;
|
||||
core::Query internal_query_;
|
||||
core::ViewSnapshot snapshot_;
|
||||
SnapshotMetadata metadata_;
|
||||
};
|
||||
|
||||
using QuerySnapshotListener =
|
||||
std::unique_ptr<core::EventListener<QuerySnapshot>>;
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_API_QUERY_SNAPSHOT_H_
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2019 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 "Firestore/core/src/api/settings.h"
|
||||
|
||||
#include "Firestore/core/src/util/hashing.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace api {
|
||||
|
||||
constexpr const char* Settings::DefaultHost;
|
||||
constexpr bool Settings::DefaultSslEnabled;
|
||||
constexpr bool Settings::DefaultPersistenceEnabled;
|
||||
constexpr int64_t Settings::DefaultCacheSizeBytes;
|
||||
constexpr int64_t Settings::MinimumCacheSizeBytes;
|
||||
|
||||
size_t Settings::Hash() const {
|
||||
return util::Hash(host_, ssl_enabled_, persistence_enabled_,
|
||||
cache_size_bytes_);
|
||||
}
|
||||
|
||||
bool operator==(const Settings& lhs, const Settings& rhs) {
|
||||
return lhs.host_ == rhs.host_ && lhs.ssl_enabled_ == rhs.ssl_enabled_ &&
|
||||
lhs.persistence_enabled_ == rhs.persistence_enabled_ &&
|
||||
lhs.cache_size_bytes_ == rhs.cache_size_bytes_;
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,92 @@
|
||||
|
||||
/*
|
||||
* Copyright 2019 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 FIRESTORE_CORE_SRC_API_SETTINGS_H_
|
||||
#define FIRESTORE_CORE_SRC_API_SETTINGS_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace api {
|
||||
|
||||
/**
|
||||
* Represents settings associated with a FirestoreClient.
|
||||
*
|
||||
* PORTING NOTE: We exclude the user callback std::executor in order to avoid
|
||||
* ownership complexity.
|
||||
*/
|
||||
class Settings {
|
||||
public:
|
||||
// Note: a constexpr array of char (`char[]`) doesn't work with Visual Studio
|
||||
// 2015.
|
||||
static constexpr const char* DefaultHost = "firestore.googleapis.com";
|
||||
static constexpr bool DefaultSslEnabled = true;
|
||||
static constexpr bool DefaultPersistenceEnabled = true;
|
||||
static constexpr int64_t DefaultCacheSizeBytes = 100 * 1024 * 1024;
|
||||
static constexpr int64_t MinimumCacheSizeBytes = 1 * 1024 * 1024;
|
||||
static constexpr int64_t CacheSizeUnlimited = -1;
|
||||
|
||||
Settings() = default;
|
||||
|
||||
void set_host(const std::string& value) {
|
||||
host_ = value;
|
||||
}
|
||||
const std::string& host() const {
|
||||
return host_;
|
||||
}
|
||||
|
||||
void set_ssl_enabled(bool value) {
|
||||
ssl_enabled_ = value;
|
||||
}
|
||||
bool ssl_enabled() const {
|
||||
return ssl_enabled_;
|
||||
}
|
||||
|
||||
void set_persistence_enabled(bool value) {
|
||||
persistence_enabled_ = value;
|
||||
}
|
||||
bool persistence_enabled() const {
|
||||
return persistence_enabled_;
|
||||
}
|
||||
|
||||
void set_cache_size_bytes(int64_t value) {
|
||||
cache_size_bytes_ = value;
|
||||
}
|
||||
int64_t cache_size_bytes() const {
|
||||
return cache_size_bytes_;
|
||||
}
|
||||
bool gc_enabled() const {
|
||||
return cache_size_bytes_ != CacheSizeUnlimited;
|
||||
}
|
||||
|
||||
friend bool operator==(const Settings& lhs, const Settings& rhs);
|
||||
|
||||
size_t Hash() const;
|
||||
|
||||
private:
|
||||
std::string host_ = DefaultHost;
|
||||
bool ssl_enabled_ = DefaultSslEnabled;
|
||||
bool persistence_enabled_ = DefaultPersistenceEnabled;
|
||||
int64_t cache_size_bytes_ = DefaultCacheSizeBytes;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_API_SETTINGS_H_
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/api/snapshot_metadata.h"
|
||||
|
||||
#include "Firestore/core/src/util/hashing.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace api {
|
||||
|
||||
bool operator==(const SnapshotMetadata& lhs, const SnapshotMetadata& rhs) {
|
||||
return lhs.pending_writes_ == rhs.pending_writes_ &&
|
||||
lhs.from_cache_ == rhs.from_cache_;
|
||||
}
|
||||
|
||||
size_t SnapshotMetadata::Hash() const {
|
||||
return util::Hash(pending_writes_, from_cache_);
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_API_SNAPSHOT_METADATA_H_
|
||||
#define FIRESTORE_CORE_SRC_API_SNAPSHOT_METADATA_H_
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace api {
|
||||
|
||||
/** Metadata about a snapshot, describing the state of the snapshot. */
|
||||
class SnapshotMetadata {
|
||||
public:
|
||||
SnapshotMetadata() = default;
|
||||
SnapshotMetadata(bool pending_writes, bool from_cache)
|
||||
: pending_writes_(pending_writes), from_cache_(from_cache) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the snapshot contains the result of local writes (e.g.
|
||||
* set() or update() calls) that have not yet been committed to the backend.
|
||||
*/
|
||||
bool pending_writes() const {
|
||||
return pending_writes_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the snapshot was created from cached data rather than
|
||||
* guaranteed up-to-date server data.
|
||||
*/
|
||||
bool from_cache() const {
|
||||
return from_cache_;
|
||||
}
|
||||
|
||||
friend bool operator==(const SnapshotMetadata& lhs,
|
||||
const SnapshotMetadata& rhs);
|
||||
|
||||
size_t Hash() const;
|
||||
|
||||
private:
|
||||
bool pending_writes_ = false;
|
||||
bool from_cache_ = false;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_API_SNAPSHOT_METADATA_H_
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/api/snapshots_in_sync_listener_registration.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/api/query_snapshot.h"
|
||||
#include "Firestore/core/src/core/firestore_client.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace api {
|
||||
|
||||
SnapshotsInSyncListenerRegistration::SnapshotsInSyncListenerRegistration(
|
||||
std::shared_ptr<core::FirestoreClient> client,
|
||||
std::shared_ptr<core::AsyncEventListener<util::Empty>> async_listener)
|
||||
: client_(std::move(client)), async_listener_(std::move(async_listener)) {
|
||||
}
|
||||
|
||||
void SnapshotsInSyncListenerRegistration::Remove() {
|
||||
auto async_listener = async_listener_.lock();
|
||||
if (async_listener) {
|
||||
async_listener->Mute();
|
||||
async_listener_.reset();
|
||||
|
||||
if (client_) {
|
||||
client_->RemoveSnapshotsInSyncListener(async_listener);
|
||||
client_.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_API_SNAPSHOTS_IN_SYNC_LISTENER_REGISTRATION_H_
|
||||
#define FIRESTORE_CORE_SRC_API_SNAPSHOTS_IN_SYNC_LISTENER_REGISTRATION_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "Firestore/core/src/api/listener_registration.h"
|
||||
#include "Firestore/core/src/core/core_fwd.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace api {
|
||||
|
||||
/**
|
||||
* An internal handle that encapsulates a user's ability to request that we
|
||||
* stop listening to the snapshots-in-sync listener. When a user calls Remove(),
|
||||
* SnapshotsInSyncListenerRegistration will synchronously mute the listener and
|
||||
* then send a request to actually unlisten.
|
||||
*/
|
||||
class SnapshotsInSyncListenerRegistration : public ListenerRegistration {
|
||||
public:
|
||||
SnapshotsInSyncListenerRegistration(
|
||||
std::shared_ptr<core::FirestoreClient> client,
|
||||
std::shared_ptr<core::AsyncEventListener<util::Empty>> async_listener);
|
||||
|
||||
/**
|
||||
* Removes the listener being tracked by this FIRListenerRegistration. After
|
||||
* the initial call, subsequent calls have no effect.
|
||||
*/
|
||||
void Remove() override;
|
||||
|
||||
private:
|
||||
/** The client that was used to register this listen. */
|
||||
std::shared_ptr<core::FirestoreClient> client_;
|
||||
|
||||
/** The async listener that is used to mute events synchronously. */
|
||||
std::weak_ptr<core::AsyncEventListener<util::Empty>> async_listener_;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_API_SNAPSHOTS_IN_SYNC_LISTENER_REGISTRATION_H_
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_API_SOURCE_H_
|
||||
#define FIRESTORE_CORE_SRC_API_SOURCE_H_
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace api {
|
||||
|
||||
/**
|
||||
* An enum that configures the behavior of `DocumentReference.GetDocument()` and
|
||||
* `Query.GetDocuments()`. By providing a source enum the `GetDocument[s]`
|
||||
* methods can be configured to fetch results only from the server, only from
|
||||
* the local cache, or attempt to fetch results from the server and fall back to
|
||||
* the cache (which is the default).
|
||||
*
|
||||
* See `FIRFirestoreSource` for more details.
|
||||
*/
|
||||
enum class Source { Default, Server, Cache };
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_API_SOURCE_H_
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/api/write_batch.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "Firestore/core/src/api/document_reference.h"
|
||||
#include "Firestore/core/src/api/firestore.h"
|
||||
#include "Firestore/core/src/api/query_snapshot.h"
|
||||
#include "Firestore/core/src/core/firestore_client.h"
|
||||
#include "Firestore/core/src/core/user_data.h"
|
||||
#include "Firestore/core/src/model/delete_mutation.h"
|
||||
#include "Firestore/core/src/util/exception.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace api {
|
||||
|
||||
using model::DeleteMutation;
|
||||
using model::Precondition;
|
||||
using util::ThrowIllegalState;
|
||||
using util::ThrowInvalidArgument;
|
||||
|
||||
void WriteBatch::SetData(const DocumentReference& reference,
|
||||
core::ParsedSetData&& set_data) {
|
||||
VerifyNotCommitted();
|
||||
ValidateReference(reference);
|
||||
|
||||
mutations_.push_back(std::move(set_data).ToMutation(
|
||||
reference.key(), model::Precondition::None()));
|
||||
}
|
||||
|
||||
void WriteBatch::UpdateData(const DocumentReference& reference,
|
||||
core::ParsedUpdateData&& update_data) {
|
||||
VerifyNotCommitted();
|
||||
ValidateReference(reference);
|
||||
|
||||
mutations_.push_back(
|
||||
std::move(update_data)
|
||||
.ToMutation(reference.key(), model::Precondition::Exists(true)));
|
||||
}
|
||||
|
||||
void WriteBatch::DeleteData(const DocumentReference& reference) {
|
||||
VerifyNotCommitted();
|
||||
ValidateReference(reference);
|
||||
|
||||
mutations_.push_back(DeleteMutation(reference.key(), Precondition::None()));
|
||||
}
|
||||
|
||||
void WriteBatch::Commit(util::StatusCallback callback) {
|
||||
VerifyNotCommitted();
|
||||
|
||||
committed_ = true;
|
||||
firestore_->client()->WriteMutations(std::move(mutations_),
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
void WriteBatch::VerifyNotCommitted() const {
|
||||
if (committed_) {
|
||||
ThrowIllegalState(
|
||||
"A write batch can no longer be used after commit has been called.");
|
||||
}
|
||||
}
|
||||
|
||||
void WriteBatch::ValidateReference(const DocumentReference& reference) const {
|
||||
if (reference.firestore() != firestore_) {
|
||||
ThrowInvalidArgument(
|
||||
"Provided document reference is from a different Cloud Firestore "
|
||||
"instance.");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_API_WRITE_BATCH_H_
|
||||
#define FIRESTORE_CORE_SRC_API_WRITE_BATCH_H_
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Firestore/core/src/api/api_fwd.h"
|
||||
#include "Firestore/core/src/core/core_fwd.h"
|
||||
#include "Firestore/core/src/model/mutation.h"
|
||||
#include "Firestore/core/src/util/status_fwd.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace api {
|
||||
|
||||
class WriteBatch {
|
||||
public:
|
||||
explicit WriteBatch(std::shared_ptr<Firestore> firestore)
|
||||
: firestore_{std::move(firestore)} {
|
||||
}
|
||||
|
||||
void SetData(const DocumentReference& reference,
|
||||
core::ParsedSetData&& set_data);
|
||||
void UpdateData(const DocumentReference& reference,
|
||||
core::ParsedUpdateData&& update_data);
|
||||
void DeleteData(const DocumentReference& reference);
|
||||
|
||||
void Commit(util::StatusCallback callback);
|
||||
|
||||
const std::shared_ptr<Firestore>& firestore() const {
|
||||
return firestore_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Firestore> firestore_;
|
||||
std::vector<model::Mutation> mutations_;
|
||||
bool committed_ = false;
|
||||
|
||||
void VerifyNotCommitted() const;
|
||||
void ValidateReference(const DocumentReference& reference) const;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_API_WRITE_BATCH_H_
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2021 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 FIRESTORE_CORE_SRC_BUNDLE_BUNDLE_CALLBACK_H_
|
||||
#define FIRESTORE_CORE_SRC_BUNDLE_BUNDLE_CALLBACK_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "Firestore/core/src/bundle/bundle_metadata.h"
|
||||
#include "Firestore/core/src/bundle/named_query.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace bundle {
|
||||
|
||||
/**
|
||||
* Interface implemented by components that can apply changes from a bundle to
|
||||
* local storage.
|
||||
*/
|
||||
class BundleCallback {
|
||||
public:
|
||||
virtual ~BundleCallback() = default;
|
||||
|
||||
/**
|
||||
* Applies the documents from a bundle to the "ground-state" (remote)
|
||||
* documents.
|
||||
*
|
||||
* Local documents are re-calculated if there are remaining mutations in the
|
||||
* queue.
|
||||
*/
|
||||
virtual model::DocumentMap ApplyBundledDocuments(
|
||||
const model::MutableDocumentMap& documents,
|
||||
const std::string& bundle_id) = 0;
|
||||
|
||||
/** Saves the given NamedQuery to local persistence. */
|
||||
virtual void SaveNamedQuery(const NamedQuery& query,
|
||||
const model::DocumentKeySet& keys) = 0;
|
||||
|
||||
/** Saves the given BundleMetadata to local persistence. */
|
||||
virtual void SaveBundle(const BundleMetadata& metadata) = 0;
|
||||
};
|
||||
|
||||
} // namespace bundle
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_BUNDLE_BUNDLE_CALLBACK_H_
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2021 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 FIRESTORE_CORE_SRC_BUNDLE_BUNDLE_DOCUMENT_H_
|
||||
#define FIRESTORE_CORE_SRC_BUNDLE_BUNDLE_DOCUMENT_H_
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/bundle/bundle_element.h"
|
||||
#include "Firestore/core/src/model/mutable_document.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace bundle {
|
||||
|
||||
/** Represents a document that was saved to a bundle. */
|
||||
class BundleDocument : public BundleElement {
|
||||
public:
|
||||
BundleDocument() = default;
|
||||
|
||||
explicit BundleDocument(model::MutableDocument document)
|
||||
: document_(std::move(document)) {
|
||||
}
|
||||
|
||||
Type element_type() const override {
|
||||
return Type::Document;
|
||||
}
|
||||
|
||||
/** Returns the key for this document. */
|
||||
const model::DocumentKey& key() const {
|
||||
return document_.key();
|
||||
}
|
||||
|
||||
/** Returns the document. */
|
||||
const model::MutableDocument& document() const {
|
||||
return document_;
|
||||
}
|
||||
|
||||
private:
|
||||
model::MutableDocument document_;
|
||||
};
|
||||
|
||||
inline bool operator==(const BundleDocument& lhs, const BundleDocument& rhs) {
|
||||
return lhs.document() == rhs.document();
|
||||
}
|
||||
|
||||
inline bool operator!=(const BundleDocument& lhs, const BundleDocument& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
} // namespace bundle
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_BUNDLE_BUNDLE_DOCUMENT_H_
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2021 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 FIRESTORE_CORE_SRC_BUNDLE_BUNDLE_ELEMENT_H_
|
||||
#define FIRESTORE_CORE_SRC_BUNDLE_BUNDLE_ELEMENT_H_
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace bundle {
|
||||
|
||||
/**
|
||||
* Abstract class to give all elements from bundles a common type.
|
||||
*/
|
||||
class BundleElement {
|
||||
public:
|
||||
enum class Type { Metadata, NamedQuery, DocumentMetadata, Document };
|
||||
|
||||
virtual ~BundleElement() = default;
|
||||
|
||||
virtual Type element_type() const = 0;
|
||||
};
|
||||
|
||||
} // namespace bundle
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_BUNDLE_BUNDLE_ELEMENT_H_
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright 2021 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 "Firestore/core/src/bundle/bundle_loader.h"
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "Firestore/core/include/firebase/firestore/firestore_errors.h"
|
||||
#include "Firestore/core/src/api/load_bundle_task.h"
|
||||
#include "Firestore/core/src/bundle/bundle_document.h"
|
||||
#include "Firestore/core/src/model/document.h"
|
||||
#include "Firestore/core/src/model/document_key.h"
|
||||
#include "Firestore/core/src/model/document_key_set.h"
|
||||
#include "Firestore/core/src/model/model_fwd.h"
|
||||
#include "Firestore/core/src/model/mutable_document.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace bundle {
|
||||
|
||||
using firestore::Error;
|
||||
using firestore::api::LoadBundleTaskProgress;
|
||||
using firestore::api::LoadBundleTaskState;
|
||||
using model::DocumentKeySet;
|
||||
using model::DocumentMap;
|
||||
using model::MutableDocument;
|
||||
using util::Status;
|
||||
using util::StatusOr;
|
||||
|
||||
Status BundleLoader::AddElementInternal(const BundleElement& element) {
|
||||
HARD_ASSERT(element.element_type() != BundleElement::Type::Metadata,
|
||||
"Unexpected bundle metadata element.");
|
||||
|
||||
switch (element.element_type()) {
|
||||
case BundleElement::Type::NamedQuery: {
|
||||
queries_.push_back(static_cast<const NamedQuery&>(element));
|
||||
break;
|
||||
}
|
||||
|
||||
case BundleElement::Type::DocumentMetadata: {
|
||||
const auto& document_metadata =
|
||||
static_cast<const BundledDocumentMetadata&>(element);
|
||||
current_document_ = document_metadata.key();
|
||||
documents_metadata_.emplace(document_metadata.key(), document_metadata);
|
||||
|
||||
if (!document_metadata.exists()) {
|
||||
documents_ = documents_.insert(
|
||||
document_metadata.key(),
|
||||
MutableDocument::NoDocument(document_metadata.key(),
|
||||
document_metadata.read_time()));
|
||||
current_document_ = absl::nullopt;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case BundleElement::Type::Document: {
|
||||
const auto& document = static_cast<const BundleDocument&>(element);
|
||||
if (!current_document_.has_value() ||
|
||||
document.key() != current_document_.value()) {
|
||||
return {Status(
|
||||
Error::kErrorInvalidArgument,
|
||||
"The document being added does not match the stored metadata.")};
|
||||
}
|
||||
|
||||
documents_ = documents_.insert(document.key(), document.document());
|
||||
current_document_ = absl::nullopt;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// It is impossible to reach here, because Type::Metadata is checked at
|
||||
// the beginning of the method.
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
StatusOr<absl::optional<LoadBundleTaskProgress>> BundleLoader::AddElement(
|
||||
std::unique_ptr<BundleElement> element_ptr, uint64_t byte_size) {
|
||||
HARD_ASSERT(element_ptr->element_type() != BundleElement::Type::Metadata,
|
||||
"Unexpected bundle metadata element.");
|
||||
|
||||
auto before_count = documents_.size();
|
||||
|
||||
auto result = AddElementInternal(*element_ptr);
|
||||
if (!result.ok()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
bytes_loaded_ += byte_size;
|
||||
|
||||
// Document has only been partially loaded, no progress to report.
|
||||
if (before_count == documents_.size()) {
|
||||
return {absl::nullopt};
|
||||
}
|
||||
|
||||
LoadBundleTaskProgress progress{
|
||||
documents_.size(), metadata_.total_documents(), bytes_loaded_,
|
||||
metadata_.total_bytes(), LoadBundleTaskState::kInProgress};
|
||||
return {absl::make_optional(std::move(progress))};
|
||||
}
|
||||
|
||||
StatusOr<DocumentMap> BundleLoader::ApplyChanges() {
|
||||
if (current_document_ != absl::nullopt) {
|
||||
return StatusOr<DocumentMap>(
|
||||
Status(Error::kErrorInvalidArgument,
|
||||
"Bundled documents end with a document metadata "
|
||||
"element instead of a document."));
|
||||
}
|
||||
if (metadata_.total_documents() != documents_.size()) {
|
||||
return StatusOr<DocumentMap>(
|
||||
Status(Error::kErrorInvalidArgument,
|
||||
"Loaded documents count is not the same as in metadata."));
|
||||
}
|
||||
|
||||
auto changes =
|
||||
callback_->ApplyBundledDocuments(documents_, metadata_.bundle_id());
|
||||
auto query_document_map = GetQueryDocumentMapping();
|
||||
for (const auto& named_query : queries_) {
|
||||
const auto& matching_keys = query_document_map[named_query.query_name()];
|
||||
callback_->SaveNamedQuery(named_query, matching_keys);
|
||||
}
|
||||
|
||||
callback_->SaveBundle(metadata_);
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, DocumentKeySet>
|
||||
BundleLoader::GetQueryDocumentMapping() {
|
||||
std::unordered_map<std::string, DocumentKeySet> result;
|
||||
for (const auto& named_query : queries_) {
|
||||
result.emplace(named_query.query_name(), DocumentKeySet{});
|
||||
}
|
||||
|
||||
for (const auto& doc_metadata : documents_metadata_) {
|
||||
const auto& metadata = doc_metadata.second;
|
||||
for (const auto& query : doc_metadata.second.queries()) {
|
||||
auto inserted = result[query].insert(metadata.key());
|
||||
result[query] = std::move(inserted);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace bundle
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2021 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 FIRESTORE_CORE_SRC_BUNDLE_BUNDLE_LOADER_H_
|
||||
#define FIRESTORE_CORE_SRC_BUNDLE_BUNDLE_LOADER_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Firestore/core/src/api/load_bundle_task.h"
|
||||
#include "Firestore/core/src/bundle/bundle_callback.h"
|
||||
#include "Firestore/core/src/bundle/bundle_element.h"
|
||||
#include "Firestore/core/src/bundle/bundled_document_metadata.h"
|
||||
#include "Firestore/core/src/immutable/sorted_map.h"
|
||||
#include "Firestore/core/src/model/document_key.h"
|
||||
#include "Firestore/core/src/model/model_fwd.h"
|
||||
#include "Firestore/core/src/util/statusor.h"
|
||||
#include "absl/types/optional.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace bundle {
|
||||
|
||||
inline api::LoadBundleTaskProgress SuccessProgress(
|
||||
const bundle::BundleMetadata& metadata) {
|
||||
return {metadata.total_documents(), metadata.total_documents(),
|
||||
metadata.total_bytes(), metadata.total_bytes(),
|
||||
api::LoadBundleTaskState::kSuccess};
|
||||
}
|
||||
|
||||
inline api::LoadBundleTaskProgress InitialProgress(
|
||||
const bundle::BundleMetadata& metadata) {
|
||||
return {0, metadata.total_documents(), 0, metadata.total_bytes(),
|
||||
api::LoadBundleTaskState::kInProgress};
|
||||
}
|
||||
|
||||
class BundleLoader {
|
||||
public:
|
||||
using AddElementResult =
|
||||
util::StatusOr<absl::optional<api::LoadBundleTaskProgress>>;
|
||||
|
||||
BundleLoader(BundleCallback* callback, BundleMetadata metadata)
|
||||
: callback_(callback), metadata_(std::move(metadata)) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an element from the bundle to the loader.
|
||||
*
|
||||
* @return a new progress if adding the element leads to a new progress,
|
||||
* otherwise returns `nullopt`. If an error occurred, returns a not `ok()`
|
||||
* status.
|
||||
*/
|
||||
AddElementResult AddElement(std::unique_ptr<BundleElement> element,
|
||||
uint64_t byte_size);
|
||||
|
||||
/**
|
||||
* Applies the loaded documents and queries to local store. Returns the
|
||||
* document view changes. If an error occurred, returns a not `ok()` status.
|
||||
*/
|
||||
util::StatusOr<model::DocumentMap> ApplyChanges();
|
||||
|
||||
private:
|
||||
/**
|
||||
* @return A map whose keys are the query names in the loading bundle, and
|
||||
* values are matching document keys.
|
||||
*/
|
||||
std::unordered_map<std::string, model::DocumentKeySet>
|
||||
GetQueryDocumentMapping();
|
||||
|
||||
/**
|
||||
* Adds the given BundleElement to the internal containers, depending on the
|
||||
* element type.
|
||||
*/
|
||||
util::Status AddElementInternal(const BundleElement& element);
|
||||
|
||||
BundleCallback* callback_ = nullptr;
|
||||
BundleMetadata metadata_;
|
||||
std::vector<NamedQuery> queries_;
|
||||
std::unordered_map<model::DocumentKey,
|
||||
BundledDocumentMetadata,
|
||||
model::DocumentKeyHash>
|
||||
documents_metadata_;
|
||||
model::MutableDocumentMap documents_;
|
||||
|
||||
uint64_t bytes_loaded_ = 0;
|
||||
absl::optional<model::DocumentKey> current_document_;
|
||||
};
|
||||
|
||||
} // namespace bundle
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_BUNDLE_BUNDLE_LOADER_H_
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2021 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 FIRESTORE_CORE_SRC_BUNDLE_BUNDLE_METADATA_H_
|
||||
#define FIRESTORE_CORE_SRC_BUNDLE_BUNDLE_METADATA_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/bundle/bundle_element.h"
|
||||
#include "Firestore/core/src/model/snapshot_version.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace bundle {
|
||||
|
||||
/**
|
||||
* Represents Firestore bundle metadata saved by the SDK in its local storage.
|
||||
*/
|
||||
class BundleMetadata : public BundleElement {
|
||||
public:
|
||||
BundleMetadata() = default;
|
||||
|
||||
BundleMetadata(std::string bundle_id,
|
||||
int version,
|
||||
model::SnapshotVersion create_time)
|
||||
: bundle_id_(std::move(bundle_id)),
|
||||
version_(version),
|
||||
create_time_(create_time) {
|
||||
}
|
||||
|
||||
BundleMetadata(std::string bundle_id,
|
||||
int version,
|
||||
model::SnapshotVersion create_time,
|
||||
uint32_t total_documents,
|
||||
uint64_t total_bytes)
|
||||
: bundle_id_(std::move(bundle_id)),
|
||||
version_(version),
|
||||
create_time_(create_time),
|
||||
total_documents_(total_documents),
|
||||
total_bytes_(total_bytes) {
|
||||
}
|
||||
|
||||
Type element_type() const override {
|
||||
return Type::Metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The ID of the bundle. It is used together with `create_time()` to
|
||||
* determine if a bundle has been loaded by the SDK.
|
||||
*/
|
||||
const std::string& bundle_id() const {
|
||||
return bundle_id_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The schema version of the bundle.
|
||||
*/
|
||||
uint32_t version() const {
|
||||
return version_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The snapshot version of the bundle when created by the server SDKs.
|
||||
*/
|
||||
model::SnapshotVersion create_time() const {
|
||||
return create_time_;
|
||||
}
|
||||
|
||||
/** @return The number of documents in the bundle. */
|
||||
uint32_t total_documents() const {
|
||||
return total_documents_;
|
||||
}
|
||||
|
||||
/** @return The number of bytes of the bundle. */
|
||||
uint64_t total_bytes() const {
|
||||
return total_bytes_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string bundle_id_;
|
||||
uint32_t version_ = 0;
|
||||
model::SnapshotVersion create_time_;
|
||||
|
||||
uint32_t total_documents_ = 0;
|
||||
uint64_t total_bytes_ = 0;
|
||||
};
|
||||
|
||||
inline bool operator==(const BundleMetadata& lhs, const BundleMetadata& rhs) {
|
||||
return lhs.bundle_id() == rhs.bundle_id() && lhs.version() == rhs.version() &&
|
||||
lhs.create_time() == rhs.create_time() &&
|
||||
lhs.total_documents() == rhs.total_documents() &&
|
||||
lhs.total_bytes() == rhs.total_bytes();
|
||||
}
|
||||
|
||||
inline bool operator!=(const BundleMetadata& lhs, const BundleMetadata& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
} // namespace bundle
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_BUNDLE_BUNDLE_METADATA_H_
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright 2021 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 "Firestore/core/src/bundle/bundle_reader.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/strings/numbers.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace bundle {
|
||||
|
||||
using nlohmann::json;
|
||||
using util::ByteStream;
|
||||
using util::StreamReadResult;
|
||||
|
||||
namespace {
|
||||
|
||||
json Parse(absl::string_view s) {
|
||||
return json::parse(s.begin(), s.end(), /*callback=*/nullptr,
|
||||
/*allow_exceptions=*/false);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BundleReader::BundleReader(BundleSerializer serializer,
|
||||
std::unique_ptr<ByteStream> input)
|
||||
: serializer_(std::move(serializer)), input_(std::move(input)) {
|
||||
}
|
||||
|
||||
BundleMetadata BundleReader::GetBundleMetadata() {
|
||||
if (metadata_loaded_) {
|
||||
return metadata_;
|
||||
}
|
||||
|
||||
std::unique_ptr<BundleElement> element = ReadNextElement();
|
||||
if (!element || element->element_type() != BundleElement::Type::Metadata) {
|
||||
Fail("Failed to get bundle metadata");
|
||||
return {};
|
||||
}
|
||||
|
||||
metadata_loaded_ = true;
|
||||
metadata_ = static_cast<BundleMetadata&>(*element);
|
||||
return metadata_;
|
||||
}
|
||||
|
||||
std::unique_ptr<BundleElement> BundleReader::GetNextElement() {
|
||||
// Makes sure metadata is read before proceeding. The metadata element is the
|
||||
// first element in the bundle stream.
|
||||
GetBundleMetadata();
|
||||
return ReadNextElement();
|
||||
}
|
||||
|
||||
std::unique_ptr<BundleElement> BundleReader::ReadNextElement() {
|
||||
auto length_prefix = ReadLengthPrefix();
|
||||
if (!length_prefix.has_value()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t prefix_value = 0;
|
||||
auto ok = absl::SimpleAtoi<size_t>(length_prefix.value(), &prefix_value);
|
||||
if (!ok) {
|
||||
Fail("Prefix string is not a valid number");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
buffer_.clear();
|
||||
ReadJsonToBuffer(prefix_value);
|
||||
if (!reader_status_.ok()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// metadata's size does not count in `bytes_read_`.
|
||||
if (metadata_loaded_) {
|
||||
bytes_read_ += length_prefix.value().size() + buffer_.size();
|
||||
}
|
||||
auto result = DecodeBundleElementFromBuffer();
|
||||
reader_status_.Update(json_reader_.status());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
absl::optional<std::string> BundleReader::ReadLengthPrefix() {
|
||||
// length string of size 16 indicates an element about 1PB, which is
|
||||
// impossible for valid bundles.
|
||||
StreamReadResult result = input_->ReadUntil('{', 16);
|
||||
if (!result.ok()) {
|
||||
reader_status_.Update(result.status());
|
||||
return absl::nullopt;
|
||||
}
|
||||
|
||||
// Underlying stream is closed, and there happens to be no more data to
|
||||
// process.
|
||||
if (result.eof() && result.ValueOrDie().empty()) {
|
||||
return absl::nullopt;
|
||||
}
|
||||
|
||||
return absl::make_optional(std::move(result).ValueOrDie());
|
||||
}
|
||||
|
||||
void BundleReader::ReadJsonToBuffer(size_t required_size) {
|
||||
if (!reader_status_.ok()) {
|
||||
return;
|
||||
}
|
||||
while (buffer_.size() < required_size) {
|
||||
// Read at most 1024 bytes every time, to avoid allocating a huge buffer
|
||||
// when corruption leads to large `required_size`.
|
||||
auto size = std::min<size_t>(1024ul, required_size - buffer_.size());
|
||||
StreamReadResult result = input_->Read(size);
|
||||
if (!result.ok()) {
|
||||
reader_status_.Update(result.status());
|
||||
return;
|
||||
}
|
||||
bool eof = result.eof();
|
||||
buffer_.append(std::move(result).ValueOrDie());
|
||||
if (eof) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer_.size() < required_size) {
|
||||
Fail("Available input string is smaller than what length prefix indicates");
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<BundleElement> BundleReader::DecodeBundleElementFromBuffer() {
|
||||
auto json_object = Parse(buffer_);
|
||||
if (json_object.is_discarded()) {
|
||||
Fail("Failed to parse string into json");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (json_object.contains("metadata")) {
|
||||
return absl::make_unique<BundleMetadata>(serializer_.DecodeBundleMetadata(
|
||||
json_reader_, json_object.at("metadata")));
|
||||
} else if (json_object.contains("namedQuery")) {
|
||||
auto q = serializer_.DecodeNamedQuery(json_reader_,
|
||||
json_object.at("namedQuery"));
|
||||
return absl::make_unique<NamedQuery>(std::move(q));
|
||||
} else if (json_object.contains("documentMetadata")) {
|
||||
return absl::make_unique<BundledDocumentMetadata>(
|
||||
serializer_.DecodeDocumentMetadata(json_reader_,
|
||||
json_object.at("documentMetadata")));
|
||||
} else if (json_object.contains("document")) {
|
||||
return absl::make_unique<BundleDocument>(
|
||||
serializer_.DecodeDocument(json_reader_, json_object.at("document")));
|
||||
} else {
|
||||
Fail("Unrecognized BundleElement");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace bundle
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright 2021 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 FIRESTORE_CORE_SRC_BUNDLE_BUNDLE_READER_H_
|
||||
#define FIRESTORE_CORE_SRC_BUNDLE_BUNDLE_READER_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/bundle/bundle_metadata.h"
|
||||
#include "Firestore/core/src/bundle/bundle_serializer.h"
|
||||
#include "Firestore/core/src/util/byte_stream.h"
|
||||
#include "absl/types/optional.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace bundle {
|
||||
|
||||
/**
|
||||
* Reads the length-prefixed JSON stream for Bundles.
|
||||
*
|
||||
* The class takes a bundle stream and presents abstractions to read bundled
|
||||
* elements out of the underlying content.
|
||||
*/
|
||||
class BundleReader {
|
||||
public:
|
||||
BundleReader(BundleSerializer serializer,
|
||||
std::unique_ptr<util::ByteStream> input);
|
||||
|
||||
/**
|
||||
* Returns the metadata element from the bundle.
|
||||
*
|
||||
* Caches the result when first called, and returns the cached result in the
|
||||
* following calls.
|
||||
*/
|
||||
BundleMetadata GetBundleMetadata();
|
||||
|
||||
/**
|
||||
* Returns the next element from the bundle. Metadata elements can be accessed
|
||||
* by `GetBundleMetadata`, they are not returned from this method.
|
||||
*
|
||||
* When there is no more element to return, a `nullptr` is returned. Check
|
||||
* `reader_status()` to see if it is due to the completion of bundle (status
|
||||
* will be `ok()`), or an error.
|
||||
*/
|
||||
std::unique_ptr<BundleElement> GetNextElement();
|
||||
|
||||
/** Returns whether this instance is in good state. */
|
||||
const util::Status& reader_status() const {
|
||||
return reader_status_;
|
||||
}
|
||||
|
||||
/** Sets this instance to a failed state. */
|
||||
void Fail(std::string msg) {
|
||||
reader_status_.Update(util::Status(Error::kErrorDataLoss, std::move(msg)));
|
||||
}
|
||||
|
||||
/** How many bytes have we read from the bundle. */
|
||||
int64_t bytes_read() const {
|
||||
return bytes_read_;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Reads from the head of internal buffer, pulls more data from underlying
|
||||
* stream until a complete element is found (including the prefixed length and
|
||||
* the JSON string).
|
||||
*
|
||||
* Once a complete element is read, it is dropped from internal buffer.
|
||||
*
|
||||
* Returns either the bundled element, or null if we have reached the end of
|
||||
* the stream.
|
||||
*/
|
||||
std::unique_ptr<BundleElement> ReadNextElement();
|
||||
|
||||
/**
|
||||
* Reads the length prefix string from bundle stream. Returns `nullopt` when
|
||||
* at the end of stream.
|
||||
*
|
||||
* The string representing a length prefix is whatever string we have from
|
||||
* the `input_` until the next character is a "{" (start of JSON element).
|
||||
* So calling this a second time will return an empty string.
|
||||
*/
|
||||
absl::optional<std::string> ReadLengthPrefix();
|
||||
|
||||
/**
|
||||
* Reads `required_size` number of chars from stream into internal `buffer_`.
|
||||
*/
|
||||
void ReadJsonToBuffer(size_t required_size);
|
||||
|
||||
/**
|
||||
* Decodes internal `buffer_` into a `BundleElement`, returned as a unique_ptr
|
||||
* pointing to the element. Returns nullptr if fails.
|
||||
*
|
||||
* Note this method will leave `buffer_` unchanged.
|
||||
*/
|
||||
std::unique_ptr<BundleElement> DecodeBundleElementFromBuffer();
|
||||
|
||||
BundleSerializer serializer_;
|
||||
JsonReader json_reader_;
|
||||
|
||||
// Input stream holding bundle data.
|
||||
std::unique_ptr<util::ByteStream> input_;
|
||||
|
||||
// Cached bundle metadata.
|
||||
BundleMetadata metadata_;
|
||||
bool metadata_loaded_ = false;
|
||||
|
||||
// Internal buffer, cleared every time a complete element is parsed from this.
|
||||
std::string buffer_;
|
||||
|
||||
util::Status reader_status_;
|
||||
int64_t bytes_read_ = 0;
|
||||
};
|
||||
|
||||
} // namespace bundle
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_BUNDLE_BUNDLE_READER_H_
|
||||
@@ -0,0 +1,812 @@
|
||||
/*
|
||||
* Copyright 2021 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 Requiredd by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/bundle/bundle_serializer.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "Firestore/core/src/core/bound.h"
|
||||
#include "Firestore/core/src/core/direction.h"
|
||||
#include "Firestore/core/src/core/field_filter.h"
|
||||
#include "Firestore/core/src/core/filter.h"
|
||||
#include "Firestore/core/src/core/order_by.h"
|
||||
#include "Firestore/core/src/core/query.h"
|
||||
#include "Firestore/core/src/core/target.h"
|
||||
#include "Firestore/core/src/model/field_path.h"
|
||||
#include "Firestore/core/src/model/mutable_document.h"
|
||||
#include "Firestore/core/src/model/resource_path.h"
|
||||
#include "Firestore/core/src/model/value_util.h"
|
||||
#include "Firestore/core/src/nanopb/byte_string.h"
|
||||
#include "Firestore/core/src/nanopb/message.h"
|
||||
#include "Firestore/core/src/nanopb/nanopb_util.h"
|
||||
#include "Firestore/core/src/timestamp_internal.h"
|
||||
#include "Firestore/core/src/util/statusor.h"
|
||||
#include "Firestore/core/src/util/string_format.h"
|
||||
#include "Firestore/core/src/util/string_util.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "absl/strings/numbers.h"
|
||||
#include "absl/time/time.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace bundle {
|
||||
namespace {
|
||||
|
||||
using absl::Time;
|
||||
using core::Bound;
|
||||
using core::Direction;
|
||||
using core::FieldFilter;
|
||||
using core::Filter;
|
||||
using core::FilterList;
|
||||
using core::LimitType;
|
||||
using core::OrderBy;
|
||||
using core::OrderByList;
|
||||
using core::Target;
|
||||
using model::Document;
|
||||
using model::DocumentKey;
|
||||
using model::FieldPath;
|
||||
using model::MutableDocument;
|
||||
using model::NaNValue;
|
||||
using model::NullValue;
|
||||
using model::ObjectValue;
|
||||
using model::ResourcePath;
|
||||
using model::SnapshotVersion;
|
||||
using nanopb::ByteString;
|
||||
using nanopb::MakeSharedMessage;
|
||||
using nanopb::Message;
|
||||
using nanopb::SetRepeatedField;
|
||||
using nanopb::SharedMessage;
|
||||
using nlohmann::json;
|
||||
using util::StatusOr;
|
||||
using util::StringFormat;
|
||||
|
||||
template <typename T>
|
||||
const std::vector<T>& EmptyVector() {
|
||||
static auto* empty = new std::vector<T>;
|
||||
return *empty;
|
||||
}
|
||||
|
||||
Timestamp DecodeTimestamp(JsonReader& reader, const json& version) {
|
||||
StatusOr<Timestamp> decoded;
|
||||
if (version.is_string()) {
|
||||
Time time;
|
||||
std::string err;
|
||||
bool ok = absl::ParseTime(
|
||||
absl::RFC3339_full, version.get_ref<const std::string&>(), &time, &err);
|
||||
if (ok) {
|
||||
decoded = TimestampInternal::FromUntrustedTime(time);
|
||||
} else {
|
||||
reader.Fail("Parsing timestamp failed with error: " + err);
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
decoded = TimestampInternal::FromUntrustedSecondsAndNanos(
|
||||
reader.RequiredInt<int64_t>("seconds", version),
|
||||
reader.RequiredInt<int32_t>("nanos", version));
|
||||
}
|
||||
|
||||
if (!decoded.ok()) {
|
||||
reader.Fail(
|
||||
"Failed to decode json into valid protobuf Timestamp with error '%s'",
|
||||
decoded.status().error_message());
|
||||
return {};
|
||||
}
|
||||
return decoded.ConsumeValueOrDie();
|
||||
}
|
||||
|
||||
SnapshotVersion DecodeSnapshotVersion(JsonReader& reader, const json& version) {
|
||||
return SnapshotVersion(DecodeTimestamp(reader, version));
|
||||
}
|
||||
|
||||
void VerifyStructuredQuery(JsonReader& reader, const json& query) {
|
||||
if (!query.is_object()) {
|
||||
reader.Fail("'structuredQuery' is not an object as expected.");
|
||||
return;
|
||||
}
|
||||
if (query.contains("select")) {
|
||||
reader.Fail(
|
||||
"Queries with 'select' statements are not supported in bundles");
|
||||
return;
|
||||
}
|
||||
if (!query.contains("from")) {
|
||||
reader.Fail("Query does not have a 'from' collection");
|
||||
return;
|
||||
}
|
||||
if (query.contains("offset")) {
|
||||
reader.Fail("Queries with 'offset' are not supported in bundles");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a json object into the given `parent` and `group` reference.
|
||||
*
|
||||
* Specifically, if the given `from_json` is for a collection group query, its
|
||||
* collection id will be decoded into `group`; otherwise, the collection id will
|
||||
* be appended to `parent`.
|
||||
*/
|
||||
void DecodeCollectionSource(JsonReader& reader,
|
||||
const json& from_json,
|
||||
ResourcePath& parent,
|
||||
std::string& group) {
|
||||
const auto& from = from_json.get_ref<const std::vector<json>&>();
|
||||
if (from.size() != 1) {
|
||||
reader.Fail(
|
||||
"Only queries with a single 'from' clause are supported by the SDK");
|
||||
return;
|
||||
}
|
||||
const auto& collection_selector = from.at(0);
|
||||
const auto& collection_id =
|
||||
reader.RequiredString("collectionId", collection_selector);
|
||||
bool all_descendants =
|
||||
reader.OptionalBool("allDescendants", collection_selector);
|
||||
|
||||
if (all_descendants) {
|
||||
group = collection_id;
|
||||
} else {
|
||||
parent = parent.Append(collection_id);
|
||||
}
|
||||
}
|
||||
|
||||
FieldPath DecodeFieldReference(JsonReader& reader, const json& field) {
|
||||
if (!field.is_object()) {
|
||||
reader.Fail("'field' should be an json object, but it is not");
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto& field_path = reader.RequiredString("fieldPath", field);
|
||||
auto result = FieldPath::FromServerFormat(field_path);
|
||||
|
||||
if (!result.ok()) {
|
||||
reader.set_status(result.status());
|
||||
return {};
|
||||
} else {
|
||||
return result.ConsumeValueOrDie();
|
||||
}
|
||||
}
|
||||
|
||||
Filter::Operator DecodeFieldFilterOperator(JsonReader& reader,
|
||||
const std::string& op) {
|
||||
if (op == "LESS_THAN") {
|
||||
return Filter::Operator::LessThan;
|
||||
} else if (op == "LESS_THAN_OR_EQUAL") {
|
||||
return Filter::Operator::LessThanOrEqual;
|
||||
} else if (op == "EQUAL") {
|
||||
return Filter::Operator::Equal;
|
||||
} else if (op == "NOT_EQUAL") {
|
||||
return Filter::Operator::NotEqual;
|
||||
} else if (op == "GREATER_THAN") {
|
||||
return Filter::Operator::GreaterThan;
|
||||
} else if (op == "GREATER_THAN_OR_EQUAL") {
|
||||
return Filter::Operator::GreaterThanOrEqual;
|
||||
} else if (op == "ARRAY_CONTAINS") {
|
||||
return Filter::Operator::ArrayContains;
|
||||
} else if (op == "IN") {
|
||||
return Filter::Operator::In;
|
||||
} else if (op == "ARRAY_CONTAINS_ANY") {
|
||||
return Filter::Operator::ArrayContainsAny;
|
||||
} else if (op == "NOT_IN") {
|
||||
return Filter::Operator::NotIn;
|
||||
} else {
|
||||
reader.Fail("Operator in filter is not valid: " + op);
|
||||
// We have to return something.
|
||||
return Filter::Operator::Equal;
|
||||
}
|
||||
}
|
||||
|
||||
Filter InvalidFilter() {
|
||||
// The exact value doesn't matter. Note that there's no way to create the base
|
||||
// class `Filter`, so it has to be one of the derived classes.
|
||||
return FieldFilter::Create({}, {},
|
||||
MakeSharedMessage(google_firestore_v1_Value{}));
|
||||
}
|
||||
|
||||
Filter DecodeUnaryFilter(JsonReader& reader, const json& filter) {
|
||||
FieldPath path =
|
||||
DecodeFieldReference(reader, reader.RequiredObject("field", filter));
|
||||
std::string op = reader.RequiredString("op", filter);
|
||||
|
||||
// Return early if !ok(), because `FieldFilter::Create` will abort with
|
||||
// invalid inputs.
|
||||
if (!reader.ok()) {
|
||||
return InvalidFilter();
|
||||
}
|
||||
|
||||
if (op == "IS_NAN") {
|
||||
return FieldFilter::Create(path, Filter::Operator::Equal, NaNValue());
|
||||
} else if (op == "IS_NULL") {
|
||||
return FieldFilter::Create(path, Filter::Operator::Equal, NullValue());
|
||||
} else if (op == "IS_NOT_NAN") {
|
||||
return FieldFilter::Create(path, Filter::Operator::NotEqual, NaNValue());
|
||||
} else if (op == "IS_NOT_NULL") {
|
||||
return FieldFilter::Create(path, Filter::Operator::NotEqual, NullValue());
|
||||
}
|
||||
|
||||
reader.Fail("Unexpected unary filter operator: " + op);
|
||||
return InvalidFilter();
|
||||
}
|
||||
|
||||
OrderByList DecodeOrderBy(JsonReader& reader, const json& query) {
|
||||
OrderByList result;
|
||||
std::vector<json> default_order_by;
|
||||
for (const auto& order_by :
|
||||
reader.OptionalArray("orderBy", query, default_order_by)) {
|
||||
FieldPath path =
|
||||
DecodeFieldReference(reader, reader.RequiredObject("field", order_by));
|
||||
|
||||
std::string direction_string =
|
||||
reader.OptionalString("direction", order_by, "ASCENDING");
|
||||
if (direction_string != "DESCENDING" && direction_string != "ASCENDING") {
|
||||
reader.Fail("'direction' value is invalid: " + direction_string);
|
||||
return {};
|
||||
}
|
||||
|
||||
Direction direction = direction_string == "ASCENDING"
|
||||
? Direction::Ascending
|
||||
: Direction::Descending;
|
||||
|
||||
result = result.push_back(OrderBy(std::move(path), direction));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int32_t DecodeLimit(JsonReader& reader, const json& query) {
|
||||
int32_t limit = Target::kNoLimit;
|
||||
if (query.contains("limit")) {
|
||||
const auto& limit_object = query.at("limit");
|
||||
// "limit" can be encoded as integer or "{"value": integer}".
|
||||
if (limit_object.is_number_integer()) {
|
||||
return limit_object.get<int32_t>();
|
||||
} else if (limit_object.is_object()) {
|
||||
if (limit_object.at("value").is_number_integer()) {
|
||||
return limit_object.at("value").get<int32_t>();
|
||||
}
|
||||
}
|
||||
reader.Fail("'limit' is not encoded as a valid integer");
|
||||
return limit;
|
||||
}
|
||||
|
||||
return limit;
|
||||
}
|
||||
|
||||
LimitType DecodeLimitType(JsonReader& reader, const json& query) {
|
||||
std::string limit_type = reader.OptionalString("limitType", query, "FIRST");
|
||||
|
||||
if (limit_type == "FIRST") {
|
||||
return LimitType::First;
|
||||
} else if (limit_type == "LAST") {
|
||||
return LimitType::Last;
|
||||
} else {
|
||||
reader.Fail("'limitType' is not encoded as a recognizable value");
|
||||
return LimitType::None;
|
||||
}
|
||||
}
|
||||
|
||||
google_type_LatLng DecodeGeoPointValue(JsonReader& reader,
|
||||
const json& geo_json) {
|
||||
google_type_LatLng result{};
|
||||
result.latitude = reader.OptionalDouble("latitude", geo_json, 0.0);
|
||||
result.longitude = reader.OptionalDouble("longitude", geo_json, 0.0);
|
||||
return result;
|
||||
}
|
||||
|
||||
pb_bytes_array_t* DecodeBytesValue(JsonReader& reader,
|
||||
const std::string& bytes_string) {
|
||||
std::string decoded;
|
||||
if (!absl::Base64Unescape(bytes_string, &decoded)) {
|
||||
reader.Fail("Failed to decode bytesValue string into binary form");
|
||||
return {};
|
||||
}
|
||||
return nanopb::MakeBytesArray(decoded);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Mark: JsonReader
|
||||
|
||||
const std::string& JsonReader::RequiredString(const char* name,
|
||||
const json& json_object) {
|
||||
if (json_object.contains(name)) {
|
||||
const json& child = json_object.at(name);
|
||||
if (child.is_string()) {
|
||||
return child.get_ref<const std::string&>();
|
||||
}
|
||||
}
|
||||
|
||||
Fail("'%s' is missing or is not a string", name);
|
||||
return util::EmptyString();
|
||||
}
|
||||
|
||||
const std::string& JsonReader::OptionalString(
|
||||
const char* name,
|
||||
const json& json_object,
|
||||
const std::string& default_value) {
|
||||
if (json_object.contains(name)) {
|
||||
const json& child = json_object.at(name);
|
||||
if (child.is_string()) {
|
||||
return child.get_ref<const std::string&>();
|
||||
}
|
||||
}
|
||||
|
||||
return default_value;
|
||||
}
|
||||
|
||||
const std::vector<json>& JsonReader::RequiredArray(const char* name,
|
||||
const json& json_object) {
|
||||
if (json_object.contains(name)) {
|
||||
const json& child = json_object.at(name);
|
||||
if (child.is_array()) {
|
||||
return child.get_ref<const std::vector<json>&>();
|
||||
}
|
||||
}
|
||||
|
||||
Fail("'%s' is missing or is not an array", name);
|
||||
return EmptyVector<json>();
|
||||
}
|
||||
|
||||
const std::vector<json>& JsonReader::OptionalArray(
|
||||
const char* name,
|
||||
const json& json_object,
|
||||
const std::vector<json>& default_value) {
|
||||
if (!json_object.contains(name)) {
|
||||
return default_value;
|
||||
}
|
||||
|
||||
const json& child = json_object.at(name);
|
||||
if (child.is_array()) {
|
||||
return child.get_ref<const std::vector<json>&>();
|
||||
} else {
|
||||
Fail("'%s' is not an array", name);
|
||||
return EmptyVector<json>();
|
||||
}
|
||||
}
|
||||
|
||||
bool JsonReader::OptionalBool(const char* name,
|
||||
const json& json_object,
|
||||
bool default_value) {
|
||||
return (json_object.contains(name) && json_object.at(name).is_boolean() &&
|
||||
json_object.at(name).get<bool>()) ||
|
||||
default_value;
|
||||
}
|
||||
|
||||
const nlohmann::json& JsonReader::RequiredObject(const char* child_name,
|
||||
const json& json_object) {
|
||||
if (!json_object.contains(child_name)) {
|
||||
Fail("Missing child '%s'", child_name);
|
||||
return json_object;
|
||||
}
|
||||
return json_object.at(child_name);
|
||||
}
|
||||
|
||||
double JsonReader::RequiredDouble(const char* name, const json& json_object) {
|
||||
if (json_object.contains(name)) {
|
||||
double result = DecodeDouble(json_object.at(name));
|
||||
if (ok()) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Fail("'%s' is missing or is not a double", name);
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double JsonReader::OptionalDouble(const char* name,
|
||||
const json& json_object,
|
||||
double default_value) {
|
||||
if (json_object.contains(name)) {
|
||||
double result = DecodeDouble(json_object.at(name));
|
||||
if (ok()) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return default_value;
|
||||
}
|
||||
|
||||
double JsonReader::DecodeDouble(const nlohmann::json& value) {
|
||||
if (value.is_number()) {
|
||||
return value.get<double>();
|
||||
}
|
||||
|
||||
double result = 0;
|
||||
if (value.is_string()) {
|
||||
const auto& s = value.get_ref<const std::string&>();
|
||||
auto ok = absl::SimpleAtod(s, &result);
|
||||
if (!ok) {
|
||||
Fail("Failed to parse into double: " + s);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename IntType>
|
||||
IntType ParseInt(const json& value, JsonReader& reader) {
|
||||
if (value.is_number_integer()) {
|
||||
return value.get<IntType>();
|
||||
}
|
||||
|
||||
IntType result = 0;
|
||||
if (value.is_string()) {
|
||||
const auto& s = value.get_ref<const std::string&>();
|
||||
auto ok = absl::SimpleAtoi<IntType>(s, &result);
|
||||
if (!ok) {
|
||||
reader.Fail("Failed to parse into integer: " + s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
reader.Fail("Only integer and string can be parsed into int type");
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename IntType>
|
||||
IntType JsonReader::RequiredInt(const char* name, const json& json_object) {
|
||||
if (!json_object.contains(name)) {
|
||||
Fail("'%s' is missing or is not a double", name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const json& value = json_object.at(name);
|
||||
return ParseInt<IntType>(value, *this);
|
||||
}
|
||||
|
||||
template <typename IntType>
|
||||
IntType JsonReader::OptionalInt(const char* name,
|
||||
const json& json_object,
|
||||
IntType default_value) {
|
||||
if (!json_object.contains(name)) {
|
||||
return default_value;
|
||||
}
|
||||
|
||||
const json& value = json_object.at(name);
|
||||
return ParseInt<IntType>(value, *this);
|
||||
}
|
||||
|
||||
// Mark: BundleSerializer
|
||||
|
||||
BundleMetadata BundleSerializer::DecodeBundleMetadata(
|
||||
JsonReader& reader, const json& metadata) const {
|
||||
return BundleMetadata(
|
||||
reader.RequiredString("id", metadata),
|
||||
reader.RequiredInt<uint32_t>("version", metadata),
|
||||
DecodeSnapshotVersion(reader,
|
||||
reader.RequiredObject("createTime", metadata)),
|
||||
reader.OptionalInt<uint32_t>("totalDocuments", metadata, 0),
|
||||
reader.OptionalInt<uint64_t>("totalBytes", metadata, 0));
|
||||
}
|
||||
|
||||
NamedQuery BundleSerializer::DecodeNamedQuery(JsonReader& reader,
|
||||
const json& named_query) const {
|
||||
return NamedQuery(
|
||||
reader.RequiredString("name", named_query),
|
||||
DecodeBundledQuery(reader,
|
||||
reader.RequiredObject("bundledQuery", named_query)),
|
||||
DecodeSnapshotVersion(reader,
|
||||
reader.RequiredObject("readTime", named_query)));
|
||||
}
|
||||
|
||||
BundledQuery BundleSerializer::DecodeBundledQuery(
|
||||
JsonReader& reader, const nlohmann::json& query) const {
|
||||
const json& structured_query =
|
||||
reader.RequiredObject("structuredQuery", query);
|
||||
VerifyStructuredQuery(reader, structured_query);
|
||||
if (!reader.ok()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ResourcePath parent =
|
||||
DecodeName(reader, reader.RequiredObject("parent", query));
|
||||
std::string collection_group_string;
|
||||
DecodeCollectionSource(reader, structured_query.at("from"), parent,
|
||||
collection_group_string);
|
||||
std::shared_ptr<std::string> collection_group;
|
||||
if (!collection_group_string.empty()) {
|
||||
collection_group = std::make_shared<std::string>(collection_group_string);
|
||||
}
|
||||
|
||||
auto filters = DecodeWhere(reader, structured_query);
|
||||
auto order_bys = DecodeOrderBy(reader, structured_query);
|
||||
|
||||
auto start_at_bound = DecodeBound(reader, structured_query, "startAt");
|
||||
absl::optional<Bound> start_at;
|
||||
if (start_at_bound.position()->values_count > 0) {
|
||||
start_at = std::move(start_at_bound);
|
||||
}
|
||||
|
||||
auto end_at_bound = DecodeBound(reader, structured_query, "endAt");
|
||||
absl::optional<Bound> end_at;
|
||||
if (end_at_bound.position()->values_count > 0) {
|
||||
end_at = std::move(end_at_bound);
|
||||
}
|
||||
|
||||
int32_t limit = DecodeLimit(reader, structured_query);
|
||||
LimitType limit_type = DecodeLimitType(reader, query);
|
||||
|
||||
return BundledQuery(Target(std::move(parent), std::move(collection_group),
|
||||
std::move(filters), std::move(order_bys), limit,
|
||||
std::move(start_at), std::move(end_at)),
|
||||
limit_type);
|
||||
}
|
||||
|
||||
ResourcePath BundleSerializer::DecodeName(JsonReader& reader,
|
||||
const json& document_name) const {
|
||||
if (!document_name.is_string()) {
|
||||
reader.Fail("Document name is not a string.");
|
||||
return {};
|
||||
}
|
||||
auto path =
|
||||
ResourcePath::FromString(document_name.get_ref<const std::string&>());
|
||||
if (!rpc_serializer_.IsLocalResourceName(path)) {
|
||||
reader.Fail("Resource name is not valid for current instance: " +
|
||||
path.CanonicalString());
|
||||
return {};
|
||||
}
|
||||
return path.PopFirst(5);
|
||||
}
|
||||
|
||||
FilterList BundleSerializer::DecodeWhere(JsonReader& reader,
|
||||
const json& query) const {
|
||||
// Absent 'where' is a valid case.
|
||||
if (!query.contains("where")) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto& where = query.at("where");
|
||||
if (!where.is_object()) {
|
||||
reader.Fail("Query's 'where' clause is not a json object.");
|
||||
return {};
|
||||
}
|
||||
|
||||
FilterList result;
|
||||
if (where.contains("compositeFilter")) {
|
||||
return DecodeCompositeFilter(reader, where.at("compositeFilter"));
|
||||
} else if (where.contains("fieldFilter")) {
|
||||
return result.push_back(DecodeFieldFilter(reader, where.at("fieldFilter")));
|
||||
} else if (where.contains("unaryFilter")) {
|
||||
return result.push_back(DecodeUnaryFilter(reader, where.at("unaryFilter")));
|
||||
} else {
|
||||
reader.Fail("'where' does not have valid filter");
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
Filter BundleSerializer::DecodeFieldFilter(JsonReader& reader,
|
||||
const json& filter) const {
|
||||
FieldPath path =
|
||||
DecodeFieldReference(reader, reader.RequiredObject("field", filter));
|
||||
|
||||
const auto& op_string = reader.RequiredString("op", filter);
|
||||
auto op = DecodeFieldFilterOperator(reader, op_string);
|
||||
|
||||
Message<google_firestore_v1_Value> value =
|
||||
DecodeValue(reader, reader.RequiredObject("value", filter));
|
||||
|
||||
// Return early if !ok(), because `FieldFilter::Create` will abort with
|
||||
// invalid inputs.
|
||||
if (!reader.ok()) {
|
||||
return InvalidFilter();
|
||||
}
|
||||
|
||||
return FieldFilter::Create(path, op, std::move(value));
|
||||
}
|
||||
|
||||
FilterList BundleSerializer::DecodeCompositeFilter(JsonReader& reader,
|
||||
const json& filter) const {
|
||||
if (reader.RequiredString("op", filter) != "AND") {
|
||||
reader.Fail("The SDK only supports composite filters of type 'AND'");
|
||||
return {};
|
||||
}
|
||||
|
||||
auto filters = reader.RequiredArray("filters", filter);
|
||||
FilterList result;
|
||||
for (const auto& f : filters) {
|
||||
result = result.push_back(
|
||||
DecodeFieldFilter(reader, reader.RequiredObject("fieldFilter", f)));
|
||||
if (!reader.ok()) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Bound BundleSerializer::DecodeBound(JsonReader& reader,
|
||||
const json& query,
|
||||
const char* bound_name) const {
|
||||
Bound default_bound = Bound::FromValue(
|
||||
MakeSharedMessage<google_firestore_v1_ArrayValue>({}), false);
|
||||
if (!query.contains(bound_name)) {
|
||||
return default_bound;
|
||||
}
|
||||
|
||||
const json& bound_json = reader.RequiredObject(bound_name, query);
|
||||
std::vector<json> values = reader.RequiredArray("values", bound_json);
|
||||
bool before = reader.OptionalBool("before", bound_json);
|
||||
|
||||
auto positions = MakeSharedMessage<google_firestore_v1_ArrayValue>({});
|
||||
SetRepeatedField(
|
||||
&positions->values, &positions->values_count, values,
|
||||
[&](const json& j) { return *DecodeValue(reader, j).release(); });
|
||||
return Bound::FromValue(std::move(positions), before);
|
||||
}
|
||||
|
||||
Message<google_firestore_v1_Value> BundleSerializer::DecodeValue(
|
||||
JsonReader& reader, const json& value) const {
|
||||
if (!value.is_object()) {
|
||||
reader.Fail("'value' is not encoded as JSON object");
|
||||
return {};
|
||||
}
|
||||
|
||||
Message<google_firestore_v1_Value> result;
|
||||
if (value.contains("nullValue")) {
|
||||
result->which_value_type = google_firestore_v1_Value_null_value_tag;
|
||||
result->null_value = {};
|
||||
} else if (value.contains("booleanValue")) {
|
||||
result->which_value_type = google_firestore_v1_Value_boolean_value_tag;
|
||||
auto val = value.at("booleanValue");
|
||||
if (!val.is_boolean()) {
|
||||
reader.Fail("'booleanValue' is not encoded as a valid boolean");
|
||||
return {};
|
||||
}
|
||||
result->boolean_value = val.get<bool>();
|
||||
} else if (value.contains("integerValue")) {
|
||||
result->which_value_type = google_firestore_v1_Value_integer_value_tag;
|
||||
result->integer_value = reader.RequiredInt<int64_t>("integerValue", value);
|
||||
} else if (value.contains("doubleValue")) {
|
||||
result->which_value_type = google_firestore_v1_Value_double_value_tag;
|
||||
result->double_value = reader.RequiredDouble("doubleValue", value);
|
||||
} else if (value.contains("timestampValue")) {
|
||||
auto val = DecodeTimestamp(reader, value.at("timestampValue"));
|
||||
result->which_value_type = google_firestore_v1_Value_timestamp_value_tag;
|
||||
result->timestamp_value.seconds = val.seconds();
|
||||
result->timestamp_value.nanos = val.nanoseconds();
|
||||
} else if (value.contains("stringValue")) {
|
||||
result->which_value_type = google_firestore_v1_Value_string_value_tag;
|
||||
result->string_value =
|
||||
nanopb::MakeBytesArray(reader.RequiredString("stringValue", value));
|
||||
} else if (value.contains("bytesValue")) {
|
||||
result->which_value_type = google_firestore_v1_Value_bytes_value_tag;
|
||||
result->bytes_value =
|
||||
DecodeBytesValue(reader, reader.RequiredString("bytesValue", value));
|
||||
} else if (value.contains("referenceValue")) {
|
||||
result->which_value_type = google_firestore_v1_Value_reference_value_tag;
|
||||
result->reference_value = DecodeReferenceValue(
|
||||
reader, reader.RequiredString("referenceValue", value));
|
||||
} else if (value.contains("geoPointValue")) {
|
||||
result->which_value_type = google_firestore_v1_Value_geo_point_value_tag;
|
||||
result->geo_point_value =
|
||||
DecodeGeoPointValue(reader, value.at("geoPointValue"));
|
||||
} else if (value.contains("arrayValue")) {
|
||||
result->which_value_type = google_firestore_v1_Value_array_value_tag;
|
||||
result->array_value =
|
||||
*DecodeArrayValue(reader, value.at("arrayValue")).release();
|
||||
} else if (value.contains("mapValue")) {
|
||||
result->which_value_type = google_firestore_v1_Value_map_value_tag;
|
||||
result->map_value = *DecodeMapValue(reader, value.at("mapValue")).release();
|
||||
} else {
|
||||
reader.Fail("Failed to decode value, no type is recognized");
|
||||
return {};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Message<google_firestore_v1_MapValue> BundleSerializer::DecodeMapValue(
|
||||
JsonReader& reader, const json& map_json) const {
|
||||
if (!map_json.is_object() || !map_json.contains("fields")) {
|
||||
reader.Fail("mapValue is not a valid map");
|
||||
return {};
|
||||
}
|
||||
const auto& fields = map_json.at("fields");
|
||||
if (!fields.is_object()) {
|
||||
reader.Fail("mapValue's 'field' is not a valid map");
|
||||
return {};
|
||||
}
|
||||
|
||||
// Fill the map array. Note that we can't use SetRepeatedField here since the
|
||||
// JSON map doesn't currently work with SetRepeatedField.
|
||||
Message<google_firestore_v1_MapValue> map_value;
|
||||
map_value->fields_count = nanopb::CheckedSize(fields.size());
|
||||
map_value->fields =
|
||||
nanopb::MakeArray<google_firestore_v1_MapValue_FieldsEntry>(
|
||||
map_value->fields_count);
|
||||
pb_size_t i = 0;
|
||||
for (const auto& entry : fields.items()) {
|
||||
map_value->fields[i] = {nanopb::MakeBytesArray(entry.key()),
|
||||
*DecodeValue(reader, entry.value()).release()};
|
||||
++i;
|
||||
}
|
||||
return map_value;
|
||||
}
|
||||
|
||||
Message<google_firestore_v1_ArrayValue> BundleSerializer::DecodeArrayValue(
|
||||
JsonReader& reader, const json& array_json) const {
|
||||
const auto& values = reader.RequiredArray("values", array_json);
|
||||
|
||||
Message<google_firestore_v1_ArrayValue> array_value;
|
||||
SetRepeatedField(
|
||||
&array_value->values, &array_value->values_count, values,
|
||||
[&](const json& j) { return *DecodeValue(reader, j).release(); });
|
||||
return array_value;
|
||||
}
|
||||
|
||||
pb_bytes_array_t* BundleSerializer::DecodeReferenceValue(
|
||||
JsonReader& reader, const std::string& ref_string) const {
|
||||
if (reader.ok() && !rpc_serializer_.IsLocalDocumentKey(ref_string)) {
|
||||
reader.Fail(
|
||||
StringFormat("Tried to deserialize an invalid key: %s", ref_string));
|
||||
}
|
||||
|
||||
return nanopb::MakeBytesArray(ref_string);
|
||||
}
|
||||
|
||||
BundledDocumentMetadata BundleSerializer::DecodeDocumentMetadata(
|
||||
JsonReader& reader, const json& document_metadata) const {
|
||||
ResourcePath path =
|
||||
DecodeName(reader, reader.RequiredObject("name", document_metadata));
|
||||
// Return early if !ok(), `DocumentKey` aborts with invalid inputs.
|
||||
if (!reader.ok()) {
|
||||
return {};
|
||||
}
|
||||
DocumentKey key = DocumentKey(path);
|
||||
|
||||
SnapshotVersion read_time = DecodeSnapshotVersion(
|
||||
reader, reader.RequiredObject("readTime", document_metadata));
|
||||
|
||||
bool exists = reader.OptionalBool("exists", document_metadata);
|
||||
|
||||
std::vector<std::string> queries;
|
||||
std::vector<json> default_queries;
|
||||
for (const json& query :
|
||||
reader.OptionalArray("queries", document_metadata, default_queries)) {
|
||||
if (!query.is_string()) {
|
||||
reader.Fail("Query name should be encoded as string");
|
||||
return {};
|
||||
}
|
||||
|
||||
queries.push_back(query.get<std::string>());
|
||||
}
|
||||
|
||||
return BundledDocumentMetadata(std::move(key), read_time, exists,
|
||||
std::move(queries));
|
||||
}
|
||||
|
||||
BundleDocument BundleSerializer::DecodeDocument(JsonReader& reader,
|
||||
const json& document) const {
|
||||
ResourcePath path =
|
||||
DecodeName(reader, reader.RequiredObject("name", document));
|
||||
// Return early if !ok(), `DocumentKey` aborts with invalid inputs.
|
||||
if (!reader.ok()) {
|
||||
return {};
|
||||
}
|
||||
DocumentKey key = DocumentKey(path);
|
||||
|
||||
SnapshotVersion update_time = DecodeSnapshotVersion(
|
||||
reader, reader.RequiredObject("updateTime", document));
|
||||
|
||||
auto map_value = DecodeMapValue(reader, document);
|
||||
|
||||
return BundleDocument(MutableDocument::FoundDocument(
|
||||
std::move(key), update_time,
|
||||
ObjectValue::FromMapValue(std::move(map_value))));
|
||||
}
|
||||
|
||||
} // namespace bundle
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2021 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 FIRESTORE_CORE_SRC_BUNDLE_BUNDLE_SERIALIZER_H_
|
||||
#define FIRESTORE_CORE_SRC_BUNDLE_BUNDLE_SERIALIZER_H_
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Firestore/core/src/bundle/bundle_document.h"
|
||||
#include "Firestore/core/src/bundle/bundle_metadata.h"
|
||||
#include "Firestore/core/src/bundle/bundled_document_metadata.h"
|
||||
#include "Firestore/core/src/bundle/named_query.h"
|
||||
#include "Firestore/core/src/core/core_fwd.h"
|
||||
#include "Firestore/core/src/model/resource_path.h"
|
||||
#include "Firestore/core/src/model/snapshot_version.h"
|
||||
#include "Firestore/core/src/nanopb/message.h"
|
||||
#include "Firestore/core/src/remote/serializer.h"
|
||||
#include "Firestore/core/src/util/read_context.h"
|
||||
#include "Firestore/third_party/nlohmann_json/json.hpp"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace bundle {
|
||||
|
||||
/**
|
||||
* Provides the ability to report failure cases by inheriting `ReadContext`, and
|
||||
* checks and reads json object into specified types.
|
||||
*
|
||||
* `Required*` methods check the existence of the given name and compatibility
|
||||
* of its value (can it be read into the given type?). They fail the reader if
|
||||
* any of the checks fail, otherwise return the read value.
|
||||
*
|
||||
* `Optional*` methods check the existence of the given name, and return a
|
||||
* specified default value if the name does not exist. They then check
|
||||
* compatibility of its value, fail the reader if that check fails, or return
|
||||
* the read value if it succeeds.
|
||||
*/
|
||||
class JsonReader : public util::ReadContext {
|
||||
public:
|
||||
const std::string& RequiredString(const char* name,
|
||||
const nlohmann::json& json_object);
|
||||
const std::string& OptionalString(const char* name,
|
||||
const nlohmann::json& json_object,
|
||||
const std::string& default_value);
|
||||
|
||||
const std::vector<nlohmann::json>& RequiredArray(
|
||||
const char* name, const nlohmann::json& json_object);
|
||||
const std::vector<nlohmann::json>& OptionalArray(
|
||||
const char* name,
|
||||
const nlohmann::json& json_object,
|
||||
const std::vector<nlohmann::json>& default_value);
|
||||
const nlohmann::json& RequiredObject(const char* child_name,
|
||||
const nlohmann::json& json_object);
|
||||
|
||||
double RequiredDouble(const char* name, const nlohmann::json& json_object);
|
||||
double OptionalDouble(const char* name,
|
||||
const nlohmann::json& json_object,
|
||||
double default_value = 0);
|
||||
|
||||
template <typename IntType>
|
||||
IntType RequiredInt(const char* name, const nlohmann::json& json_object);
|
||||
|
||||
template <typename IntType>
|
||||
IntType OptionalInt(const char* name,
|
||||
const nlohmann::json& json_object,
|
||||
IntType default_value);
|
||||
|
||||
static bool OptionalBool(const char* name,
|
||||
const nlohmann::json& json_object,
|
||||
bool default_value = false);
|
||||
|
||||
private:
|
||||
double DecodeDouble(const nlohmann::json& value);
|
||||
};
|
||||
|
||||
/** A JSON serializer to deserialize Firestore Bundles. */
|
||||
class BundleSerializer {
|
||||
public:
|
||||
explicit BundleSerializer(remote::Serializer serializer)
|
||||
: rpc_serializer_(std::move(serializer)) {
|
||||
}
|
||||
BundleMetadata DecodeBundleMetadata(JsonReader& reader,
|
||||
const nlohmann::json& metadata) const;
|
||||
|
||||
NamedQuery DecodeNamedQuery(JsonReader& reader,
|
||||
const nlohmann::json& named_query) const;
|
||||
|
||||
BundledDocumentMetadata DecodeDocumentMetadata(
|
||||
JsonReader& reader, const nlohmann::json& document_metadata) const;
|
||||
|
||||
BundleDocument DecodeDocument(JsonReader& reader,
|
||||
const nlohmann::json& document) const;
|
||||
|
||||
private:
|
||||
BundledQuery DecodeBundledQuery(JsonReader& reader,
|
||||
const nlohmann::json& query) const;
|
||||
core::FilterList DecodeWhere(JsonReader& reader,
|
||||
const nlohmann::json& query) const;
|
||||
core::Filter DecodeFieldFilter(JsonReader& reader,
|
||||
const nlohmann::json& filter) const;
|
||||
core::FilterList DecodeCompositeFilter(JsonReader& reader,
|
||||
const nlohmann::json& filter) const;
|
||||
nanopb::Message<google_firestore_v1_Value> DecodeValue(
|
||||
JsonReader& reader, const nlohmann::json& value) const;
|
||||
core::Bound DecodeBound(JsonReader& reader,
|
||||
const nlohmann::json& query,
|
||||
const char* bound_name) const;
|
||||
model::ResourcePath DecodeName(JsonReader& reader,
|
||||
const nlohmann::json& name) const;
|
||||
nanopb::Message<google_firestore_v1_ArrayValue> DecodeArrayValue(
|
||||
JsonReader& reader, const nlohmann::json& array_json) const;
|
||||
nanopb::Message<google_firestore_v1_MapValue> DecodeMapValue(
|
||||
JsonReader& reader, const nlohmann::json& map_json) const;
|
||||
pb_bytes_array_t* DecodeReferenceValue(JsonReader& reader,
|
||||
const std::string& ref_string) const;
|
||||
|
||||
remote::Serializer rpc_serializer_;
|
||||
};
|
||||
|
||||
} // namespace bundle
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_BUNDLE_BUNDLE_SERIALIZER_H_
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2021 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 FIRESTORE_CORE_SRC_BUNDLE_BUNDLED_DOCUMENT_METADATA_H_
|
||||
#define FIRESTORE_CORE_SRC_BUNDLE_BUNDLED_DOCUMENT_METADATA_H_
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Firestore/core/src/bundle/bundle_element.h"
|
||||
#include "Firestore/core/src/model/document_key.h"
|
||||
#include "Firestore/core/src/model/snapshot_version.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace bundle {
|
||||
|
||||
/** Metadata describing a Firestore document saved in the bundle. */
|
||||
class BundledDocumentMetadata : public BundleElement {
|
||||
public:
|
||||
BundledDocumentMetadata() = default;
|
||||
|
||||
BundledDocumentMetadata(model::DocumentKey key,
|
||||
model::SnapshotVersion read_time,
|
||||
bool exists,
|
||||
std::vector<std::string> queries)
|
||||
: key_(std::move(key)),
|
||||
read_time_(read_time),
|
||||
exists_(exists),
|
||||
queries_(std::move(queries)) {
|
||||
}
|
||||
|
||||
Type element_type() const override {
|
||||
return Type::DocumentMetadata;
|
||||
}
|
||||
|
||||
/** Returns the document key of a bundled document. */
|
||||
const model::DocumentKey& key() const {
|
||||
return key_;
|
||||
}
|
||||
|
||||
/** Returns the snapshot version of the document data bundled. */
|
||||
const model::SnapshotVersion& read_time() const {
|
||||
return read_time_;
|
||||
}
|
||||
|
||||
/** Returns whether the document exists. */
|
||||
bool exists() const {
|
||||
return exists_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the names of the queries in this bundle that this document matches
|
||||
* to.
|
||||
*/
|
||||
const std::vector<std::string>& queries() const {
|
||||
return queries_;
|
||||
}
|
||||
|
||||
private:
|
||||
model::DocumentKey key_;
|
||||
model::SnapshotVersion read_time_;
|
||||
bool exists_ = false;
|
||||
std::vector<std::string> queries_;
|
||||
};
|
||||
|
||||
inline bool operator==(const BundledDocumentMetadata& lhs,
|
||||
const BundledDocumentMetadata& rhs) {
|
||||
return lhs.key() == rhs.key() && lhs.exists() == rhs.exists() &&
|
||||
lhs.read_time() == rhs.read_time() && lhs.queries() == rhs.queries();
|
||||
}
|
||||
|
||||
inline bool operator!=(const BundledDocumentMetadata& lhs,
|
||||
const BundledDocumentMetadata& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
} // namespace bundle
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_BUNDLE_BUNDLED_DOCUMENT_METADATA_H_
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2021 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 FIRESTORE_CORE_SRC_BUNDLE_BUNDLED_QUERY_H_
|
||||
#define FIRESTORE_CORE_SRC_BUNDLE_BUNDLED_QUERY_H_
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/core/query.h"
|
||||
#include "Firestore/core/src/core/target.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace bundle {
|
||||
|
||||
/**
|
||||
* A bundled query represents a query target and its limit type.
|
||||
*/
|
||||
class BundledQuery {
|
||||
public:
|
||||
BundledQuery() = default;
|
||||
|
||||
BundledQuery(core::Target target, core::LimitType limit_type)
|
||||
: target_(std::move(target)), limit_type_(limit_type) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The target that represents the user-issued query when building
|
||||
* bundles. Client side transformations are not performed for client-specific
|
||||
* features: order by constraints are not inverted for limit to last queries,
|
||||
* for example.
|
||||
*/
|
||||
const core::Target& target() const {
|
||||
return target_;
|
||||
}
|
||||
|
||||
/** @return The user provided limit type. */
|
||||
core::LimitType limit_type() const {
|
||||
return limit_type_;
|
||||
}
|
||||
|
||||
private:
|
||||
core::Target target_;
|
||||
core::LimitType limit_type_;
|
||||
};
|
||||
|
||||
inline bool operator==(const BundledQuery& lhs, const BundledQuery& rhs) {
|
||||
return lhs.target() == rhs.target() && lhs.limit_type() == rhs.limit_type();
|
||||
}
|
||||
|
||||
inline bool operator!=(const BundledQuery& lhs, const BundledQuery& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
} // namespace bundle
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_BUNDLE_BUNDLED_QUERY_H_
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2021 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 FIRESTORE_CORE_SRC_BUNDLE_NAMED_QUERY_H_
|
||||
#define FIRESTORE_CORE_SRC_BUNDLE_NAMED_QUERY_H_
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/bundle/bundle_element.h"
|
||||
#include "Firestore/core/src/bundle/bundled_query.h"
|
||||
#include "Firestore/core/src/model/snapshot_version.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace bundle {
|
||||
|
||||
/**
|
||||
* Represents a named query saved by the SDK in its local storage.
|
||||
*/
|
||||
class NamedQuery : public BundleElement {
|
||||
public:
|
||||
NamedQuery() = default;
|
||||
|
||||
NamedQuery(std::string query_name,
|
||||
BundledQuery bundled_query,
|
||||
model::SnapshotVersion read_time)
|
||||
: query_name_(std::move(query_name)),
|
||||
bundled_query_(std::move(bundled_query)),
|
||||
read_time_(read_time) {
|
||||
}
|
||||
|
||||
Type element_type() const override {
|
||||
return Type::NamedQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The name of the query.
|
||||
*/
|
||||
const std::string& query_name() const {
|
||||
return query_name_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The underlying query associated with the given name.
|
||||
*/
|
||||
const BundledQuery& bundled_query() const {
|
||||
return bundled_query_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The time at which the results for this query were read.
|
||||
*/
|
||||
model::SnapshotVersion read_time() const {
|
||||
return read_time_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string query_name_;
|
||||
BundledQuery bundled_query_;
|
||||
model::SnapshotVersion read_time_;
|
||||
};
|
||||
|
||||
inline bool operator==(const NamedQuery& lhs, const NamedQuery& rhs) {
|
||||
return lhs.query_name() == rhs.query_name() &&
|
||||
lhs.read_time() == rhs.read_time() &&
|
||||
lhs.bundled_query() == rhs.bundled_query();
|
||||
}
|
||||
|
||||
inline bool operator!=(const NamedQuery& lhs, const NamedQuery& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
} // namespace bundle
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_BUNDLE_NAMED_QUERY_H_
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/core/array_contains_any_filter.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/model/document.h"
|
||||
#include "Firestore/core/src/model/value_util.h"
|
||||
#include "Firestore/core/src/util/hard_assert.h"
|
||||
#include "absl/algorithm/container.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
using model::Contains;
|
||||
using model::Document;
|
||||
using model::FieldPath;
|
||||
using model::IsArray;
|
||||
using nanopb::SharedMessage;
|
||||
|
||||
using Operator = Filter::Operator;
|
||||
|
||||
class ArrayContainsAnyFilter::Rep : public FieldFilter::Rep {
|
||||
public:
|
||||
Rep(FieldPath field, SharedMessage<google_firestore_v1_Value> value)
|
||||
: FieldFilter::Rep(
|
||||
std::move(field), Operator::ArrayContainsAny, std::move(value)) {
|
||||
HARD_ASSERT(IsArray(this->value()),
|
||||
"ArrayContainsAnyFilter expects an ArrayValue");
|
||||
}
|
||||
|
||||
Type type() const override {
|
||||
return Type::kArrayContainsAnyFilter;
|
||||
}
|
||||
|
||||
bool Matches(const model::Document& doc) const override;
|
||||
};
|
||||
|
||||
ArrayContainsAnyFilter::ArrayContainsAnyFilter(
|
||||
const model::FieldPath& field,
|
||||
SharedMessage<google_firestore_v1_Value> value)
|
||||
: FieldFilter(std::make_shared<Rep>(field, std::move(value))) {
|
||||
}
|
||||
|
||||
bool ArrayContainsAnyFilter::Rep::Matches(const Document& doc) const {
|
||||
const google_firestore_v1_ArrayValue& array_value = value().array_value;
|
||||
absl::optional<google_firestore_v1_Value> maybe_lhs = doc->field(field());
|
||||
if (!maybe_lhs) return false;
|
||||
|
||||
const google_firestore_v1_Value& lhs = *maybe_lhs;
|
||||
if (!IsArray(lhs)) return false;
|
||||
|
||||
for (pb_size_t i = 0; i < lhs.array_value.values_count; ++i) {
|
||||
if (Contains(array_value, lhs.array_value.values[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_CORE_ARRAY_CONTAINS_ANY_FILTER_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_ARRAY_CONTAINS_ANY_FILTER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h"
|
||||
#include "Firestore/core/src/core/field_filter.h"
|
||||
#include "Firestore/core/src/nanopb/message.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
|
||||
namespace model {
|
||||
class FieldPath;
|
||||
class FieldValue;
|
||||
} // namespace model
|
||||
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* A Filter that implements the array-contains-any operator.
|
||||
*/
|
||||
class ArrayContainsAnyFilter : public FieldFilter {
|
||||
public:
|
||||
/** Creates a new array-contains-any filter. Takes ownership of `value`. */
|
||||
ArrayContainsAnyFilter(
|
||||
const model::FieldPath& field,
|
||||
nanopb::SharedMessage<google_firestore_v1_Value> value);
|
||||
|
||||
private:
|
||||
class Rep;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_ARRAY_CONTAINS_ANY_FILTER_H_
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/core/array_contains_filter.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/model/document.h"
|
||||
#include "Firestore/core/src/model/value_util.h"
|
||||
#include "absl/algorithm/container.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
using model::Contains;
|
||||
using model::Document;
|
||||
using model::FieldPath;
|
||||
using model::IsArray;
|
||||
using nanopb::SharedMessage;
|
||||
using Operator = Filter::Operator;
|
||||
|
||||
class ArrayContainsFilter::Rep : public FieldFilter::Rep {
|
||||
public:
|
||||
Rep(FieldPath field, SharedMessage<google_firestore_v1_Value> value)
|
||||
: FieldFilter::Rep(
|
||||
std::move(field), Operator::ArrayContains, std::move(value)) {
|
||||
}
|
||||
|
||||
Type type() const override {
|
||||
return Type::kArrayContainsFilter;
|
||||
}
|
||||
|
||||
bool Matches(const model::Document& doc) const override;
|
||||
};
|
||||
|
||||
ArrayContainsFilter::ArrayContainsFilter(
|
||||
const model::FieldPath& field,
|
||||
SharedMessage<google_firestore_v1_Value> value)
|
||||
: FieldFilter(std::make_shared<const Rep>(field, std::move(value))) {
|
||||
}
|
||||
|
||||
bool ArrayContainsFilter::Rep::Matches(const Document& doc) const {
|
||||
absl::optional<google_firestore_v1_Value> maybe_lhs = doc->field(field());
|
||||
if (!maybe_lhs) return false;
|
||||
|
||||
const google_firestore_v1_Value& lhs = *maybe_lhs;
|
||||
if (!IsArray(lhs)) return false;
|
||||
|
||||
const google_firestore_v1_ArrayValue& contents = lhs.array_value;
|
||||
return Contains(contents, value());
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_CORE_ARRAY_CONTAINS_FILTER_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_ARRAY_CONTAINS_FILTER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h"
|
||||
#include "Firestore/core/src/core/field_filter.h"
|
||||
#include "Firestore/core/src/nanopb/message.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
|
||||
namespace model {
|
||||
class FieldPath;
|
||||
class FieldValue;
|
||||
} // namespace model
|
||||
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* A Filter that implements the array-contains operator.
|
||||
*/
|
||||
class ArrayContainsFilter : public FieldFilter {
|
||||
public:
|
||||
/** Creates a new array-contains filter. Takes ownership of `value`. */
|
||||
ArrayContainsFilter(const model::FieldPath& field,
|
||||
nanopb::SharedMessage<google_firestore_v1_Value> value);
|
||||
|
||||
private:
|
||||
class Rep;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_ARRAY_CONTAINS_FILTER_H_
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/core/bound.h"
|
||||
|
||||
#include <ostream>
|
||||
|
||||
#include "Firestore/core/src/core/order_by.h"
|
||||
#include "Firestore/core/src/immutable/append_only_list.h"
|
||||
#include "Firestore/core/src/model/document.h"
|
||||
#include "Firestore/core/src/model/document_key.h"
|
||||
#include "Firestore/core/src/model/value_util.h"
|
||||
#include "Firestore/core/src/nanopb/nanopb_util.h"
|
||||
#include "Firestore/core/src/util/hashing.h"
|
||||
#include "Firestore/core/src/util/to_string.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
using model::Compare;
|
||||
using model::DocumentKey;
|
||||
using model::FieldPath;
|
||||
using model::GetTypeOrder;
|
||||
using model::TypeOrder;
|
||||
using nanopb::SharedMessage;
|
||||
using util::ComparisonResult;
|
||||
|
||||
Bound Bound::FromValue(SharedMessage<google_firestore_v1_ArrayValue> position,
|
||||
bool is_before) {
|
||||
model::SortFields(*position);
|
||||
return Bound(std::move(position), is_before);
|
||||
}
|
||||
|
||||
bool Bound::SortsBeforeDocument(const OrderByList& order_by,
|
||||
const model::Document& document) const {
|
||||
HARD_ASSERT(position_->values_count <= order_by.size(),
|
||||
"Bound has more components than the provided order by.");
|
||||
|
||||
ComparisonResult result = ComparisonResult::Same;
|
||||
for (size_t idx = 0; idx < position_->values_count; ++idx) {
|
||||
const google_firestore_v1_Value& field_value = position_->values[idx];
|
||||
const OrderBy& ordering_component = order_by[idx];
|
||||
|
||||
ComparisonResult comparison;
|
||||
if (ordering_component.field() == FieldPath::KeyFieldPath()) {
|
||||
HARD_ASSERT(
|
||||
GetTypeOrder(field_value) == TypeOrder ::kReference,
|
||||
"Bound has a non-key value where the key path is being used %s",
|
||||
field_value.ToString());
|
||||
auto key = DocumentKey::FromName(
|
||||
nanopb::MakeString(field_value.reference_value));
|
||||
comparison = key.CompareTo(document->key());
|
||||
|
||||
} else {
|
||||
absl::optional<google_firestore_v1_Value> doc_value =
|
||||
document->field(ordering_component.field());
|
||||
HARD_ASSERT(
|
||||
doc_value.has_value(),
|
||||
"Field should exist since document matched the orderBy already.");
|
||||
comparison = Compare(field_value, *doc_value);
|
||||
}
|
||||
|
||||
comparison = ordering_component.direction().ApplyTo(comparison);
|
||||
if (!util::Same(comparison)) {
|
||||
result = comparison;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return before_ ? result <= ComparisonResult::Same
|
||||
: result < ComparisonResult::Same;
|
||||
}
|
||||
|
||||
std::string Bound::CanonicalId() const {
|
||||
std::string result = before_ ? "b:" : "a:";
|
||||
for (pb_size_t i = 0; i < position_->values_count; ++i) {
|
||||
result.append(model::CanonicalId(position_->values[i]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string Bound::ToString() const {
|
||||
return util::StringFormat("Bound(position=%s, before=%s)",
|
||||
model::CanonicalId(*position_),
|
||||
util::ToString(before_));
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Bound& bound) {
|
||||
return os << bound.ToString();
|
||||
}
|
||||
|
||||
bool operator==(const Bound& lhs, const Bound& rhs) {
|
||||
return *lhs.position() == *rhs.position() && lhs.before() == rhs.before();
|
||||
}
|
||||
|
||||
size_t Bound::Hash() const {
|
||||
return util::Hash(model::CanonicalId(*position_), before_);
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_CORE_BOUND_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_BOUND_H_
|
||||
|
||||
#include <iosfwd>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h"
|
||||
#include "Firestore/core/src/core/core_fwd.h"
|
||||
#include "Firestore/core/src/model/model_fwd.h"
|
||||
#include "Firestore/core/src/model/value_util.h"
|
||||
#include "Firestore/core/src/nanopb/message.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* Bound represents the starting or ending position in query results.
|
||||
*
|
||||
* The bound is specified with components representing a position in the results
|
||||
* and whether it's just before or just after the position (relative to whatever
|
||||
* the query order is).
|
||||
*
|
||||
* The position represents a logical index position for a query. It's a prefix
|
||||
* of values for the (potentially implicit) order by clauses of a query.
|
||||
*
|
||||
* Bound provides a function to determine whether a document comes before or
|
||||
* after a bound. This is influenced by whether the position is just before or
|
||||
* just after the provided values.
|
||||
*/
|
||||
class Bound {
|
||||
public:
|
||||
/**
|
||||
* Creates a new bound.
|
||||
*
|
||||
* @param position The position relative to the sort order.
|
||||
* @param is_before Whether this bound is just before or just after the
|
||||
* position.
|
||||
*/
|
||||
static Bound FromValue(
|
||||
nanopb::SharedMessage<google_firestore_v1_ArrayValue> position,
|
||||
bool is_before);
|
||||
|
||||
/**
|
||||
* The index position of this bound represented as an array of field values.
|
||||
*/
|
||||
const nanopb::SharedMessage<google_firestore_v1_ArrayValue> position() const {
|
||||
return position_;
|
||||
}
|
||||
|
||||
/** Whether this bound is just before or just after the provided position */
|
||||
bool before() const {
|
||||
return before_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given document comes before this bound using the
|
||||
* provided sort order.
|
||||
*/
|
||||
bool SortsBeforeDocument(const OrderByList& order_by,
|
||||
const model::Document& document) const;
|
||||
|
||||
std::string CanonicalId() const;
|
||||
|
||||
std::string ToString() const;
|
||||
|
||||
size_t Hash() const;
|
||||
|
||||
private:
|
||||
Bound(nanopb::SharedMessage<google_firestore_v1_ArrayValue> position,
|
||||
bool is_before)
|
||||
: position_{std::move(position)}, before_(is_before) {
|
||||
}
|
||||
|
||||
nanopb::SharedMessage<google_firestore_v1_ArrayValue> position_;
|
||||
bool before_;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Bound& bound);
|
||||
|
||||
bool operator==(const Bound& lhs, const Bound& rhs);
|
||||
|
||||
inline bool operator!=(const Bound& lhs, const Bound& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_BOUND_H_
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2020 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 FIRESTORE_CORE_SRC_CORE_CORE_FWD_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_CORE_FWD_H_
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
|
||||
namespace immutable {
|
||||
template <typename T>
|
||||
class AppendOnlyList;
|
||||
} // namespace immutable
|
||||
|
||||
namespace util {
|
||||
class Status;
|
||||
|
||||
struct Empty;
|
||||
|
||||
using StatusCallback = std::function<void(Status)>;
|
||||
} // namespace util
|
||||
|
||||
namespace core {
|
||||
|
||||
class Bound;
|
||||
class DatabaseInfo;
|
||||
class Direction;
|
||||
class EventManager;
|
||||
class FieldFilter;
|
||||
class Filter;
|
||||
class FirestoreClient;
|
||||
class ListenOptions;
|
||||
class OrderBy;
|
||||
class ParsedSetData;
|
||||
class ParsedUpdateData;
|
||||
class Query;
|
||||
class QueryListener;
|
||||
class SyncEngine;
|
||||
class SyncEngineCallback;
|
||||
class Target;
|
||||
class TargetIdGenerator;
|
||||
class Transaction;
|
||||
class ViewDocumentChanges;
|
||||
class ViewChange;
|
||||
class View;
|
||||
class DocumentViewChange;
|
||||
class DocumentViewChangeSet;
|
||||
class ViewSnapshot;
|
||||
|
||||
template <typename T>
|
||||
class AsyncEventListener;
|
||||
|
||||
template <typename T>
|
||||
class EventListener;
|
||||
|
||||
using CollectionGroupId = std::shared_ptr<const std::string>;
|
||||
|
||||
using FilterList = immutable::AppendOnlyList<Filter>;
|
||||
|
||||
using OrderByList = immutable::AppendOnlyList<OrderBy>;
|
||||
|
||||
using TransactionResultCallback = util::StatusCallback;
|
||||
|
||||
using TransactionUpdateCallback = std::function<void(
|
||||
std::shared_ptr<Transaction>, TransactionResultCallback)>;
|
||||
|
||||
using ViewSnapshotListener = std::unique_ptr<EventListener<ViewSnapshot>>;
|
||||
|
||||
using ViewSnapshotSharedListener = std::shared_ptr<EventListener<ViewSnapshot>>;
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_CORE_FWD_H_
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2018 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/core/database_info.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
DatabaseInfo::DatabaseInfo(model::DatabaseId database_id,
|
||||
std::string persistence_key,
|
||||
std::string host,
|
||||
bool ssl_enabled)
|
||||
: database_id_{std::move(database_id)},
|
||||
persistence_key_{std::move(persistence_key)},
|
||||
host_{std::move(host)},
|
||||
ssl_enabled_{ssl_enabled} {
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2018 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_CORE_DATABASE_INFO_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_DATABASE_INFO_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "Firestore/core/src/model/database_id.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
/** DatabaseInfo contains data about the database. */
|
||||
class DatabaseInfo {
|
||||
public:
|
||||
/**
|
||||
* Creates a new DatabaseInfo.
|
||||
*
|
||||
* @param database_id The project/database to use.
|
||||
* @param persistence_key A unique identifier for this Firestore's local
|
||||
* storage. Usually derived from -[FIRApp appName].
|
||||
* @param host The hostname of the Firestore backend.
|
||||
* @param ssl_enabled Whether to use SSL when connecting.
|
||||
*/
|
||||
DatabaseInfo(model::DatabaseId database_id,
|
||||
std::string persistence_key,
|
||||
std::string host,
|
||||
bool ssl_enabled);
|
||||
|
||||
DatabaseInfo() = default;
|
||||
|
||||
const model::DatabaseId& database_id() const {
|
||||
return database_id_;
|
||||
}
|
||||
|
||||
const std::string& persistence_key() const {
|
||||
return persistence_key_;
|
||||
}
|
||||
|
||||
const std::string& host() const {
|
||||
return host_;
|
||||
}
|
||||
|
||||
bool ssl_enabled() const {
|
||||
return ssl_enabled_;
|
||||
}
|
||||
|
||||
private:
|
||||
model::DatabaseId database_id_;
|
||||
std::string persistence_key_;
|
||||
std::string host_;
|
||||
bool ssl_enabled_ = false;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_DATABASE_INFO_H_
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/core/direction.h"
|
||||
|
||||
#include <ostream>
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
const Direction Direction::Ascending(Direction::AscendingModifier);
|
||||
const Direction Direction::Descending(Direction::DescendingModifier);
|
||||
|
||||
std::string Direction::CanonicalId() const {
|
||||
return comparison_modifier_ == AscendingModifier ? "asc" : "desc";
|
||||
}
|
||||
|
||||
util::ComparisonResult Direction::ApplyTo(util::ComparisonResult result) const {
|
||||
if (comparison_modifier_ == AscendingModifier) {
|
||||
return result;
|
||||
} else {
|
||||
return util::ReverseOrder(result);
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Direction& direction) {
|
||||
return os << direction.CanonicalId();
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_CORE_DIRECTION_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_DIRECTION_H_
|
||||
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
|
||||
#include "Firestore/core/src/util/comparison.h"
|
||||
#include "absl/base/attributes.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
/** Interface used for all query orderings. All Directions are immutable. */
|
||||
class Direction {
|
||||
public:
|
||||
ABSL_CONST_INIT static const Direction Ascending;
|
||||
ABSL_CONST_INIT static const Direction Descending;
|
||||
|
||||
/**
|
||||
* Creates a Direction from a boolean. This is useful only because the
|
||||
* public Objective-C API uses it.
|
||||
*/
|
||||
static const Direction& FromDescending(bool descending) {
|
||||
return descending ? Descending : Ascending;
|
||||
}
|
||||
|
||||
Direction() = default;
|
||||
|
||||
/**
|
||||
* Changes the direction of the given ComparisonResult if the direction is
|
||||
* Descending.
|
||||
*/
|
||||
util::ComparisonResult ApplyTo(util::ComparisonResult) const;
|
||||
|
||||
int comparison_modifier() const {
|
||||
return comparison_modifier_;
|
||||
}
|
||||
|
||||
std::string CanonicalId() const;
|
||||
|
||||
private:
|
||||
enum {
|
||||
AscendingModifier = 1,
|
||||
DescendingModifier = -1,
|
||||
};
|
||||
|
||||
constexpr explicit Direction(int comparison_modifier)
|
||||
: comparison_modifier_(comparison_modifier) {
|
||||
}
|
||||
int comparison_modifier_ = AscendingModifier;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Direction& direction);
|
||||
|
||||
inline bool operator==(const Direction& lhs, const Direction& rhs) {
|
||||
return lhs.comparison_modifier() == rhs.comparison_modifier();
|
||||
}
|
||||
|
||||
inline bool operator!=(const Direction& lhs, const Direction& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_DIRECTION_H_
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_CORE_EVENT_LISTENER_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_EVENT_LISTENER_H_
|
||||
|
||||
#include <memory>
|
||||
#include <mutex> // NOLINT(build/c++11)
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/util/executor.h"
|
||||
#include "Firestore/core/src/util/status_fwd.h"
|
||||
#include "Firestore/core/src/util/statusor.h"
|
||||
#include "absl/memory/memory.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* A general interface for listening to events internally.
|
||||
*/
|
||||
template <typename T>
|
||||
class EventListener {
|
||||
public:
|
||||
static std::unique_ptr<EventListener<T>> Create(
|
||||
util::StatusOrCallback<T> callback);
|
||||
|
||||
virtual ~EventListener() = default;
|
||||
|
||||
/**
|
||||
* OnEvent will be called with the new value or the error if an error
|
||||
* occurred.
|
||||
*
|
||||
* @param maybe_value The value of the event or the error.
|
||||
*/
|
||||
virtual void OnEvent(util::StatusOr<T> maybe_value) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* A wrapper around another EventListener that dispatches events asynchronously.
|
||||
*/
|
||||
template <typename T>
|
||||
class AsyncEventListener
|
||||
: public EventListener<T>,
|
||||
public std::enable_shared_from_this<AsyncEventListener<T>> {
|
||||
public:
|
||||
using DelegateListener = std::unique_ptr<EventListener<T>>;
|
||||
|
||||
AsyncEventListener(const std::shared_ptr<util::Executor>& executor,
|
||||
DelegateListener&& delegate)
|
||||
: executor_(executor), delegate_(std::move(delegate)) {
|
||||
}
|
||||
|
||||
static std::shared_ptr<AsyncEventListener<T>> Create(
|
||||
std::shared_ptr<util::Executor> executor, DelegateListener&& delegate);
|
||||
|
||||
static std::shared_ptr<AsyncEventListener<T>> Create(
|
||||
std::shared_ptr<util::Executor> executor, EventListener<T>&& delegate) {
|
||||
return Create(executor,
|
||||
absl::make_unique<EventListener>(std::move(delegate)));
|
||||
}
|
||||
|
||||
void OnEvent(util::StatusOr<T> maybe_value) override;
|
||||
|
||||
/**
|
||||
* Synchronously mutes the listener and raises no further events. This method
|
||||
* is thread safe and can be called from any queue.
|
||||
*/
|
||||
void Mute();
|
||||
|
||||
private:
|
||||
// PORTING NOTE: Android uses a volatile here but that's not enough in C++.
|
||||
//
|
||||
// In C++, the user can call `ListenerRegistration::Remove` (which calls
|
||||
// `Mute`) and then immediately delete the state backing the listener. Using
|
||||
// a mutex here instead of an atomic ensures that `Mute` won't return until
|
||||
// it's safe to delete the state backing a listener. In Java this is safe
|
||||
// because the state backing the listener is garbage collected so it doesn't
|
||||
// matter if the mute is concurrent with a callback.
|
||||
//
|
||||
// Use a recursive mutex instead of `std::mutex` to avoid deadlock in the case
|
||||
// where a user calls `Remove` from within a callback on that listener.
|
||||
std::recursive_mutex mutex_;
|
||||
bool muted_ = false;
|
||||
std::shared_ptr<util::Executor> executor_;
|
||||
DelegateListener delegate_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
std::unique_ptr<EventListener<T>> EventListener<T>::Create(
|
||||
util::StatusOrCallback<T> callback) {
|
||||
class CallbackEventListener : public EventListener<T> {
|
||||
public:
|
||||
explicit CallbackEventListener(util::StatusOrCallback<T>&& callback)
|
||||
: callback_(std::move(callback)) {
|
||||
}
|
||||
|
||||
void OnEvent(util::StatusOr<T> maybe_value) override {
|
||||
callback_(std::move(maybe_value));
|
||||
}
|
||||
|
||||
private:
|
||||
util::StatusOrCallback<T> callback_;
|
||||
};
|
||||
|
||||
return absl::make_unique<CallbackEventListener>(std::move(callback));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::shared_ptr<AsyncEventListener<T>> AsyncEventListener<T>::Create(
|
||||
std::shared_ptr<util::Executor> executor, DelegateListener&& delegate) {
|
||||
return std::make_shared<AsyncEventListener<T>>(executor, std::move(delegate));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void AsyncEventListener<T>::Mute() {
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
muted_ = true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void AsyncEventListener<T>::OnEvent(util::StatusOr<T> maybe_value) {
|
||||
// Retain a strong reference to this. If the EventManager is sending an error
|
||||
// it will immediately clear its strong reference to this after posting the
|
||||
// event. The strong reference here allows the AsyncEventListener to survive
|
||||
// until the executor gets around to calling.
|
||||
std::shared_ptr<AsyncEventListener<T>> shared_this = this->shared_from_this();
|
||||
|
||||
executor_->Execute([shared_this, maybe_value]() {
|
||||
std::lock_guard<std::recursive_mutex> lock(shared_this->mutex_);
|
||||
if (!shared_this->muted_) {
|
||||
shared_this->delegate_->OnEvent(std::move(maybe_value));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_EVENT_LISTENER_H_
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright 2019 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 "Firestore/core/src/core/event_manager.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/core/query_listener.h"
|
||||
#include "Firestore/core/src/core/sync_engine.h"
|
||||
#include "Firestore/core/src/util/hard_assert.h"
|
||||
#include "absl/algorithm/container.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
using util::Empty;
|
||||
|
||||
EventManager::EventManager(QueryEventSource* query_event_source)
|
||||
: query_event_source_(query_event_source) {
|
||||
query_event_source->SetCallback(this);
|
||||
}
|
||||
|
||||
model::TargetId EventManager::AddQueryListener(
|
||||
std::shared_ptr<core::QueryListener> listener) {
|
||||
const Query& query = listener->query();
|
||||
|
||||
auto inserted = queries_.emplace(query, QueryListenersInfo{});
|
||||
bool first_listen = inserted.second;
|
||||
QueryListenersInfo& query_info = inserted.first->second;
|
||||
|
||||
query_info.listeners.push_back(listener);
|
||||
|
||||
bool raised_event = listener->OnOnlineStateChanged(online_state_);
|
||||
HARD_ASSERT(!raised_event,
|
||||
"OnOnlineStateChanged() shouldn't raise an event "
|
||||
"for brand-new listeners.");
|
||||
|
||||
if (query_info.view_snapshot().has_value()) {
|
||||
raised_event = listener->OnViewSnapshot(query_info.view_snapshot().value());
|
||||
if (raised_event) {
|
||||
RaiseSnapshotsInSyncEvent();
|
||||
}
|
||||
}
|
||||
|
||||
if (first_listen) {
|
||||
query_info.target_id = query_event_source_->Listen(query);
|
||||
}
|
||||
return query_info.target_id;
|
||||
}
|
||||
|
||||
void EventManager::RemoveQueryListener(
|
||||
std::shared_ptr<core::QueryListener> listener) {
|
||||
const Query& query = listener->query();
|
||||
bool last_listen = false;
|
||||
|
||||
auto found_iter = queries_.find(query);
|
||||
if (found_iter != queries_.end()) {
|
||||
QueryListenersInfo& query_info = found_iter->second;
|
||||
query_info.Erase(listener);
|
||||
last_listen = query_info.listeners.empty();
|
||||
}
|
||||
|
||||
if (last_listen) {
|
||||
queries_.erase(found_iter);
|
||||
query_event_source_->StopListening(query);
|
||||
}
|
||||
}
|
||||
|
||||
void EventManager::AddSnapshotsInSyncListener(
|
||||
const std::shared_ptr<EventListener<Empty>>& listener) {
|
||||
snapshots_in_sync_listeners_.insert(listener);
|
||||
listener->OnEvent(Empty());
|
||||
}
|
||||
|
||||
void EventManager::RemoveSnapshotsInSyncListener(
|
||||
const std::shared_ptr<EventListener<Empty>>& listener) {
|
||||
snapshots_in_sync_listeners_.erase(listener);
|
||||
}
|
||||
|
||||
void EventManager::HandleOnlineStateChange(model::OnlineState online_state) {
|
||||
bool raised_event = false;
|
||||
online_state_ = online_state;
|
||||
|
||||
for (auto&& kv : queries_) {
|
||||
QueryListenersInfo& info = kv.second;
|
||||
for (auto&& listener : info.listeners) {
|
||||
if (listener->OnOnlineStateChanged(online_state_)) {
|
||||
raised_event = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (raised_event) {
|
||||
RaiseSnapshotsInSyncEvent();
|
||||
}
|
||||
}
|
||||
|
||||
void EventManager::RaiseSnapshotsInSyncEvent() {
|
||||
Empty empty{};
|
||||
for (const auto& listener : snapshots_in_sync_listeners_) {
|
||||
listener->OnEvent(empty);
|
||||
}
|
||||
}
|
||||
|
||||
void EventManager::OnViewSnapshots(
|
||||
std::vector<core::ViewSnapshot>&& snapshots) {
|
||||
bool raised_event = false;
|
||||
for (ViewSnapshot& snapshot : snapshots) {
|
||||
const Query& query = snapshot.query();
|
||||
auto found_iter = queries_.find(query);
|
||||
if (found_iter != queries_.end()) {
|
||||
QueryListenersInfo& query_info = found_iter->second;
|
||||
for (const auto& listener : query_info.listeners) {
|
||||
if (listener->OnViewSnapshot(snapshot)) {
|
||||
raised_event = true;
|
||||
}
|
||||
}
|
||||
query_info.set_view_snapshot(std::move(snapshot));
|
||||
}
|
||||
}
|
||||
if (raised_event) {
|
||||
RaiseSnapshotsInSyncEvent();
|
||||
}
|
||||
}
|
||||
|
||||
void EventManager::OnError(const core::Query& query,
|
||||
const util::Status& error) {
|
||||
auto found_iter = queries_.find(query);
|
||||
if (found_iter == queries_.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QueryListenersInfo& query_info = found_iter->second;
|
||||
for (const auto& listener : query_info.listeners) {
|
||||
listener->OnError(error);
|
||||
}
|
||||
|
||||
// Remove all listeners. NOTE: We don't need to call
|
||||
// `SyncEngine::StopListening()` after an error.
|
||||
queries_.erase(found_iter);
|
||||
}
|
||||
|
||||
bool EventManager::QueryListenersInfo::Erase(
|
||||
const std::shared_ptr<QueryListener>& listener) {
|
||||
auto found_iter = absl::c_find(listeners, listener);
|
||||
auto found = found_iter != listeners.end();
|
||||
if (found) {
|
||||
listeners.erase(found_iter);
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2019 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 FIRESTORE_CORE_SRC_CORE_EVENT_MANAGER_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_EVENT_MANAGER_H_
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "Firestore/core/src/core/query.h"
|
||||
#include "Firestore/core/src/core/sync_engine_callback.h"
|
||||
#include "Firestore/core/src/core/view_snapshot.h"
|
||||
#include "Firestore/core/src/model/model_fwd.h"
|
||||
#include "Firestore/core/src/util/empty.h"
|
||||
#include "Firestore/core/src/util/status_fwd.h"
|
||||
#include "absl/types/optional.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
class QueryEventSource;
|
||||
class QueryListener;
|
||||
|
||||
/**
|
||||
* EventManager is responsible for mapping queries to query event listeners.
|
||||
* It handles "fan-out". (Identical queries will re-use the same watch on the
|
||||
* backend.)
|
||||
*/
|
||||
class EventManager : public SyncEngineCallback {
|
||||
public:
|
||||
explicit EventManager(QueryEventSource* query_event_source_);
|
||||
|
||||
/**
|
||||
* Adds a query listener that will be called with new snapshots for the query.
|
||||
* The EventManager is responsible for multiplexing many listeners to a single
|
||||
* listen in the SyncEngine and will perform a listen if it's the first
|
||||
* QueryListener added for a query.
|
||||
*
|
||||
* Returns the TargetId of the listen call in the SyncEngine.
|
||||
*/
|
||||
model::TargetId AddQueryListener(
|
||||
std::shared_ptr<core::QueryListener> listener);
|
||||
|
||||
/**
|
||||
* Removes a previously added listener. It's a no-op if the listener is not
|
||||
* found.
|
||||
*/
|
||||
void RemoveQueryListener(std::shared_ptr<core::QueryListener> listener);
|
||||
|
||||
void AddSnapshotsInSyncListener(
|
||||
const std::shared_ptr<EventListener<util::Empty>>& listener);
|
||||
void RemoveSnapshotsInSyncListener(
|
||||
const std::shared_ptr<EventListener<util::Empty>>& listener);
|
||||
|
||||
// Implements `QueryEventCallback`.
|
||||
void HandleOnlineStateChange(model::OnlineState online_state) override;
|
||||
void OnViewSnapshots(std::vector<core::ViewSnapshot>&& snapshots) override;
|
||||
void OnError(const core::Query& query, const util::Status& error) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Call all global snapshot listeners that have been set.
|
||||
*/
|
||||
void RaiseSnapshotsInSyncEvent();
|
||||
|
||||
/**
|
||||
* Holds the listeners and the last received ViewSnapshot for a query being
|
||||
* tracked by EventManager.
|
||||
*/
|
||||
struct QueryListenersInfo {
|
||||
model::TargetId target_id;
|
||||
std::vector<std::shared_ptr<QueryListener>> listeners;
|
||||
|
||||
bool Erase(const std::shared_ptr<QueryListener>& listener);
|
||||
|
||||
const absl::optional<ViewSnapshot>& view_snapshot() const {
|
||||
return snapshot_;
|
||||
}
|
||||
|
||||
void set_view_snapshot(const absl::optional<ViewSnapshot>& snapshot) {
|
||||
snapshot_ = snapshot;
|
||||
}
|
||||
|
||||
private:
|
||||
// Other members are public in this struct, ensure that any reads are
|
||||
// copies by requiring reads to go through a const getter.
|
||||
absl::optional<ViewSnapshot> snapshot_;
|
||||
};
|
||||
|
||||
QueryEventSource* query_event_source_ = nullptr;
|
||||
model::OnlineState online_state_ = model::OnlineState::Unknown;
|
||||
std::unordered_map<core::Query, QueryListenersInfo> queries_;
|
||||
std::unordered_set<std::shared_ptr<EventListener<util::Empty>>>
|
||||
snapshots_in_sync_listeners_;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_EVENT_MANAGER_H_
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright 2018 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/core/field_filter.h"
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Firestore/core/src/core/array_contains_any_filter.h"
|
||||
#include "Firestore/core/src/core/array_contains_filter.h"
|
||||
#include "Firestore/core/src/core/in_filter.h"
|
||||
#include "Firestore/core/src/core/key_field_filter.h"
|
||||
#include "Firestore/core/src/core/key_field_in_filter.h"
|
||||
#include "Firestore/core/src/core/key_field_not_in_filter.h"
|
||||
#include "Firestore/core/src/core/not_in_filter.h"
|
||||
#include "Firestore/core/src/core/operator.h"
|
||||
#include "Firestore/core/src/model/document.h"
|
||||
#include "Firestore/core/src/model/value_util.h"
|
||||
#include "Firestore/core/src/util/exception.h"
|
||||
#include "Firestore/core/src/util/hashing.h"
|
||||
#include "absl/algorithm/container.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/types/optional.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
using model::Compare;
|
||||
using model::FieldPath;
|
||||
using model::GetTypeOrder;
|
||||
using model::IsArray;
|
||||
using model::TypeOrder;
|
||||
using nanopb::SharedMessage;
|
||||
using util::ComparisonResult;
|
||||
|
||||
namespace {
|
||||
|
||||
const char* CanonicalName(Filter::Operator op) {
|
||||
switch (op) {
|
||||
case Filter::Operator::LessThan:
|
||||
return "<";
|
||||
case Filter::Operator::LessThanOrEqual:
|
||||
return "<=";
|
||||
case Filter::Operator::Equal:
|
||||
return "==";
|
||||
case Filter::Operator::NotEqual:
|
||||
return "!=";
|
||||
case Filter::Operator::GreaterThanOrEqual:
|
||||
return ">=";
|
||||
case Filter::Operator::GreaterThan:
|
||||
return ">";
|
||||
case Filter::Operator::ArrayContains:
|
||||
// The canonical name for this is array_contains for compatibility with
|
||||
// existing entries in `query_targets` stored on user devices. This cannot
|
||||
// be changed without causing users to lose their associated resume
|
||||
// tokens.
|
||||
return "array_contains";
|
||||
case Filter::Operator::In:
|
||||
return "in";
|
||||
case Filter::Operator::ArrayContainsAny:
|
||||
return "array-contains-any";
|
||||
case Filter::Operator::NotIn:
|
||||
return "not-in";
|
||||
}
|
||||
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
FieldFilter FieldFilter::Create(
|
||||
const FieldPath& path,
|
||||
Operator op,
|
||||
SharedMessage<google_firestore_v1_Value> value_rhs) {
|
||||
google_firestore_v1_Value& value = *value_rhs;
|
||||
model::SortFields(value);
|
||||
if (path.IsKeyFieldPath()) {
|
||||
if (op == Filter::Operator::In) {
|
||||
return KeyFieldInFilter(path, std::move(value_rhs));
|
||||
} else if (op == Filter::Operator::NotIn) {
|
||||
return KeyFieldNotInFilter(path, std::move(value_rhs));
|
||||
} else {
|
||||
HARD_ASSERT(!IsArrayOperator(op),
|
||||
"%s queries don't make sense on document keys.",
|
||||
CanonicalName(op));
|
||||
return KeyFieldFilter(path, op, std::move(value_rhs));
|
||||
}
|
||||
} else if (op == Operator::ArrayContains) {
|
||||
return ArrayContainsFilter(path, std::move(value_rhs));
|
||||
|
||||
} else if (op == Operator::In) {
|
||||
return InFilter(path, std::move(value_rhs));
|
||||
} else if (op == Operator::ArrayContainsAny) {
|
||||
return ArrayContainsAnyFilter(path, std::move(value_rhs));
|
||||
} else if (op == Operator::NotIn) {
|
||||
return NotInFilter(path, std::move(value_rhs));
|
||||
} else {
|
||||
Rep filter(path, op, value_rhs);
|
||||
return FieldFilter(std::make_shared<const Rep>(std::move(filter)));
|
||||
}
|
||||
}
|
||||
|
||||
FieldFilter::FieldFilter(const Filter& other) : Filter(other) {
|
||||
HARD_ASSERT(IsAFieldFilter());
|
||||
}
|
||||
|
||||
FieldFilter::FieldFilter(std::shared_ptr<const Filter::Rep> rep)
|
||||
: Filter(std::move(rep)) {
|
||||
}
|
||||
|
||||
FieldFilter::Rep::Rep(FieldPath field,
|
||||
Operator op,
|
||||
SharedMessage<google_firestore_v1_Value> value_rhs)
|
||||
: field_(std::move(field)), op_(op), value_rhs_(std::move(value_rhs)) {
|
||||
}
|
||||
|
||||
bool FieldFilter::Rep::IsInequality() const {
|
||||
return op_ == Operator::LessThan || op_ == Operator::LessThanOrEqual ||
|
||||
op_ == Operator::GreaterThan || op_ == Operator::GreaterThanOrEqual ||
|
||||
op_ == Operator::NotEqual || op_ == Operator::NotIn;
|
||||
}
|
||||
|
||||
bool FieldFilter::Rep::Matches(const model::Document& doc) const {
|
||||
absl::optional<google_firestore_v1_Value> maybe_lhs = doc->field(field_);
|
||||
if (!maybe_lhs) return false;
|
||||
|
||||
const google_firestore_v1_Value& lhs = *maybe_lhs;
|
||||
|
||||
// Types do not have to match in NotEqual filters.
|
||||
if (op_ == Operator::NotEqual) {
|
||||
return MatchesComparison(Compare(lhs, *value_rhs_));
|
||||
}
|
||||
|
||||
// Only compare types with matching backend order (such as double and int).
|
||||
return GetTypeOrder(lhs) == GetTypeOrder(*value_rhs_) &&
|
||||
MatchesComparison(Compare(lhs, *value_rhs_));
|
||||
}
|
||||
|
||||
bool FieldFilter::Rep::MatchesComparison(ComparisonResult comparison) const {
|
||||
switch (op_) {
|
||||
case Operator::LessThan:
|
||||
return comparison == ComparisonResult::Ascending;
|
||||
case Operator::LessThanOrEqual:
|
||||
return comparison == ComparisonResult::Ascending ||
|
||||
comparison == ComparisonResult::Same;
|
||||
case Operator::Equal:
|
||||
return comparison == ComparisonResult::Same;
|
||||
case Operator::GreaterThanOrEqual:
|
||||
return comparison == ComparisonResult::Descending ||
|
||||
comparison == ComparisonResult::Same;
|
||||
case Operator::GreaterThan:
|
||||
return comparison == ComparisonResult::Descending;
|
||||
case Operator::NotEqual:
|
||||
return comparison != ComparisonResult::Same;
|
||||
default:
|
||||
HARD_FAIL("Operator %s unsuitable for comparison", op_);
|
||||
}
|
||||
}
|
||||
|
||||
std::string FieldFilter::Rep::CanonicalId() const {
|
||||
return absl::StrCat(field_.CanonicalString(), CanonicalName(op_),
|
||||
model::CanonicalId(*value_rhs_));
|
||||
}
|
||||
|
||||
std::string FieldFilter::Rep::ToString() const {
|
||||
return util::StringFormat("%s %s %s", field_.CanonicalString(),
|
||||
CanonicalName(op_),
|
||||
model::CanonicalId(*value_rhs_));
|
||||
}
|
||||
|
||||
size_t FieldFilter::Rep::Hash() const {
|
||||
return util::Hash(field_, op_, model::CanonicalId(*value_rhs_));
|
||||
}
|
||||
|
||||
bool FieldFilter::Rep::Equals(const Filter::Rep& other) const {
|
||||
if (type() != other.type()) return false;
|
||||
|
||||
const auto& other_rep = static_cast<const FieldFilter::Rep&>(other);
|
||||
return op_ == other_rep.op_ && field_ == other_rep.field_ &&
|
||||
*value_rhs_ == *other_rep.value_rhs_;
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 2018 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_CORE_FIELD_FILTER_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_FIELD_FILTER_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h"
|
||||
#include "Firestore/core/src/core/filter.h"
|
||||
#include "Firestore/core/src/model/field_path.h"
|
||||
#include "Firestore/core/src/nanopb/message.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
|
||||
namespace model {
|
||||
class Document;
|
||||
} // namespace model
|
||||
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* FieldFilter is a document filter constraint on a query with a single
|
||||
* relation operator.
|
||||
*/
|
||||
class FieldFilter : public Filter {
|
||||
public:
|
||||
/**
|
||||
* Creates a Filter instance for the provided path, operator, and value.
|
||||
*/
|
||||
static FieldFilter Create(
|
||||
const model::FieldPath& path,
|
||||
Operator op,
|
||||
nanopb::SharedMessage<google_firestore_v1_Value> value_rhs);
|
||||
|
||||
explicit FieldFilter(const Filter& other);
|
||||
|
||||
const model::FieldPath& field() const {
|
||||
return field_filter_rep().field_;
|
||||
}
|
||||
|
||||
Operator op() const {
|
||||
return field_filter_rep().op_;
|
||||
}
|
||||
|
||||
const google_firestore_v1_Value& value() const {
|
||||
return *(field_filter_rep().value_rhs_);
|
||||
}
|
||||
|
||||
protected:
|
||||
class Rep : public Filter::Rep {
|
||||
public:
|
||||
Type type() const override {
|
||||
return Type::kFieldFilter;
|
||||
}
|
||||
|
||||
bool IsAFieldFilter() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsInequality() const override;
|
||||
|
||||
const model::FieldPath& field() const override {
|
||||
return field_;
|
||||
}
|
||||
|
||||
Operator op() const {
|
||||
return op_;
|
||||
}
|
||||
|
||||
const google_firestore_v1_Value& value() const {
|
||||
return *value_rhs_;
|
||||
}
|
||||
|
||||
bool Matches(const model::Document& doc) const override;
|
||||
|
||||
std::string CanonicalId() const override;
|
||||
|
||||
std::string ToString() const override;
|
||||
|
||||
size_t Hash() const override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Creates a new filter that compares fields and values. Only intended to be
|
||||
* called from Filter::Create().
|
||||
*
|
||||
* The FieldFilter takes ownership of `value_rhs`.
|
||||
*
|
||||
* @param field A path to a field in the document to filter on. The LHS of
|
||||
* the expression.
|
||||
* @param op The binary operator to apply.
|
||||
* @param value_rhs A constant value to compare `field` to. The RHS of the
|
||||
* expression.
|
||||
*/
|
||||
Rep(model::FieldPath field,
|
||||
Operator op,
|
||||
nanopb::SharedMessage<google_firestore_v1_Value> value_rhs);
|
||||
|
||||
bool MatchesComparison(util::ComparisonResult comparison) const;
|
||||
|
||||
private:
|
||||
friend class FieldFilter;
|
||||
|
||||
bool Equals(const Filter::Rep& other) const override;
|
||||
|
||||
/** The left hand side of the relation. A path into a document field. */
|
||||
model::FieldPath field_;
|
||||
|
||||
/** The type of equality/inequality operator to use in the relation. */
|
||||
Operator op_;
|
||||
|
||||
/** The right hand side of the relation. A constant value to compare to. */
|
||||
nanopb::SharedMessage<google_firestore_v1_Value> value_rhs_;
|
||||
};
|
||||
|
||||
explicit FieldFilter(std::shared_ptr<const Filter::Rep> rep);
|
||||
|
||||
private:
|
||||
const Rep& field_filter_rep() const {
|
||||
return static_cast<const Rep&>(rep());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_FIELD_FILTER_H_
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2018 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/core/filter.h"
|
||||
|
||||
#include <ostream>
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
bool operator==(const Filter& lhs, const Filter& rhs) {
|
||||
return lhs.rep_ == nullptr
|
||||
? rhs.rep_ == nullptr
|
||||
: (rhs.rep_ != nullptr && lhs.rep_->Equals(*rhs.rep_));
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Filter& filter) {
|
||||
return os << filter.ToString();
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright 2018 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_CORE_FILTER_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_FILTER_H_
|
||||
|
||||
#include <iosfwd>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "Firestore/core/src/model/model_fwd.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
|
||||
namespace immutable {
|
||||
template <typename T>
|
||||
class AppendOnlyList;
|
||||
} // namespace immutable
|
||||
|
||||
namespace core {
|
||||
|
||||
/** Interface used for all query filters. All filters are immutable. */
|
||||
class Filter {
|
||||
public:
|
||||
/**
|
||||
* Operator is a value relation operator that can be used to filter documents.
|
||||
* It is similar to NSPredicateOperatorType, but only has operators supported
|
||||
* by Firestore.
|
||||
*/
|
||||
enum class Operator {
|
||||
LessThan,
|
||||
LessThanOrEqual,
|
||||
Equal,
|
||||
NotEqual,
|
||||
GreaterThanOrEqual,
|
||||
GreaterThan,
|
||||
ArrayContains,
|
||||
In,
|
||||
ArrayContainsAny,
|
||||
NotIn,
|
||||
};
|
||||
|
||||
// For lack of RTTI, all subclasses must identify themselves so that
|
||||
// comparisons properly take type into account.
|
||||
enum class Type {
|
||||
kArrayContainsAnyFilter,
|
||||
kArrayContainsFilter,
|
||||
kFieldFilter,
|
||||
kInFilter,
|
||||
kNotInFilter,
|
||||
kKeyFieldFilter,
|
||||
kKeyFieldInFilter,
|
||||
kKeyFieldNotInFilter,
|
||||
};
|
||||
|
||||
Type type() const {
|
||||
return rep_->type();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this instance is FieldFilter or any derived class.
|
||||
* Equivalent to `instanceof FieldFilter` on other platforms.
|
||||
*
|
||||
* Note this is different than checking `type() == Type::kFieldFilter` which
|
||||
* is only true if the type is exactly FieldFilter.
|
||||
*/
|
||||
bool IsAFieldFilter() const {
|
||||
return rep_->IsAFieldFilter();
|
||||
}
|
||||
|
||||
bool IsInequality() const {
|
||||
return rep_->IsInequality();
|
||||
}
|
||||
|
||||
/** Returns the field the Filter operates over. */
|
||||
const model::FieldPath& field() const {
|
||||
return rep_->field();
|
||||
}
|
||||
|
||||
/** Returns true if a document matches the filter. */
|
||||
bool Matches(const model::Document& doc) const {
|
||||
return rep_->Matches(doc);
|
||||
}
|
||||
|
||||
/** A unique ID identifying the filter; used when serializing queries. */
|
||||
std::string CanonicalId() const {
|
||||
return rep_->CanonicalId();
|
||||
}
|
||||
|
||||
/** A debug description of the Filter. */
|
||||
std::string ToString() const {
|
||||
return rep_->ToString();
|
||||
}
|
||||
|
||||
size_t Hash() const {
|
||||
return rep_->Hash();
|
||||
}
|
||||
|
||||
friend bool operator==(const Filter& lhs, const Filter& rhs);
|
||||
|
||||
protected:
|
||||
class Rep {
|
||||
public:
|
||||
virtual ~Rep() = default;
|
||||
|
||||
virtual Type type() const = 0;
|
||||
|
||||
virtual bool IsAFieldFilter() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool IsInequality() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns the field the Filter operates over. */
|
||||
virtual const model::FieldPath& field() const = 0;
|
||||
|
||||
/** Returns true if a document matches the filter. */
|
||||
virtual bool Matches(const model::Document& doc) const = 0;
|
||||
|
||||
/** A unique ID identifying the filter; used when serializing queries. */
|
||||
virtual std::string CanonicalId() const = 0;
|
||||
|
||||
virtual bool Equals(const Rep& other) const = 0;
|
||||
|
||||
virtual size_t Hash() const = 0;
|
||||
|
||||
/** A debug description of the Filter. */
|
||||
virtual std::string ToString() const = 0;
|
||||
};
|
||||
|
||||
explicit Filter(std::shared_ptr<const Rep> rep) : rep_(rep) {
|
||||
}
|
||||
|
||||
const Rep& rep() const {
|
||||
return *rep_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<const Rep> rep_;
|
||||
};
|
||||
|
||||
inline bool operator!=(const Filter& lhs, const Filter& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
/** A list of Filters, as used in Queries and elsewhere. */
|
||||
using FilterList = immutable::AppendOnlyList<Filter>;
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Filter& filter);
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_FILTER_H_
|
||||
@@ -0,0 +1,572 @@
|
||||
/*
|
||||
* Copyright 2019 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 "Firestore/core/src/core/firestore_client.h"
|
||||
|
||||
#include <functional>
|
||||
#include <future> // NOLINT(build/c++11)
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/api/document_reference.h"
|
||||
#include "Firestore/core/src/api/document_snapshot.h"
|
||||
#include "Firestore/core/src/api/query_core.h"
|
||||
#include "Firestore/core/src/api/query_snapshot.h"
|
||||
#include "Firestore/core/src/api/settings.h"
|
||||
#include "Firestore/core/src/bundle/bundle_reader.h"
|
||||
#include "Firestore/core/src/core/database_info.h"
|
||||
#include "Firestore/core/src/core/event_manager.h"
|
||||
#include "Firestore/core/src/core/query_listener.h"
|
||||
#include "Firestore/core/src/core/sync_engine.h"
|
||||
#include "Firestore/core/src/core/view.h"
|
||||
#include "Firestore/core/src/credentials/credentials_provider.h"
|
||||
#include "Firestore/core/src/local/leveldb_opener.h"
|
||||
#include "Firestore/core/src/local/leveldb_persistence.h"
|
||||
#include "Firestore/core/src/local/local_documents_view.h"
|
||||
#include "Firestore/core/src/local/local_serializer.h"
|
||||
#include "Firestore/core/src/local/local_store.h"
|
||||
#include "Firestore/core/src/local/memory_persistence.h"
|
||||
#include "Firestore/core/src/local/query_engine.h"
|
||||
#include "Firestore/core/src/local/query_result.h"
|
||||
#include "Firestore/core/src/model/database_id.h"
|
||||
#include "Firestore/core/src/model/document.h"
|
||||
#include "Firestore/core/src/model/document_set.h"
|
||||
#include "Firestore/core/src/model/mutation.h"
|
||||
#include "Firestore/core/src/remote/connectivity_monitor.h"
|
||||
#include "Firestore/core/src/remote/datastore.h"
|
||||
#include "Firestore/core/src/remote/firebase_metadata_provider.h"
|
||||
#include "Firestore/core/src/remote/remote_store.h"
|
||||
#include "Firestore/core/src/remote/serializer.h"
|
||||
#include "Firestore/core/src/util/async_queue.h"
|
||||
#include "Firestore/core/src/util/delayed_constructor.h"
|
||||
#include "Firestore/core/src/util/exception.h"
|
||||
#include "Firestore/core/src/util/hard_assert.h"
|
||||
#include "Firestore/core/src/util/log.h"
|
||||
#include "Firestore/core/src/util/status.h"
|
||||
#include "Firestore/core/src/util/statusor.h"
|
||||
#include "Firestore/core/src/util/string_apple.h"
|
||||
#include "absl/memory/memory.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
using api::DocumentReference;
|
||||
using api::DocumentSnapshot;
|
||||
using api::DocumentSnapshotListener;
|
||||
using api::QuerySnapshot;
|
||||
using api::QuerySnapshotListener;
|
||||
using api::Settings;
|
||||
using api::SnapshotMetadata;
|
||||
using credentials::AuthCredentialsProvider;
|
||||
using credentials::User;
|
||||
using firestore::Error;
|
||||
using local::LevelDbOpener;
|
||||
using local::LocalStore;
|
||||
using local::LruParams;
|
||||
using local::MemoryPersistence;
|
||||
using local::QueryEngine;
|
||||
using local::QueryResult;
|
||||
using model::Document;
|
||||
using model::DocumentKeySet;
|
||||
using model::DocumentMap;
|
||||
using model::Mutation;
|
||||
using model::OnlineState;
|
||||
using remote::ConnectivityMonitor;
|
||||
using remote::Datastore;
|
||||
using remote::FirebaseMetadataProvider;
|
||||
using remote::RemoteStore;
|
||||
using remote::Serializer;
|
||||
using util::AsyncQueue;
|
||||
using util::Empty;
|
||||
using util::Executor;
|
||||
using util::Status;
|
||||
using util::StatusCallback;
|
||||
using util::StatusOr;
|
||||
using util::StatusOrCallback;
|
||||
using util::ThrowIllegalState;
|
||||
using util::TimerId;
|
||||
|
||||
static const size_t kMaxConcurrentLimboResolutions = 100;
|
||||
|
||||
std::shared_ptr<FirestoreClient> FirestoreClient::Create(
|
||||
const DatabaseInfo& database_info,
|
||||
const api::Settings& settings,
|
||||
std::shared_ptr<credentials::AuthCredentialsProvider>
|
||||
auth_credentials_provider,
|
||||
std::shared_ptr<credentials::AppCheckCredentialsProvider>
|
||||
app_check_credentials_provider,
|
||||
std::shared_ptr<Executor> user_executor,
|
||||
std::shared_ptr<AsyncQueue> worker_queue,
|
||||
std::unique_ptr<FirebaseMetadataProvider> firebase_metadata_provider) {
|
||||
// Have to use `new` because `make_shared` cannot access private constructor.
|
||||
std::shared_ptr<FirestoreClient> shared_client(new FirestoreClient(
|
||||
database_info, std::move(auth_credentials_provider),
|
||||
std::move(app_check_credentials_provider), std::move(user_executor),
|
||||
std::move(worker_queue), std::move(firebase_metadata_provider)));
|
||||
|
||||
std::weak_ptr<FirestoreClient> weak_client(shared_client);
|
||||
auto credential_change_listener = [weak_client, settings](User user) mutable {
|
||||
auto shared_client = weak_client.lock();
|
||||
if (!shared_client) return;
|
||||
|
||||
if (!shared_client->credentials_initialized_) {
|
||||
shared_client->credentials_initialized_ = true;
|
||||
|
||||
// When we register the credentials listener for the first time,
|
||||
// it is invoked synchronously on the calling thread. This ensures that
|
||||
// the first item enqueued on the worker queue is
|
||||
// `FirestoreClient::Initialize()`.
|
||||
shared_client->worker_queue_->Enqueue([shared_client, user, settings] {
|
||||
shared_client->Initialize(user, settings);
|
||||
});
|
||||
} else {
|
||||
shared_client->worker_queue_->Enqueue([shared_client, user] {
|
||||
shared_client->worker_queue_->VerifyIsCurrentQueue();
|
||||
|
||||
LOG_DEBUG("Credential Changed. Current user: %s", user.uid());
|
||||
shared_client->sync_engine_->HandleCredentialChange(user);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
shared_client->app_check_credentials_provider_->SetCredentialChangeListener(
|
||||
[](std::string) {
|
||||
// Register an empty credentials change listener to activate token
|
||||
// refresh.
|
||||
});
|
||||
shared_client->auth_credentials_provider_->SetCredentialChangeListener(
|
||||
credential_change_listener);
|
||||
|
||||
HARD_ASSERT(
|
||||
shared_client->credentials_initialized_,
|
||||
"CredentialChangeListener not invoked during client initialization");
|
||||
|
||||
return shared_client;
|
||||
}
|
||||
|
||||
FirestoreClient::FirestoreClient(
|
||||
const DatabaseInfo& database_info,
|
||||
std::shared_ptr<credentials::AuthCredentialsProvider>
|
||||
auth_credentials_provider,
|
||||
std::shared_ptr<credentials::AppCheckCredentialsProvider>
|
||||
app_check_credentials_provider,
|
||||
std::shared_ptr<Executor> user_executor,
|
||||
std::shared_ptr<AsyncQueue> worker_queue,
|
||||
std::unique_ptr<FirebaseMetadataProvider> firebase_metadata_provider)
|
||||
: database_info_(database_info),
|
||||
app_check_credentials_provider_(
|
||||
std::move(app_check_credentials_provider)),
|
||||
auth_credentials_provider_(std::move(auth_credentials_provider)),
|
||||
worker_queue_(std::move(worker_queue)),
|
||||
user_executor_(std::move(user_executor)),
|
||||
firebase_metadata_provider_(std::move(firebase_metadata_provider)) {
|
||||
}
|
||||
|
||||
void FirestoreClient::Initialize(const User& user, const Settings& settings) {
|
||||
// Do all of our initialization on our own dispatch queue.
|
||||
worker_queue_->VerifyIsCurrentQueue();
|
||||
LOG_DEBUG("Initializing. Current user: %s", user.uid());
|
||||
|
||||
// Note: The initialization work must all be synchronous (we can't dispatch
|
||||
// more work) since external write/listen operations could get queued to run
|
||||
// before that subsequent work completes.
|
||||
if (settings.persistence_enabled()) {
|
||||
LevelDbOpener opener(database_info_);
|
||||
|
||||
auto created =
|
||||
opener.Create(LruParams::WithCacheSize(settings.cache_size_bytes()));
|
||||
// If leveldb fails to start then just throw up our hands: the error is
|
||||
// unrecoverable. There's nothing an end-user can do and nearly all
|
||||
// failures indicate the developer is doing something grossly wrong so we
|
||||
// should stop them cold in their tracks with a failure they can't ignore.
|
||||
HARD_ASSERT(created.ok(), "Failed to open DB: %s",
|
||||
created.status().ToString());
|
||||
|
||||
auto ldb = std::move(created).ValueOrDie();
|
||||
lru_delegate_ = ldb->reference_delegate();
|
||||
|
||||
persistence_ = std::move(ldb);
|
||||
if (settings.gc_enabled()) {
|
||||
ScheduleLruGarbageCollection();
|
||||
}
|
||||
} else {
|
||||
persistence_ = MemoryPersistence::WithEagerGarbageCollector();
|
||||
}
|
||||
|
||||
query_engine_ = absl::make_unique<QueryEngine>();
|
||||
local_store_ = absl::make_unique<LocalStore>(persistence_.get(),
|
||||
query_engine_.get(), user);
|
||||
connectivity_monitor_ = ConnectivityMonitor::Create(worker_queue_);
|
||||
auto datastore = std::make_shared<Datastore>(
|
||||
database_info_, worker_queue_, auth_credentials_provider_,
|
||||
app_check_credentials_provider_, connectivity_monitor_.get(),
|
||||
firebase_metadata_provider_.get());
|
||||
|
||||
remote_store_ = absl::make_unique<RemoteStore>(
|
||||
local_store_.get(), std::move(datastore), worker_queue_,
|
||||
connectivity_monitor_.get(), [this](OnlineState online_state) {
|
||||
sync_engine_->HandleOnlineStateChange(online_state);
|
||||
});
|
||||
|
||||
sync_engine_ =
|
||||
absl::make_unique<SyncEngine>(local_store_.get(), remote_store_.get(),
|
||||
user, kMaxConcurrentLimboResolutions);
|
||||
|
||||
event_manager_ = absl::make_unique<EventManager>(sync_engine_.get());
|
||||
|
||||
// Setup wiring for remote store.
|
||||
remote_store_->set_sync_engine(sync_engine_.get());
|
||||
|
||||
// NOTE: RemoteStore depends on LocalStore (for persisting stream tokens,
|
||||
// refilling mutation queue, etc.) so must be started after LocalStore.
|
||||
local_store_->Start();
|
||||
remote_store_->Start();
|
||||
}
|
||||
|
||||
FirestoreClient::~FirestoreClient() {
|
||||
Dispose();
|
||||
}
|
||||
|
||||
void FirestoreClient::Dispose() {
|
||||
// Prevent new API invocations from enqueueing further work.
|
||||
worker_queue_->EnterRestrictedMode();
|
||||
|
||||
// Clean up internal resources. It's possible that this can race with a call
|
||||
// to `Firestore::ClearPersistence` or `Firestore::Terminate`, but that's OK
|
||||
// because that operation does not rely on any state in this FirestoreClient.
|
||||
std::promise<void> signal_disposing;
|
||||
bool enqueued = worker_queue_->EnqueueEvenWhileRestricted([&, this] {
|
||||
// Once this task has started running, AsyncQueue::Dispose will block on its
|
||||
// completion. Signal as early as possible to lock out even restricted tasks
|
||||
// as early as possible.
|
||||
signal_disposing.set_value();
|
||||
|
||||
TerminateInternal();
|
||||
});
|
||||
|
||||
// If we successfully enqueued the TerminateInternal task then wait for it to
|
||||
// start.
|
||||
//
|
||||
// If the task was not enqueued, we lost the race with some other concurrent
|
||||
// invocation of Dispose. In that case, `signal_disposing` will never be
|
||||
// completed.
|
||||
if (enqueued) {
|
||||
signal_disposing.get_future().wait();
|
||||
}
|
||||
|
||||
worker_queue_->Dispose();
|
||||
user_executor_->Dispose();
|
||||
}
|
||||
|
||||
void FirestoreClient::TerminateAsync(StatusCallback callback) {
|
||||
worker_queue_->EnterRestrictedMode();
|
||||
worker_queue_->EnqueueEvenWhileRestricted([this, callback] {
|
||||
TerminateInternal();
|
||||
|
||||
if (callback) {
|
||||
user_executor_->Execute([=] { callback(Status::OK()); });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void FirestoreClient::TerminateInternal() {
|
||||
if (!remote_store_) return;
|
||||
|
||||
app_check_credentials_provider_->SetCredentialChangeListener(nullptr);
|
||||
app_check_credentials_provider_.reset();
|
||||
|
||||
auth_credentials_provider_->SetCredentialChangeListener(nullptr);
|
||||
auth_credentials_provider_.reset();
|
||||
|
||||
// If we've scheduled LRU garbage collection, cancel it.
|
||||
lru_callback_.Cancel();
|
||||
|
||||
remote_store_->Shutdown();
|
||||
persistence_->Shutdown();
|
||||
|
||||
local_store_.reset();
|
||||
query_engine_.reset();
|
||||
event_manager_.reset();
|
||||
|
||||
// Clear the remote store to indicate terminate is complete.
|
||||
remote_store_.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a callback to try running LRU garbage collection. Reschedules
|
||||
* itself after the GC has run.
|
||||
*/
|
||||
void FirestoreClient::ScheduleLruGarbageCollection() {
|
||||
std::chrono::milliseconds delay =
|
||||
gc_has_run_ ? regular_gc_delay_ : initial_gc_delay_;
|
||||
|
||||
lru_callback_ = worker_queue_->EnqueueAfterDelay(
|
||||
delay, TimerId::GarbageCollectionDelay, [this] {
|
||||
local_store_->CollectGarbage(lru_delegate_->garbage_collector());
|
||||
gc_has_run_ = true;
|
||||
ScheduleLruGarbageCollection();
|
||||
});
|
||||
}
|
||||
|
||||
void FirestoreClient::DisableNetwork(StatusCallback callback) {
|
||||
VerifyNotTerminated();
|
||||
|
||||
worker_queue_->Enqueue([this, callback] {
|
||||
remote_store_->DisableNetwork();
|
||||
if (callback) {
|
||||
user_executor_->Execute([=] { callback(Status::OK()); });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void FirestoreClient::EnableNetwork(StatusCallback callback) {
|
||||
VerifyNotTerminated();
|
||||
|
||||
worker_queue_->Enqueue([this, callback] {
|
||||
remote_store_->EnableNetwork();
|
||||
if (callback) {
|
||||
user_executor_->Execute([=] { callback(Status::OK()); });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void FirestoreClient::WaitForPendingWrites(StatusCallback callback) {
|
||||
VerifyNotTerminated();
|
||||
|
||||
// Dispatch the result back onto the user dispatch queue.
|
||||
auto async_callback = [this, callback](util::Status status) {
|
||||
if (callback) {
|
||||
user_executor_->Execute([=] { callback(std::move(status)); });
|
||||
}
|
||||
};
|
||||
|
||||
worker_queue_->Enqueue([this, async_callback] {
|
||||
sync_engine_->RegisterPendingWritesCallback(std::move(async_callback));
|
||||
});
|
||||
}
|
||||
|
||||
void FirestoreClient::VerifyNotTerminated() {
|
||||
if (is_terminated()) {
|
||||
ThrowIllegalState("The client has already been terminated.");
|
||||
}
|
||||
}
|
||||
|
||||
bool FirestoreClient::is_terminated() const {
|
||||
// When the user calls `Terminate`, it puts the `AsyncQueue` into restricted
|
||||
// mode.
|
||||
//
|
||||
// Note that `remote_store_ == nullptr` is not a good test for this because
|
||||
// `remote_store_` is reset asynchronously.
|
||||
return !worker_queue_->is_running();
|
||||
}
|
||||
|
||||
std::shared_ptr<QueryListener> FirestoreClient::ListenToQuery(
|
||||
Query query, ListenOptions options, ViewSnapshotSharedListener&& listener) {
|
||||
VerifyNotTerminated();
|
||||
|
||||
auto query_listener = QueryListener::Create(
|
||||
std::move(query), std::move(options), std::move(listener));
|
||||
|
||||
worker_queue_->Enqueue([this, query_listener] {
|
||||
event_manager_->AddQueryListener(std::move(query_listener));
|
||||
});
|
||||
|
||||
return query_listener;
|
||||
}
|
||||
|
||||
void FirestoreClient::RemoveListener(
|
||||
const std::shared_ptr<QueryListener>& listener) {
|
||||
// Checks for termination but does not throw error, allowing it to be an no-op
|
||||
// if client is already terminated.
|
||||
if (is_terminated()) {
|
||||
return;
|
||||
}
|
||||
worker_queue_->Enqueue(
|
||||
[this, listener] { event_manager_->RemoveQueryListener(listener); });
|
||||
}
|
||||
|
||||
void FirestoreClient::GetDocumentFromLocalCache(
|
||||
const DocumentReference& doc, DocumentSnapshotListener&& callback) {
|
||||
VerifyNotTerminated();
|
||||
|
||||
// TODO(c++14): move `callback` into lambda.
|
||||
auto shared_callback = absl::ShareUniquePtr(std::move(callback));
|
||||
worker_queue_->Enqueue([this, doc, shared_callback] {
|
||||
Document document = local_store_->ReadDocument(doc.key());
|
||||
StatusOr<DocumentSnapshot> maybe_snapshot;
|
||||
|
||||
if (document->is_found_document()) {
|
||||
maybe_snapshot = DocumentSnapshot::FromDocument(
|
||||
doc.firestore(), document,
|
||||
SnapshotMetadata{document->has_local_mutations(),
|
||||
/*from_cache=*/true});
|
||||
} else if (document->is_no_document()) {
|
||||
maybe_snapshot = DocumentSnapshot::FromNoDocument(
|
||||
doc.firestore(), doc.key(),
|
||||
SnapshotMetadata{/*pending_writes=*/false,
|
||||
/*from_cache=*/true});
|
||||
} else {
|
||||
maybe_snapshot =
|
||||
Status{Error::kErrorUnavailable,
|
||||
"Failed to get document from cache. (However, this document "
|
||||
"may exist on the server. Run again without setting source to "
|
||||
"FirestoreSourceCache to attempt to retrieve the document "};
|
||||
}
|
||||
|
||||
if (shared_callback) {
|
||||
user_executor_->Execute(
|
||||
[=] { shared_callback->OnEvent(std::move(maybe_snapshot)); });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void FirestoreClient::GetDocumentsFromLocalCache(
|
||||
const api::Query& query, QuerySnapshotListener&& callback) {
|
||||
VerifyNotTerminated();
|
||||
|
||||
// TODO(c++14): move `callback` into lambda.
|
||||
auto shared_callback = absl::ShareUniquePtr(std::move(callback));
|
||||
worker_queue_->Enqueue([this, query, shared_callback] {
|
||||
QueryResult query_result = local_store_->ExecuteQuery(
|
||||
query.query(), /* use_previous_results= */ true);
|
||||
|
||||
View view(query.query(), query_result.remote_keys());
|
||||
ViewDocumentChanges view_doc_changes =
|
||||
view.ComputeDocumentChanges(query_result.documents());
|
||||
ViewChange view_change = view.ApplyChanges(view_doc_changes);
|
||||
HARD_ASSERT(
|
||||
view_change.limbo_changes().empty(),
|
||||
"View returned limbo documents during local-only query execution.");
|
||||
|
||||
HARD_ASSERT(view_change.snapshot().has_value(), "Expected a snapshot");
|
||||
|
||||
ViewSnapshot snapshot = std::move(view_change.snapshot()).value();
|
||||
SnapshotMetadata metadata(snapshot.has_pending_writes(),
|
||||
snapshot.from_cache());
|
||||
|
||||
QuerySnapshot result(query.firestore(), query.query(), std::move(snapshot),
|
||||
std::move(metadata));
|
||||
|
||||
if (shared_callback) {
|
||||
user_executor_->Execute(
|
||||
[=] { shared_callback->OnEvent(std::move(result)); });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void FirestoreClient::WriteMutations(std::vector<Mutation>&& mutations,
|
||||
StatusCallback callback) {
|
||||
VerifyNotTerminated();
|
||||
|
||||
// TODO(c++14): move `mutations` into lambda (C++14).
|
||||
worker_queue_->Enqueue([this, mutations, callback]() mutable {
|
||||
if (mutations.empty()) {
|
||||
if (callback) {
|
||||
user_executor_->Execute([=] { callback(Status::OK()); });
|
||||
}
|
||||
} else {
|
||||
sync_engine_->WriteMutations(
|
||||
std::move(mutations), [this, callback](Status error) {
|
||||
// Dispatch the result back onto the user dispatch queue.
|
||||
if (callback) {
|
||||
user_executor_->Execute([=] { callback(std::move(error)); });
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void FirestoreClient::Transaction(int retries,
|
||||
TransactionUpdateCallback update_callback,
|
||||
TransactionResultCallback result_callback) {
|
||||
VerifyNotTerminated();
|
||||
|
||||
// Dispatch the result back onto the user dispatch queue.
|
||||
auto async_callback = [this, result_callback](Status status) {
|
||||
if (result_callback) {
|
||||
user_executor_->Execute([=] { result_callback(std::move(status)); });
|
||||
}
|
||||
};
|
||||
|
||||
worker_queue_->Enqueue([this, retries, update_callback, async_callback] {
|
||||
sync_engine_->Transaction(retries, worker_queue_,
|
||||
std::move(update_callback),
|
||||
std::move(async_callback));
|
||||
});
|
||||
}
|
||||
|
||||
void FirestoreClient::AddSnapshotsInSyncListener(
|
||||
const std::shared_ptr<EventListener<Empty>>& user_listener) {
|
||||
worker_queue_->Enqueue([this, user_listener] {
|
||||
event_manager_->AddSnapshotsInSyncListener(std::move(user_listener));
|
||||
});
|
||||
}
|
||||
|
||||
void FirestoreClient::RemoveSnapshotsInSyncListener(
|
||||
const std::shared_ptr<EventListener<Empty>>& user_listener) {
|
||||
worker_queue_->Enqueue([this, user_listener] {
|
||||
event_manager_->RemoveSnapshotsInSyncListener(user_listener);
|
||||
});
|
||||
}
|
||||
|
||||
void FirestoreClient::LoadBundle(
|
||||
std::unique_ptr<util::ByteStream> bundle_data,
|
||||
std::shared_ptr<api::LoadBundleTask> result_task) {
|
||||
VerifyNotTerminated();
|
||||
|
||||
bundle::BundleSerializer bundle_serializer(
|
||||
remote::Serializer(database_info_.database_id()));
|
||||
auto reader = std::make_shared<bundle::BundleReader>(
|
||||
std::move(bundle_serializer), std::move(bundle_data));
|
||||
worker_queue_->Enqueue([this, reader, result_task] {
|
||||
sync_engine_->LoadBundle(std::move(reader), std::move(result_task));
|
||||
});
|
||||
}
|
||||
|
||||
void FirestoreClient::GetNamedQuery(const std::string& name,
|
||||
api::QueryCallback callback) {
|
||||
VerifyNotTerminated();
|
||||
|
||||
// Dispatch the result back onto the user dispatch queue.
|
||||
auto async_callback =
|
||||
[this, callback](const absl::optional<bundle::NamedQuery>& named_query) {
|
||||
if (callback) {
|
||||
if (named_query.has_value()) {
|
||||
const Target& target = named_query.value().bundled_query().target();
|
||||
Query query(target.path(), target.collection_group(),
|
||||
target.filters(), target.order_bys(), target.limit(),
|
||||
named_query.value().bundled_query().limit_type(),
|
||||
target.start_at(), target.end_at());
|
||||
user_executor_->Execute([query, callback] {
|
||||
callback(std::move(query), /*found=*/true);
|
||||
});
|
||||
} else {
|
||||
user_executor_->Execute(
|
||||
[callback] { callback(Query(), /*found=*/false); });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
worker_queue_->Enqueue([this, name, async_callback] {
|
||||
async_callback(local_store_->GetNamedQuery(name));
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright 2019 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 FIRESTORE_CORE_SRC_CORE_FIRESTORE_CLIENT_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_FIRESTORE_CLIENT_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Firestore/core/src/api/api_fwd.h"
|
||||
#include "Firestore/core/src/api/load_bundle_task.h"
|
||||
#include "Firestore/core/src/bundle/bundle_serializer.h"
|
||||
#include "Firestore/core/src/core/core_fwd.h"
|
||||
#include "Firestore/core/src/core/database_info.h"
|
||||
#include "Firestore/core/src/credentials/credentials_fwd.h"
|
||||
#include "Firestore/core/src/model/database_id.h"
|
||||
#include "Firestore/core/src/util/async_queue.h"
|
||||
#include "Firestore/core/src/util/byte_stream.h"
|
||||
#include "Firestore/core/src/util/delayed_constructor.h"
|
||||
#include "Firestore/core/src/util/empty.h"
|
||||
#include "Firestore/core/src/util/executor.h"
|
||||
#include "Firestore/core/src/util/nullability.h"
|
||||
#include "Firestore/core/src/util/status_fwd.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
|
||||
namespace local {
|
||||
class LocalStore;
|
||||
class LruDelegate;
|
||||
class Persistence;
|
||||
class QueryEngine;
|
||||
} // namespace local
|
||||
|
||||
namespace model {
|
||||
class Mutation;
|
||||
} // namespace model
|
||||
|
||||
namespace remote {
|
||||
class ConnectivityMonitor;
|
||||
class FirebaseMetadataProvider;
|
||||
class RemoteStore;
|
||||
} // namespace remote
|
||||
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* FirestoreClient is a top-level class that constructs and owns all of the
|
||||
* pieces of the client SDK architecture.
|
||||
*/
|
||||
class FirestoreClient : public std::enable_shared_from_this<FirestoreClient> {
|
||||
public:
|
||||
/**
|
||||
* Creates a fully initialized `FirestoreClient`.
|
||||
*
|
||||
* PORTING NOTE: We use factory function instead of public constructor
|
||||
* because `FirestoreClient` is supposed to be managed by shared_ptr, and
|
||||
* it is invalid to call `shared_from_this()` from constructors.
|
||||
* The factory function enforces that `FirestoreClient` has to be managed
|
||||
* by a shared pointer.
|
||||
*/
|
||||
static std::shared_ptr<FirestoreClient> Create(
|
||||
const DatabaseInfo& database_info,
|
||||
const api::Settings& settings,
|
||||
std::shared_ptr<credentials::AuthCredentialsProvider>
|
||||
auth_credentials_provider,
|
||||
std::shared_ptr<credentials::AppCheckCredentialsProvider>
|
||||
app_check_credentials_provider,
|
||||
std::shared_ptr<util::Executor> user_executor,
|
||||
std::shared_ptr<util::AsyncQueue> worker_queue,
|
||||
std::unique_ptr<remote::FirebaseMetadataProvider>
|
||||
firebase_metadata_provider);
|
||||
|
||||
~FirestoreClient();
|
||||
|
||||
/**
|
||||
* Synchronously destroys this client, cancels all writes / listeners, and
|
||||
* releases all resources.
|
||||
*/
|
||||
void Dispose();
|
||||
|
||||
/**
|
||||
* Terminates this client, cancels all writes / listeners, and releases all
|
||||
* resources.
|
||||
*/
|
||||
void TerminateAsync(util::StatusCallback callback);
|
||||
|
||||
/**
|
||||
* Passes a callback that is triggered when all the pending writes at the
|
||||
* time when this method is called received server acknowledgement.
|
||||
* An acknowledgement can be either acceptance or rejections.
|
||||
*/
|
||||
void WaitForPendingWrites(util::StatusCallback callback);
|
||||
|
||||
/** Disables the network connection. Pending operations will not complete. */
|
||||
void DisableNetwork(util::StatusCallback callback);
|
||||
|
||||
/** Enables the network connection and requeues all pending operations. */
|
||||
void EnableNetwork(util::StatusCallback callback);
|
||||
|
||||
/** Starts listening to a query. */
|
||||
std::shared_ptr<QueryListener> ListenToQuery(
|
||||
Query query,
|
||||
ListenOptions options,
|
||||
ViewSnapshotSharedListener&& listener);
|
||||
|
||||
/** Stops listening to a query previously listened to. */
|
||||
void RemoveListener(const std::shared_ptr<core::QueryListener>& listener);
|
||||
|
||||
/**
|
||||
* Retrieves a document from the cache via the indicated callback. If the doc
|
||||
* doesn't exist, an error will be sent to the callback.
|
||||
*/
|
||||
void GetDocumentFromLocalCache(const api::DocumentReference& doc,
|
||||
api::DocumentSnapshotListener&& callback);
|
||||
|
||||
/**
|
||||
* Retrieves a (possibly empty) set of documents from the cache via the
|
||||
* indicated callback.
|
||||
*/
|
||||
void GetDocumentsFromLocalCache(const api::Query& query,
|
||||
api::QuerySnapshotListener&& callback);
|
||||
|
||||
/**
|
||||
* Write mutations. callback will be notified when it's written to the
|
||||
* backend.
|
||||
*/
|
||||
void WriteMutations(std::vector<model::Mutation>&& mutations,
|
||||
util::StatusCallback callback);
|
||||
|
||||
/**
|
||||
* Tries to execute the transaction in update_callback up to retries times.
|
||||
*/
|
||||
void Transaction(int retries,
|
||||
TransactionUpdateCallback update_callback,
|
||||
TransactionResultCallback result_callback);
|
||||
|
||||
/**
|
||||
* Adds a listener to be called when a snapshots-in-sync event fires.
|
||||
*/
|
||||
void AddSnapshotsInSyncListener(
|
||||
const std::shared_ptr<EventListener<util::Empty>>& listener);
|
||||
|
||||
/**
|
||||
* Removes a specific listener for snapshots-in-sync events.
|
||||
*/
|
||||
void RemoveSnapshotsInSyncListener(
|
||||
const std::shared_ptr<EventListener<util::Empty>>& listener);
|
||||
|
||||
/** The database ID of the DatabaseInfo this client was initialized with. */
|
||||
const model::DatabaseId& database_id() const {
|
||||
return database_info_.database_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch queue for user callbacks / events. This will often be the "Main
|
||||
* Dispatch Queue" of the app but the developer can configure it to a
|
||||
* different queue if they so choose.
|
||||
*/
|
||||
const std::shared_ptr<util::Executor>& user_executor() const {
|
||||
return user_executor_;
|
||||
}
|
||||
|
||||
void LoadBundle(std::unique_ptr<util::ByteStream> bundle_data,
|
||||
std::shared_ptr<api::LoadBundleTask> result_task);
|
||||
|
||||
void GetNamedQuery(const std::string& name, api::QueryCallback callback);
|
||||
|
||||
/** For usage in this class and testing only. */
|
||||
const std::shared_ptr<util::AsyncQueue>& worker_queue() const {
|
||||
return worker_queue_;
|
||||
}
|
||||
bool is_terminated() const;
|
||||
|
||||
private:
|
||||
FirestoreClient(const DatabaseInfo& database_info,
|
||||
std::shared_ptr<credentials::AuthCredentialsProvider>
|
||||
auth_credentials_provider,
|
||||
std::shared_ptr<credentials::AppCheckCredentialsProvider>
|
||||
app_check_credentials_provider,
|
||||
std::shared_ptr<util::Executor> user_executor,
|
||||
std::shared_ptr<util::AsyncQueue> worker_queue,
|
||||
std::unique_ptr<remote::FirebaseMetadataProvider>
|
||||
firebase_metadata_provider);
|
||||
|
||||
void Initialize(const credentials::User& user, const api::Settings& settings);
|
||||
|
||||
void VerifyNotTerminated();
|
||||
|
||||
void TerminateInternal();
|
||||
|
||||
void ScheduleLruGarbageCollection();
|
||||
|
||||
DatabaseInfo database_info_;
|
||||
std::shared_ptr<credentials::AppCheckCredentialsProvider>
|
||||
app_check_credentials_provider_;
|
||||
std::shared_ptr<credentials::AuthCredentialsProvider>
|
||||
auth_credentials_provider_;
|
||||
/**
|
||||
* Async queue responsible for all of our internal processing. When we get
|
||||
* incoming work from the user (via public API) or the network (incoming gRPC
|
||||
* messages), we should always dispatch onto this queue. This ensures our
|
||||
* internal data structures are never accessed from multiple threads
|
||||
* simultaneously.
|
||||
*/
|
||||
std::shared_ptr<util::AsyncQueue> worker_queue_;
|
||||
std::shared_ptr<util::Executor> user_executor_;
|
||||
|
||||
std::unique_ptr<remote::FirebaseMetadataProvider> firebase_metadata_provider_;
|
||||
|
||||
std::unique_ptr<local::Persistence> persistence_;
|
||||
std::unique_ptr<local::LocalStore> local_store_;
|
||||
std::unique_ptr<local::QueryEngine> query_engine_;
|
||||
std::unique_ptr<remote::ConnectivityMonitor> connectivity_monitor_;
|
||||
std::unique_ptr<remote::RemoteStore> remote_store_;
|
||||
std::unique_ptr<SyncEngine> sync_engine_;
|
||||
std::unique_ptr<EventManager> event_manager_;
|
||||
|
||||
std::chrono::milliseconds initial_gc_delay_ = std::chrono::minutes(1);
|
||||
std::chrono::milliseconds regular_gc_delay_ = std::chrono::minutes(5);
|
||||
bool gc_has_run_ = false;
|
||||
bool credentials_initialized_ = false;
|
||||
local::LruDelegate* _Nullable lru_delegate_;
|
||||
util::DelayedOperation lru_callback_;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_FIRESTORE_CLIENT_H_
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/core/in_filter.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/model/document.h"
|
||||
#include "Firestore/core/src/model/value_util.h"
|
||||
#include "Firestore/core/src/util/hard_assert.h"
|
||||
#include "absl/algorithm/container.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
using model::Contains;
|
||||
using model::Document;
|
||||
using model::FieldPath;
|
||||
using model::IsArray;
|
||||
using nanopb::SharedMessage;
|
||||
|
||||
using Operator = Filter::Operator;
|
||||
|
||||
class InFilter::Rep : public FieldFilter::Rep {
|
||||
public:
|
||||
Rep(FieldPath field, SharedMessage<google_firestore_v1_Value> value)
|
||||
: FieldFilter::Rep(std::move(field), Operator::In, std::move(value)) {
|
||||
HARD_ASSERT(IsArray(this->value()), "InFilter expects an ArrayValue");
|
||||
}
|
||||
|
||||
Type type() const override {
|
||||
return Type::kInFilter;
|
||||
}
|
||||
|
||||
bool Matches(const model::Document& doc) const override;
|
||||
};
|
||||
|
||||
InFilter::InFilter(const FieldPath& field,
|
||||
SharedMessage<google_firestore_v1_Value> value)
|
||||
: FieldFilter(std::make_shared<const Rep>(field, std::move(value))) {
|
||||
}
|
||||
|
||||
bool InFilter::Rep::Matches(const Document& doc) const {
|
||||
const google_firestore_v1_ArrayValue& array_value = value().array_value;
|
||||
absl::optional<google_firestore_v1_Value> maybe_lhs = doc->field(field());
|
||||
if (!maybe_lhs) return false;
|
||||
return Contains(array_value, *maybe_lhs);
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_CORE_IN_FILTER_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_IN_FILTER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h"
|
||||
#include "Firestore/core/src/core/field_filter.h"
|
||||
#include "Firestore/core/src/nanopb/message.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
|
||||
namespace model {
|
||||
class FieldPath;
|
||||
} // namespace model
|
||||
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* A Filter that implements the IN operator.
|
||||
*/
|
||||
class InFilter : public FieldFilter {
|
||||
public:
|
||||
/** Creates a new 'in' filter. Takes ownership of `value`. */
|
||||
InFilter(const model::FieldPath& field,
|
||||
nanopb::SharedMessage<google_firestore_v1_Value> value);
|
||||
|
||||
private:
|
||||
class Rep;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_IN_FILTER_H_
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/core/key_field_filter.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/model/document.h"
|
||||
#include "Firestore/core/src/model/document_key.h"
|
||||
#include "Firestore/core/src/model/value_util.h"
|
||||
#include "Firestore/core/src/nanopb/nanopb_util.h"
|
||||
#include "Firestore/core/src/util/hard_assert.h"
|
||||
#include "absl/algorithm/container.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
using model::Document;
|
||||
using model::DocumentKey;
|
||||
using model::FieldPath;
|
||||
using model::GetTypeOrder;
|
||||
using model::TypeOrder;
|
||||
using nanopb::SharedMessage;
|
||||
|
||||
using Operator = Filter::Operator;
|
||||
|
||||
class KeyFieldFilter::Rep : public FieldFilter::Rep {
|
||||
public:
|
||||
Rep(FieldPath field,
|
||||
Operator op,
|
||||
SharedMessage<google_firestore_v1_Value> value)
|
||||
: FieldFilter::Rep(std::move(field), op, std::move(value)) {
|
||||
HARD_ASSERT(GetTypeOrder(this->value()) == TypeOrder::kReference,
|
||||
"KeyFieldFilter expects a ReferenceValue");
|
||||
key_ = DocumentKey::FromName(
|
||||
nanopb::MakeString(this->value().reference_value));
|
||||
}
|
||||
|
||||
Type type() const override {
|
||||
return Type::kKeyFieldFilter;
|
||||
}
|
||||
|
||||
bool Matches(const model::Document& doc) const override;
|
||||
|
||||
private:
|
||||
DocumentKey key_;
|
||||
};
|
||||
|
||||
KeyFieldFilter::KeyFieldFilter(const FieldPath& field,
|
||||
Operator op,
|
||||
SharedMessage<google_firestore_v1_Value> value)
|
||||
: FieldFilter(std::make_shared<const Rep>(field, op, std::move(value))) {
|
||||
}
|
||||
|
||||
bool KeyFieldFilter::Rep::Matches(const Document& doc) const {
|
||||
return MatchesComparison(doc->key().CompareTo(key_));
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_CORE_KEY_FIELD_FILTER_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_KEY_FIELD_FILTER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h"
|
||||
#include "Firestore/core/src/core/field_filter.h"
|
||||
#include "Firestore/core/src/nanopb/message.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* A Filter that matches on key fields (i.e. '__name__').
|
||||
*/
|
||||
class KeyFieldFilter : public FieldFilter {
|
||||
public:
|
||||
/** Creates a new document key filter. Takes ownership of `value`. */
|
||||
KeyFieldFilter(const model::FieldPath& field,
|
||||
core::Filter::Operator op,
|
||||
nanopb::SharedMessage<google_firestore_v1_Value> value);
|
||||
|
||||
private:
|
||||
class Rep;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_KEY_FIELD_FILTER_H_
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/core/key_field_in_filter.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/model/document.h"
|
||||
#include "Firestore/core/src/model/document_key.h"
|
||||
#include "Firestore/core/src/model/value_util.h"
|
||||
#include "Firestore/core/src/nanopb/nanopb_util.h"
|
||||
#include "absl/algorithm/container.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
using model::Document;
|
||||
using model::DocumentKey;
|
||||
using model::DocumentKeyHash;
|
||||
using model::FieldPath;
|
||||
using model::GetTypeOrder;
|
||||
using model::IsArray;
|
||||
using model::TypeOrder;
|
||||
using nanopb::SharedMessage;
|
||||
|
||||
using Operator = Filter::Operator;
|
||||
|
||||
class KeyFieldInFilter::Rep : public FieldFilter::Rep {
|
||||
public:
|
||||
Rep(FieldPath field, SharedMessage<google_firestore_v1_Value> value)
|
||||
: FieldFilter::Rep(std::move(field), Operator::In, std::move(value)) {
|
||||
keys_ = ExtractDocumentKeysFromValue(this->value());
|
||||
}
|
||||
|
||||
Type type() const override {
|
||||
return Type::kKeyFieldInFilter;
|
||||
}
|
||||
|
||||
bool Matches(const model::Document& doc) const override;
|
||||
|
||||
private:
|
||||
std::unordered_set<DocumentKey, DocumentKeyHash> keys_;
|
||||
};
|
||||
|
||||
KeyFieldInFilter::KeyFieldInFilter(
|
||||
const FieldPath& field, SharedMessage<google_firestore_v1_Value> value)
|
||||
: FieldFilter(std::make_shared<const Rep>(field, std::move(value))) {
|
||||
}
|
||||
|
||||
bool KeyFieldInFilter::Rep::Matches(const Document& doc) const {
|
||||
return keys_.find(doc->key()) != keys_.end();
|
||||
}
|
||||
|
||||
std::unordered_set<DocumentKey, DocumentKeyHash>
|
||||
KeyFieldInFilter::ExtractDocumentKeysFromValue(
|
||||
const google_firestore_v1_Value& value) {
|
||||
HARD_ASSERT(IsArray(value),
|
||||
"Comparing on key with In/NotIn, but the value was not an Array");
|
||||
std::unordered_set<DocumentKey, DocumentKeyHash> keys;
|
||||
const google_firestore_v1_ArrayValue& array_value = value.array_value;
|
||||
for (pb_size_t i = 0; i < array_value.values_count; ++i) {
|
||||
HARD_ASSERT(GetTypeOrder(array_value.values[i]) == TypeOrder::kReference,
|
||||
"Comparing on key with In/NotIn, but an array value was not"
|
||||
" a Reference");
|
||||
keys.insert(DocumentKey::FromName(
|
||||
nanopb::MakeString(array_value.values[i].reference_value)));
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_CORE_KEY_FIELD_IN_FILTER_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_KEY_FIELD_IN_FILTER_H_
|
||||
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h"
|
||||
#include "Firestore/core/src/core/field_filter.h"
|
||||
#include "Firestore/core/src/model/document.h"
|
||||
#include "Firestore/core/src/model/model_fwd.h"
|
||||
#include "Firestore/core/src/nanopb/message.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* A Filter that matches on an array of key fields.
|
||||
*/
|
||||
class KeyFieldInFilter : public FieldFilter {
|
||||
public:
|
||||
/** Creates a new document keys filter. Takes ownership of `value`. */
|
||||
KeyFieldInFilter(const model::FieldPath& field,
|
||||
nanopb::SharedMessage<google_firestore_v1_Value> value);
|
||||
|
||||
private:
|
||||
class Rep;
|
||||
|
||||
static std::unordered_set<model::DocumentKey, model::DocumentKeyHash>
|
||||
ExtractDocumentKeysFromValue(const google_firestore_v1_Value& value);
|
||||
|
||||
friend class KeyFieldNotInFilter;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_KEY_FIELD_IN_FILTER_H_
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2020 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 "Firestore/core/src/core/key_field_not_in_filter.h"
|
||||
#include "Firestore/core/src/core/key_field_in_filter.h"
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/model/document.h"
|
||||
#include "Firestore/core/src/model/document_key.h"
|
||||
#include "absl/algorithm/container.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
using model::Document;
|
||||
using model::DocumentKey;
|
||||
using model::DocumentKeyHash;
|
||||
using model::FieldPath;
|
||||
using nanopb::SharedMessage;
|
||||
|
||||
using Operator = Filter::Operator;
|
||||
|
||||
class KeyFieldNotInFilter::Rep : public FieldFilter::Rep {
|
||||
public:
|
||||
Rep(FieldPath field, SharedMessage<google_firestore_v1_Value> value)
|
||||
: FieldFilter::Rep(std::move(field), Operator::NotIn, std::move(value)) {
|
||||
keys_ = KeyFieldInFilter::ExtractDocumentKeysFromValue(this->value());
|
||||
}
|
||||
|
||||
Type type() const override {
|
||||
return Type::kKeyFieldInFilter;
|
||||
}
|
||||
|
||||
bool Matches(const model::Document& doc) const override;
|
||||
|
||||
private:
|
||||
std::unordered_set<DocumentKey, DocumentKeyHash> keys_;
|
||||
};
|
||||
|
||||
KeyFieldNotInFilter::KeyFieldNotInFilter(
|
||||
const FieldPath& field, SharedMessage<google_firestore_v1_Value> value)
|
||||
: FieldFilter(std::make_shared<const Rep>(field, std::move(value))) {
|
||||
}
|
||||
|
||||
bool KeyFieldNotInFilter::Rep::Matches(const Document& doc) const {
|
||||
return keys_.find(doc->key()) == keys_.end();
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2020 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 FIRESTORE_CORE_SRC_CORE_KEY_FIELD_NOT_IN_FILTER_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_KEY_FIELD_NOT_IN_FILTER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h"
|
||||
#include "Firestore/core/src/core/field_filter.h"
|
||||
#include "Firestore/core/src/model/model_fwd.h"
|
||||
#include "Firestore/core/src/nanopb/message.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* A Filter that matches on key fields not present within an array.
|
||||
*/
|
||||
class KeyFieldNotInFilter : public FieldFilter {
|
||||
public:
|
||||
/** Creates a new document keys not-in filter. Takes ownership of `value`. */
|
||||
KeyFieldNotInFilter(const model::FieldPath& field,
|
||||
nanopb::SharedMessage<google_firestore_v1_Value> value);
|
||||
|
||||
private:
|
||||
class Rep;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_KEY_FIELD_NOT_IN_FILTER_H_
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_CORE_LISTEN_OPTIONS_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_LISTEN_OPTIONS_H_
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
class ListenOptions {
|
||||
public:
|
||||
ListenOptions() = default;
|
||||
|
||||
/**
|
||||
* Creates a new ListenOptions.
|
||||
*
|
||||
* @param include_query_metadata_changes Raise events when only metadata of
|
||||
* the query changes.
|
||||
* @param include_document_metadata_changes Raise events when only metadata of
|
||||
* documents changes.
|
||||
* @param wait_for_sync_when_online Wait for a sync with the server when
|
||||
* online, but still raise events while offline
|
||||
*/
|
||||
ListenOptions(bool include_query_metadata_changes,
|
||||
bool include_document_metadata_changes,
|
||||
bool wait_for_sync_when_online)
|
||||
: include_query_metadata_changes_(include_query_metadata_changes),
|
||||
include_document_metadata_changes_(include_document_metadata_changes),
|
||||
wait_for_sync_when_online_(wait_for_sync_when_online) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a default ListenOptions, with metadata changes and
|
||||
* wait_for_sync_when_online disabled.
|
||||
*/
|
||||
static ListenOptions DefaultOptions() {
|
||||
return ListenOptions(
|
||||
/*include_query_metadata_changes=*/false,
|
||||
/*include_document_metadata_changes=*/false,
|
||||
/*wait_for_sync_when_online=*/false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ListenOptions which optionally includes both query and document
|
||||
* metadata changes.
|
||||
*/
|
||||
static ListenOptions FromIncludeMetadataChanges(
|
||||
bool include_metadata_changes) {
|
||||
return ListenOptions(
|
||||
/*include_query_metadata_changes=*/include_metadata_changes,
|
||||
/*include_document_metadata_changes=*/include_metadata_changes,
|
||||
/*wait_for_sync_when_online=*/false);
|
||||
}
|
||||
|
||||
bool include_query_metadata_changes() const {
|
||||
return include_query_metadata_changes_;
|
||||
}
|
||||
|
||||
bool include_document_metadata_changes() const {
|
||||
return include_document_metadata_changes_;
|
||||
}
|
||||
|
||||
bool wait_for_sync_when_online() const {
|
||||
return wait_for_sync_when_online_;
|
||||
}
|
||||
|
||||
private:
|
||||
bool include_query_metadata_changes_ = false;
|
||||
bool include_document_metadata_changes_ = false;
|
||||
bool wait_for_sync_when_online_ = false;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_LISTEN_OPTIONS_H_
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2020 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 "Firestore/core/src/core/not_in_filter.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/model/document.h"
|
||||
#include "Firestore/core/src/model/value_util.h"
|
||||
#include "Firestore/core/src/util/hard_assert.h"
|
||||
#include "absl/algorithm/container.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
using model::Contains;
|
||||
using model::Document;
|
||||
using model::FieldPath;
|
||||
using model::IsArray;
|
||||
using model::NullValue;
|
||||
using nanopb::SharedMessage;
|
||||
|
||||
using Operator = Filter::Operator;
|
||||
|
||||
class NotInFilter::Rep : public FieldFilter::Rep {
|
||||
public:
|
||||
Rep(FieldPath field, SharedMessage<google_firestore_v1_Value> value)
|
||||
: FieldFilter::Rep(std::move(field), Operator::NotIn, std::move(value)) {
|
||||
HARD_ASSERT(IsArray(this->value()), "NotInFilter expects an ArrayValue");
|
||||
}
|
||||
|
||||
Type type() const override {
|
||||
return Type::kNotInFilter;
|
||||
}
|
||||
|
||||
bool Matches(const model::Document& doc) const override;
|
||||
};
|
||||
|
||||
NotInFilter::NotInFilter(const FieldPath& field,
|
||||
SharedMessage<google_firestore_v1_Value> value)
|
||||
: FieldFilter(std::make_shared<const Rep>(field, std::move(value))) {
|
||||
}
|
||||
|
||||
bool NotInFilter::Rep::Matches(const Document& doc) const {
|
||||
const google_firestore_v1_ArrayValue& array_value = value().array_value;
|
||||
if (Contains(array_value, *NullValue())) {
|
||||
return false;
|
||||
}
|
||||
absl::optional<google_firestore_v1_Value> maybe_lhs = doc->field(field());
|
||||
return maybe_lhs && !Contains(array_value, *maybe_lhs);
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2020 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 FIRESTORE_CORE_SRC_CORE_NOT_IN_FILTER_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_NOT_IN_FILTER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h"
|
||||
#include "Firestore/core/src/core/field_filter.h"
|
||||
#include "Firestore/core/src/model/model_fwd.h"
|
||||
#include "Firestore/core/src/nanopb/message.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* A Filter that implements the not-in operator.
|
||||
*/
|
||||
class NotInFilter : public FieldFilter {
|
||||
public:
|
||||
/** Creates a new not-in filter. Takes ownership of `value`. */
|
||||
NotInFilter(const model::FieldPath& field,
|
||||
nanopb::SharedMessage<google_firestore_v1_Value> value);
|
||||
|
||||
private:
|
||||
class Rep;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_NOT_IN_FILTER_H_
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_CORE_OPERATOR_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_OPERATOR_H_
|
||||
|
||||
#include "Firestore/core/src/core/filter.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
inline bool IsArrayOperator(Filter::Operator op) {
|
||||
return op == Filter::Operator::ArrayContains ||
|
||||
op == Filter::Operator::ArrayContainsAny;
|
||||
}
|
||||
|
||||
inline bool IsDisjunctiveOperator(Filter::Operator op) {
|
||||
return op == Filter::Operator::In ||
|
||||
op == Filter::Operator::ArrayContainsAny ||
|
||||
op == Filter::Operator::NotIn;
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_OPERATOR_H_
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/core/order_by.h"
|
||||
|
||||
#include <ostream>
|
||||
|
||||
#include "Firestore/core/src/model/document.h"
|
||||
#include "Firestore/core/src/model/value_util.h"
|
||||
#include "Firestore/core/src/util/string_format.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
using model::Document;
|
||||
using model::FieldPath;
|
||||
using util::ComparisonResult;
|
||||
|
||||
ComparisonResult OrderBy::Compare(const Document& lhs,
|
||||
const Document& rhs) const {
|
||||
ComparisonResult result;
|
||||
if (field_ == FieldPath::KeyFieldPath()) {
|
||||
result = lhs->key().CompareTo(rhs->key());
|
||||
} else {
|
||||
absl::optional<google_firestore_v1_Value> value1 = lhs->field(field_);
|
||||
absl::optional<google_firestore_v1_Value> value2 = rhs->field(field_);
|
||||
HARD_ASSERT(value1.has_value() && value2.has_value(),
|
||||
"Trying to compare documents on fields that don't exist.");
|
||||
result = model::Compare(*value1, *value2);
|
||||
}
|
||||
|
||||
return direction_.ApplyTo(result);
|
||||
}
|
||||
|
||||
std::string OrderBy::CanonicalId() const {
|
||||
return absl::StrCat(field_.CanonicalString(), direction_.CanonicalId());
|
||||
}
|
||||
|
||||
std::string OrderBy::ToString() const {
|
||||
return util::StringFormat("OrderBy(path=%s, dir=%s)",
|
||||
field_.CanonicalString(), direction_.CanonicalId());
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const OrderBy& order) {
|
||||
return os << order.ToString();
|
||||
}
|
||||
|
||||
bool operator==(const OrderBy& lhs, const OrderBy& rhs) {
|
||||
return lhs.field() == rhs.field() && lhs.direction() == rhs.direction();
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_CORE_ORDER_BY_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_ORDER_BY_H_
|
||||
|
||||
#include <iosfwd>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/core/direction.h"
|
||||
#include "Firestore/core/src/model/field_path.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
|
||||
namespace immutable {
|
||||
template <typename T>
|
||||
class AppendOnlyList;
|
||||
} // namespace immutable
|
||||
|
||||
namespace model {
|
||||
class Document;
|
||||
} // namespace model
|
||||
|
||||
namespace util {
|
||||
enum class ComparisonResult;
|
||||
} // namespace util
|
||||
|
||||
namespace core {
|
||||
|
||||
/** OrderBy is a field and direction by which to order query results. */
|
||||
class OrderBy {
|
||||
public:
|
||||
static std::shared_ptr<OrderBy> Create(model::FieldPath field,
|
||||
Direction direction) {
|
||||
return std::make_shared<OrderBy>(std::move(field), direction);
|
||||
}
|
||||
|
||||
OrderBy() = default;
|
||||
|
||||
/** Creates a new sort order with the given field and direction. */
|
||||
OrderBy(model::FieldPath field, Direction direction)
|
||||
: field_(std::move(field)), direction_(direction) {
|
||||
}
|
||||
|
||||
/** The field by which to sort. */
|
||||
const model::FieldPath& field() const {
|
||||
return field_;
|
||||
}
|
||||
|
||||
/** The direction of the sort. */
|
||||
const Direction& direction() const {
|
||||
return direction_;
|
||||
}
|
||||
|
||||
bool ascending() const {
|
||||
return direction_ == Direction::Ascending;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two documents based on the field and direction of this sort
|
||||
* order.
|
||||
*/
|
||||
util::ComparisonResult Compare(const model::Document& lhs,
|
||||
const model::Document& rhs) const;
|
||||
|
||||
/** A unique ID identifying the filter; used when serializing queries. */
|
||||
std::string CanonicalId() const;
|
||||
|
||||
std::string ToString() const;
|
||||
|
||||
private:
|
||||
model::FieldPath field_;
|
||||
Direction direction_;
|
||||
};
|
||||
|
||||
/** A list of OrderBys, as used in Queries and elsewhere. */
|
||||
using OrderByList = immutable::AppendOnlyList<OrderBy>;
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const OrderBy& order);
|
||||
|
||||
bool operator==(const OrderBy& lhs, const OrderBy& rhs);
|
||||
|
||||
inline bool operator!=(const OrderBy& lhs, const OrderBy& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_ORDER_BY_H_
|
||||
+358
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
* Copyright 2018 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/core/query.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ostream>
|
||||
|
||||
#include "Firestore/core/src/core/bound.h"
|
||||
#include "Firestore/core/src/core/field_filter.h"
|
||||
#include "Firestore/core/src/core/operator.h"
|
||||
#include "Firestore/core/src/model/document.h"
|
||||
#include "Firestore/core/src/model/document_key.h"
|
||||
#include "Firestore/core/src/model/document_set.h"
|
||||
#include "Firestore/core/src/model/field_path.h"
|
||||
#include "Firestore/core/src/model/resource_path.h"
|
||||
#include "Firestore/core/src/util/equality.h"
|
||||
#include "Firestore/core/src/util/hard_assert.h"
|
||||
#include "Firestore/core/src/util/hashing.h"
|
||||
#include "absl/algorithm/container.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
using Operator = Filter::Operator;
|
||||
using Type = Filter::Type;
|
||||
|
||||
using model::Document;
|
||||
using model::DocumentComparator;
|
||||
using model::DocumentKey;
|
||||
using model::FieldPath;
|
||||
using model::ResourcePath;
|
||||
using util::ComparisonResult;
|
||||
|
||||
Query::Query(ResourcePath path, std::string collection_group)
|
||||
: path_(std::move(path)),
|
||||
collection_group_(
|
||||
std::make_shared<const std::string>(std::move(collection_group))) {
|
||||
}
|
||||
|
||||
// MARK: - Accessors
|
||||
|
||||
bool Query::IsDocumentQuery() const {
|
||||
return DocumentKey::IsDocumentKey(path_) && !collection_group_ &&
|
||||
filters_.empty();
|
||||
}
|
||||
|
||||
bool Query::MatchesAllDocuments() const {
|
||||
return filters_.empty() && limit_ == Target::kNoLimit && !start_at_ &&
|
||||
!end_at_ &&
|
||||
(explicit_order_bys_.empty() ||
|
||||
(explicit_order_bys_.size() == 1 &&
|
||||
explicit_order_bys_.front().field().IsKeyFieldPath()));
|
||||
}
|
||||
|
||||
const FieldPath* Query::InequalityFilterField() const {
|
||||
for (const auto& filter : filters_) {
|
||||
if (filter.IsInequality()) {
|
||||
return &filter.field();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
absl::optional<Operator> Query::FindOperator(
|
||||
const std::vector<Operator>& ops) const {
|
||||
for (const auto& filter : filters_) {
|
||||
if (filter.IsAFieldFilter()) {
|
||||
FieldFilter relation_filter(filter);
|
||||
if (absl::c_linear_search(ops, relation_filter.op())) {
|
||||
return relation_filter.op();
|
||||
}
|
||||
}
|
||||
}
|
||||
return absl::nullopt;
|
||||
}
|
||||
|
||||
const OrderByList& Query::order_bys() const {
|
||||
if (memoized_order_bys_.empty()) {
|
||||
const FieldPath* inequality_field = InequalityFilterField();
|
||||
const FieldPath* first_order_by_field = FirstOrderByField();
|
||||
if (inequality_field && !first_order_by_field) {
|
||||
// In order to implicitly add key ordering, we must also add the
|
||||
// inequality filter field for it to be a valid query. Note that the
|
||||
// default inequality field and key ordering is ascending.
|
||||
if (inequality_field->IsKeyFieldPath()) {
|
||||
memoized_order_bys_ = {
|
||||
OrderBy(FieldPath::KeyFieldPath(), Direction::Ascending),
|
||||
};
|
||||
} else {
|
||||
memoized_order_bys_ = {
|
||||
OrderBy(*inequality_field, Direction::Ascending),
|
||||
OrderBy(FieldPath::KeyFieldPath(), Direction::Ascending),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
HARD_ASSERT(
|
||||
!inequality_field || *inequality_field == *first_order_by_field,
|
||||
"First orderBy %s should match inequality field %s.",
|
||||
first_order_by_field->CanonicalString(),
|
||||
inequality_field->CanonicalString());
|
||||
|
||||
OrderByList result = explicit_order_bys_;
|
||||
|
||||
bool found_explicit_key_order = false;
|
||||
for (const OrderBy& order_by : explicit_order_bys_) {
|
||||
if (order_by.field().IsKeyFieldPath()) {
|
||||
found_explicit_key_order = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_explicit_key_order) {
|
||||
// The direction of the implicit key ordering always matches the
|
||||
// direction of the last explicit sort order
|
||||
Direction last_direction = explicit_order_bys_.empty()
|
||||
? Direction::Ascending
|
||||
: explicit_order_bys_.back().direction();
|
||||
result = result.emplace_back(FieldPath::KeyFieldPath(), last_direction);
|
||||
}
|
||||
|
||||
memoized_order_bys_ = std::move(result);
|
||||
}
|
||||
}
|
||||
return memoized_order_bys_;
|
||||
}
|
||||
|
||||
const FieldPath* Query::FirstOrderByField() const {
|
||||
if (explicit_order_bys_.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &explicit_order_bys_.front().field();
|
||||
}
|
||||
|
||||
LimitType Query::limit_type() const {
|
||||
return limit_type_;
|
||||
}
|
||||
|
||||
int32_t Query::limit() const {
|
||||
HARD_ASSERT(limit_type_ != LimitType::None,
|
||||
"Called limit() when no limit was set");
|
||||
return limit_;
|
||||
}
|
||||
|
||||
// MARK: - Builder methods
|
||||
|
||||
Query Query::AddingFilter(Filter filter) const {
|
||||
HARD_ASSERT(!IsDocumentQuery(), "No filter is allowed for document query");
|
||||
|
||||
const FieldPath* new_inequality_field = nullptr;
|
||||
if (filter.IsInequality()) {
|
||||
new_inequality_field = &filter.field();
|
||||
}
|
||||
const FieldPath* query_inequality_field = InequalityFilterField();
|
||||
HARD_ASSERT(!query_inequality_field || !new_inequality_field ||
|
||||
*query_inequality_field == *new_inequality_field,
|
||||
"Query must only have one inequality field.");
|
||||
|
||||
// TODO(rsgowman): ensure first orderby must match inequality field
|
||||
|
||||
return Query(path_, collection_group_, filters_.push_back(std::move(filter)),
|
||||
explicit_order_bys_, limit_, limit_type_, start_at_, end_at_);
|
||||
}
|
||||
|
||||
Query Query::AddingOrderBy(OrderBy order_by) const {
|
||||
HARD_ASSERT(!IsDocumentQuery(), "No ordering is allowed for document query");
|
||||
|
||||
if (explicit_order_bys_.empty()) {
|
||||
const FieldPath* inequality = InequalityFilterField();
|
||||
HARD_ASSERT(inequality == nullptr || *inequality == order_by.field(),
|
||||
"First OrderBy must match inequality field.");
|
||||
}
|
||||
|
||||
return Query(path_, collection_group_, filters_,
|
||||
explicit_order_bys_.push_back(std::move(order_by)), limit_,
|
||||
limit_type_, start_at_, end_at_);
|
||||
}
|
||||
|
||||
Query Query::WithLimitToFirst(int32_t limit) const {
|
||||
return Query(path_, collection_group_, filters_, explicit_order_bys_, limit,
|
||||
LimitType::First, start_at_, end_at_);
|
||||
}
|
||||
|
||||
Query Query::WithLimitToLast(int32_t limit) const {
|
||||
return Query(path_, collection_group_, filters_, explicit_order_bys_, limit,
|
||||
LimitType::Last, start_at_, end_at_);
|
||||
}
|
||||
|
||||
Query Query::StartingAt(Bound bound) const {
|
||||
return Query(path_, collection_group_, filters_, explicit_order_bys_, limit_,
|
||||
limit_type_, std::move(bound), end_at_);
|
||||
}
|
||||
|
||||
Query Query::EndingAt(Bound bound) const {
|
||||
return Query(path_, collection_group_, filters_, explicit_order_bys_, limit_,
|
||||
limit_type_, start_at_, std::move(bound));
|
||||
}
|
||||
|
||||
Query Query::AsCollectionQueryAtPath(ResourcePath path) const {
|
||||
return Query(path, /*collection_group=*/nullptr, filters_,
|
||||
explicit_order_bys_, limit_, limit_type_, start_at_, end_at_);
|
||||
}
|
||||
|
||||
// MARK: - Matching
|
||||
|
||||
bool Query::Matches(const Document& doc) const {
|
||||
return doc->is_found_document() && MatchesPathAndCollectionGroup(doc) &&
|
||||
MatchesOrderBy(doc) && MatchesFilters(doc) && MatchesBounds(doc);
|
||||
}
|
||||
|
||||
bool Query::MatchesPathAndCollectionGroup(const Document& doc) const {
|
||||
const ResourcePath& doc_path = doc->key().path();
|
||||
if (collection_group_) {
|
||||
// NOTE: path_ is currently always empty since we don't expose Collection
|
||||
// Group queries rooted at a document path yet.
|
||||
return doc->key().HasCollectionId(*collection_group_) &&
|
||||
path_.IsPrefixOf(doc_path);
|
||||
} else if (DocumentKey::IsDocumentKey(path_)) {
|
||||
// Exact match for document queries.
|
||||
return path_ == doc_path;
|
||||
} else {
|
||||
// Shallow ancestor queries by default.
|
||||
return path_.IsImmediateParentOf(doc_path);
|
||||
}
|
||||
}
|
||||
|
||||
bool Query::MatchesFilters(const Document& doc) const {
|
||||
for (const auto& filter : filters_) {
|
||||
if (!filter.Matches(doc)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Query::MatchesOrderBy(const Document& doc) const {
|
||||
for (const OrderBy& order_by : explicit_order_bys_) {
|
||||
const FieldPath& field_path = order_by.field();
|
||||
// order by key always matches
|
||||
if (field_path != FieldPath::KeyFieldPath() &&
|
||||
doc->field(field_path) == absl::nullopt) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Query::MatchesBounds(const Document& doc) const {
|
||||
const OrderByList& ordering = order_bys();
|
||||
if (start_at_ && !start_at_->SortsBeforeDocument(ordering, doc)) {
|
||||
return false;
|
||||
}
|
||||
if (end_at_ && end_at_->SortsBeforeDocument(ordering, doc)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
model::DocumentComparator Query::Comparator() const {
|
||||
OrderByList ordering = order_bys();
|
||||
|
||||
bool has_key_ordering = false;
|
||||
for (const OrderBy& order_by : ordering) {
|
||||
if (order_by.field() == FieldPath::KeyFieldPath()) {
|
||||
has_key_ordering = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
HARD_ASSERT(has_key_ordering,
|
||||
"QueryComparator needs to have a key ordering.");
|
||||
|
||||
return DocumentComparator(
|
||||
[ordering](const Document& doc1, const Document& doc2) {
|
||||
for (const OrderBy& order_by : ordering) {
|
||||
ComparisonResult comp = order_by.Compare(doc1, doc2);
|
||||
if (!util::Same(comp)) return comp;
|
||||
}
|
||||
return ComparisonResult::Same;
|
||||
});
|
||||
}
|
||||
|
||||
const std::string Query::CanonicalId() const {
|
||||
if (limit_type_ != LimitType::None) {
|
||||
return absl::StrCat(ToTarget().CanonicalId(),
|
||||
"|lt:", (limit_type_ == LimitType::Last) ? "l" : "f");
|
||||
}
|
||||
return ToTarget().CanonicalId();
|
||||
}
|
||||
|
||||
size_t Query::Hash() const {
|
||||
return util::Hash(CanonicalId());
|
||||
}
|
||||
|
||||
std::string Query::ToString() const {
|
||||
return absl::StrCat("Query(canonical_id=", CanonicalId(), ")");
|
||||
}
|
||||
|
||||
const Target& Query::ToTarget() const& {
|
||||
if (memoized_target == nullptr) {
|
||||
if (limit_type_ == LimitType::Last) {
|
||||
// Flip the orderBy directions since we want the last results
|
||||
OrderByList new_order_bys;
|
||||
for (const auto& order_by : order_bys()) {
|
||||
Direction dir = order_by.direction() == Direction::Descending
|
||||
? Direction::Ascending
|
||||
: Direction::Descending;
|
||||
new_order_bys = new_order_bys.push_back(OrderBy(order_by.field(), dir));
|
||||
}
|
||||
|
||||
// We need to swap the cursors to match the now-flipped query ordering.
|
||||
auto new_start_at = end_at_
|
||||
? absl::optional<Bound>{Bound::FromValue(
|
||||
end_at_->position(), !end_at_->before())}
|
||||
: absl::nullopt;
|
||||
auto new_end_at = start_at_
|
||||
? absl::optional<Bound>{Bound::FromValue(
|
||||
start_at_->position(), !start_at_->before())}
|
||||
: absl::nullopt;
|
||||
|
||||
Target target(path(), collection_group(), filters(), new_order_bys,
|
||||
limit_, new_start_at, new_end_at);
|
||||
memoized_target = std::make_shared<Target>(std::move(target));
|
||||
} else {
|
||||
Target target(path(), collection_group(), filters(), order_bys(), limit_,
|
||||
start_at(), end_at());
|
||||
memoized_target = std::make_shared<Target>(std::move(target));
|
||||
}
|
||||
}
|
||||
|
||||
return *memoized_target;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Query& query) {
|
||||
return os << query.ToString();
|
||||
}
|
||||
|
||||
bool operator==(const Query& lhs, const Query& rhs) {
|
||||
return (lhs.limit_type_ == rhs.limit_type_) &&
|
||||
(lhs.ToTarget() == rhs.ToTarget());
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
/*
|
||||
* Copyright 2018 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_CORE_QUERY_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_QUERY_H_
|
||||
|
||||
#include <iosfwd>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Firestore/core/src/core/filter.h"
|
||||
#include "Firestore/core/src/core/order_by.h"
|
||||
#include "Firestore/core/src/core/target.h"
|
||||
#include "Firestore/core/src/immutable/append_only_list.h"
|
||||
#include "Firestore/core/src/model/model_fwd.h"
|
||||
#include "Firestore/core/src/model/resource_path.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
class Bound;
|
||||
|
||||
using CollectionGroupId = std::shared_ptr<const std::string>;
|
||||
|
||||
enum class LimitType { None, First, Last };
|
||||
|
||||
/**
|
||||
* Encapsulates all the query attributes we support in the SDK. It represents
|
||||
* query features visible to user, and can be run against the LocalStore.
|
||||
* `Query` is first convert to `Target` to run against RemoteStore to query
|
||||
* backend results, because `Target` encapsulates features backend knows about.
|
||||
*/
|
||||
class Query {
|
||||
public:
|
||||
Query() = default;
|
||||
|
||||
explicit Query(model::ResourcePath path,
|
||||
CollectionGroupId collection_group = nullptr)
|
||||
: path_(std::move(path)), collection_group_(std::move(collection_group)) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a Query with a path and optional additional query constraints.
|
||||
* Path must currently be empty if this is a collection group query.
|
||||
*/
|
||||
Query(model::ResourcePath path,
|
||||
CollectionGroupId collection_group,
|
||||
FilterList filters,
|
||||
OrderByList explicit_order_bys,
|
||||
int32_t limit,
|
||||
LimitType limit_type,
|
||||
absl::optional<Bound> start_at,
|
||||
absl::optional<Bound> end_at)
|
||||
: path_(std::move(path)),
|
||||
collection_group_(std::move(collection_group)),
|
||||
filters_(std::move(filters)),
|
||||
explicit_order_bys_(std::move(explicit_order_bys)),
|
||||
limit_(limit),
|
||||
limit_type_(limit_type),
|
||||
start_at_(std::move(start_at)),
|
||||
end_at_(std::move(end_at)) {
|
||||
}
|
||||
|
||||
Query(model::ResourcePath path, std::string collection_group);
|
||||
|
||||
// MARK: - Accessors
|
||||
|
||||
/** The base path of the query. */
|
||||
const model::ResourcePath& path() const {
|
||||
return path_;
|
||||
}
|
||||
|
||||
/** The collection group of the query, if any. */
|
||||
const std::shared_ptr<const std::string>& collection_group() const {
|
||||
return collection_group_;
|
||||
}
|
||||
|
||||
/** Returns true if this Query is for a specific document. */
|
||||
bool IsDocumentQuery() const;
|
||||
|
||||
/** Returns true if this Query is a collection group query. */
|
||||
bool IsCollectionGroupQuery() const {
|
||||
return collection_group_ != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this query does not specify any query constraints that
|
||||
* could remove results.
|
||||
*/
|
||||
bool MatchesAllDocuments() const;
|
||||
|
||||
/** The filters on the documents returned by the query. */
|
||||
const FilterList& filters() const {
|
||||
return filters_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the field of the first filter on this Query that's an inequality,
|
||||
* or nullptr if there are no inequalities.
|
||||
*/
|
||||
const model::FieldPath* InequalityFilterField() const;
|
||||
|
||||
/**
|
||||
* Checks if any of the provided filter operators are included in the query
|
||||
* and returns the first one that is, or null if none are.
|
||||
*/
|
||||
absl::optional<Filter::Operator> FindOperator(
|
||||
const std::vector<Filter::Operator>& ops) const;
|
||||
|
||||
/**
|
||||
* Returns the list of ordering constraints that were explicitly requested on
|
||||
* the query by the user.
|
||||
*
|
||||
* Note that the actual query performed might add additional sort orders to
|
||||
* match the behavior of the backend.
|
||||
*/
|
||||
const OrderByList& explicit_order_bys() const {
|
||||
return explicit_order_bys_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full list of ordering constraints on the query.
|
||||
*
|
||||
* This might include additional sort orders added implicitly to match the
|
||||
* backend behavior.
|
||||
*/
|
||||
const OrderByList& order_bys() const;
|
||||
|
||||
/** Returns the first field in an order-by constraint, or nullptr if none. */
|
||||
const model::FieldPath* FirstOrderByField() const;
|
||||
|
||||
bool has_limit_to_first() const {
|
||||
return limit_type_ == LimitType::First && limit_ != Target::kNoLimit;
|
||||
}
|
||||
|
||||
bool has_limit_to_last() const {
|
||||
return limit_type_ == LimitType::Last && limit_ != Target::kNoLimit;
|
||||
}
|
||||
|
||||
LimitType limit_type() const;
|
||||
|
||||
int32_t limit() const;
|
||||
|
||||
const absl::optional<Bound>& start_at() const {
|
||||
return start_at_;
|
||||
}
|
||||
|
||||
const absl::optional<Bound>& end_at() const {
|
||||
return end_at_;
|
||||
}
|
||||
|
||||
// MARK: - Builder methods
|
||||
|
||||
/**
|
||||
* Returns a copy of this Query object with the additional specified filter.
|
||||
*/
|
||||
Query AddingFilter(Filter filter) const;
|
||||
|
||||
/**
|
||||
* Returns a copy of this Query object with the additional specified order by.
|
||||
*/
|
||||
Query AddingOrderBy(OrderBy order_by) const;
|
||||
|
||||
/**
|
||||
* Returns a new `Query` that returns the first matching documents up to
|
||||
* the specified number.
|
||||
*
|
||||
* @param limit The maximum number of results to return. If
|
||||
* `limit == kNoLimit`, then no limit is applied. Otherwise, if
|
||||
* `limit <= 0`, behavior is unspecified.
|
||||
*/
|
||||
Query WithLimitToFirst(int32_t limit) const;
|
||||
|
||||
/**
|
||||
* Returns a new `Query` that returns the last matching documents up to
|
||||
* the specified number.
|
||||
*
|
||||
* You must specify at least one `OrderBy` clause for `LimitToLast` queries,
|
||||
* it is an error otherwise.
|
||||
*
|
||||
* @param limit The maximum number of results to return. If
|
||||
* `limit == kNoLimit`, then no limit is applied. Otherwise, if
|
||||
* `limit <= 0`, behavior is unspecified.
|
||||
*/
|
||||
Query WithLimitToLast(int32_t limit) const;
|
||||
|
||||
/**
|
||||
* Returns a copy of this Query starting at the provided bound.
|
||||
*/
|
||||
Query StartingAt(Bound bound) const;
|
||||
|
||||
/**
|
||||
* Returns a copy of this Query ending at the provided bound.
|
||||
*/
|
||||
Query EndingAt(Bound bound) const;
|
||||
|
||||
// MARK: - Matching
|
||||
|
||||
/**
|
||||
* Converts this collection group query into a collection query at a specific
|
||||
* path. This is used when executing collection group queries, since we have
|
||||
* to split the query into a set of collection queries, one for each
|
||||
* collection in the group.
|
||||
*/
|
||||
Query AsCollectionQueryAtPath(model::ResourcePath path) const;
|
||||
|
||||
/** Returns true if the document matches the constraints of this query. */
|
||||
bool Matches(const model::Document& doc) const;
|
||||
|
||||
/**
|
||||
* Returns a comparator that will sort documents according to the order by
|
||||
* clauses in this query.
|
||||
*/
|
||||
model::DocumentComparator Comparator() const;
|
||||
|
||||
const std::string CanonicalId() const;
|
||||
|
||||
std::string ToString() const;
|
||||
|
||||
/**
|
||||
* Returns a `Target` instance this query will be mapped to in backend
|
||||
* and local store.
|
||||
*/
|
||||
const Target& ToTarget() const&;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const Query& query);
|
||||
|
||||
friend bool operator==(const Query& lhs, const Query& rhs);
|
||||
size_t Hash() const;
|
||||
|
||||
private:
|
||||
bool MatchesPathAndCollectionGroup(const model::Document& doc) const;
|
||||
bool MatchesFilters(const model::Document& doc) const;
|
||||
bool MatchesOrderBy(const model::Document& doc) const;
|
||||
bool MatchesBounds(const model::Document& doc) const;
|
||||
|
||||
model::ResourcePath path_;
|
||||
std::shared_ptr<const std::string> collection_group_;
|
||||
|
||||
// Filters are shared across related Query instance. i.e. when you call
|
||||
// Query::Filter(f), a new Query instance is created that contains all of the
|
||||
// existing filters, plus the new one. (Both Query and Filter objects are
|
||||
// immutable.) Filters are not shared across unrelated Query instances.
|
||||
FilterList filters_;
|
||||
|
||||
// A list of fields given to sort by. This does not include the implicit key
|
||||
// sort at the end.
|
||||
OrderByList explicit_order_bys_;
|
||||
|
||||
// The memoized list of sort orders.
|
||||
mutable OrderByList memoized_order_bys_;
|
||||
|
||||
int32_t limit_ = Target::kNoLimit;
|
||||
LimitType limit_type_ = LimitType::None;
|
||||
|
||||
absl::optional<Bound> start_at_;
|
||||
absl::optional<Bound> end_at_;
|
||||
|
||||
// The corresponding Target of this Query instance.
|
||||
mutable std::shared_ptr<const Target> memoized_target;
|
||||
};
|
||||
|
||||
bool operator==(const Query& lhs, const Query& rhs);
|
||||
inline bool operator!=(const Query& lhs, const Query& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<firebase::firestore::core::Query> {
|
||||
size_t operator()(const firebase::firestore::core::Query& query) const {
|
||||
return query.Hash();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_QUERY_H_
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright 2019 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 "Firestore/core/src/core/query_listener.h"
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Firestore/core/src/model/document_set.h"
|
||||
#include "Firestore/core/src/util/hard_assert.h"
|
||||
#include "Firestore/core/src/util/status.h"
|
||||
#include "absl/types/optional.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
using model::OnlineState;
|
||||
using model::TargetId;
|
||||
using util::Status;
|
||||
|
||||
std::shared_ptr<QueryListener> QueryListener::Create(
|
||||
Query query, ListenOptions options, ViewSnapshotSharedListener&& listener) {
|
||||
return std::make_shared<QueryListener>(std::move(query), std::move(options),
|
||||
std::move(listener));
|
||||
}
|
||||
|
||||
std::shared_ptr<QueryListener> QueryListener::Create(
|
||||
Query query, ViewSnapshotSharedListener&& listener) {
|
||||
return Create(std::move(query), ListenOptions::DefaultOptions(),
|
||||
std::move(listener));
|
||||
}
|
||||
|
||||
std::shared_ptr<QueryListener> QueryListener::Create(
|
||||
Query query,
|
||||
ListenOptions options,
|
||||
util::StatusOrCallback<ViewSnapshot>&& listener) {
|
||||
auto event_listener =
|
||||
EventListener<ViewSnapshot>::Create(std::move(listener));
|
||||
return Create(std::move(query), std::move(options),
|
||||
std::move(event_listener));
|
||||
}
|
||||
|
||||
std::shared_ptr<QueryListener> QueryListener::Create(
|
||||
Query query, util::StatusOrCallback<ViewSnapshot>&& listener) {
|
||||
return Create(std::move(query), ListenOptions::DefaultOptions(),
|
||||
std::move(listener));
|
||||
}
|
||||
|
||||
QueryListener::QueryListener(Query query,
|
||||
ListenOptions options,
|
||||
ViewSnapshotSharedListener&& listener)
|
||||
: query_(std::move(query)),
|
||||
options_(std::move(options)),
|
||||
listener_(std::move(listener)) {
|
||||
}
|
||||
|
||||
bool QueryListener::OnViewSnapshot(ViewSnapshot snapshot) {
|
||||
HARD_ASSERT(
|
||||
!snapshot.document_changes().empty() || snapshot.sync_state_changed(),
|
||||
"We got a new snapshot with no changes?");
|
||||
bool raised_event = false;
|
||||
if (!options_.include_document_metadata_changes()) {
|
||||
// Remove the metadata-only changes.
|
||||
std::vector<DocumentViewChange> changes;
|
||||
for (const DocumentViewChange& change : snapshot.document_changes()) {
|
||||
if (change.type() != DocumentViewChange::Type::Metadata) {
|
||||
changes.push_back(change);
|
||||
}
|
||||
}
|
||||
|
||||
snapshot = ViewSnapshot{snapshot.query(),
|
||||
snapshot.documents(),
|
||||
snapshot.old_documents(),
|
||||
std::move(changes),
|
||||
snapshot.mutated_keys(),
|
||||
snapshot.from_cache(),
|
||||
snapshot.sync_state_changed(),
|
||||
/*excludes_metadata_changes=*/true};
|
||||
}
|
||||
|
||||
if (!raised_initial_event_) {
|
||||
if (ShouldRaiseInitialEvent(snapshot, online_state_)) {
|
||||
RaiseInitialEvent(snapshot);
|
||||
raised_event = true;
|
||||
}
|
||||
} else if (ShouldRaiseEvent(snapshot)) {
|
||||
listener_->OnEvent(snapshot);
|
||||
raised_event = true;
|
||||
}
|
||||
|
||||
snapshot_ = std::move(snapshot);
|
||||
return raised_event;
|
||||
}
|
||||
|
||||
void QueryListener::OnError(Status error) {
|
||||
listener_->OnEvent(std::move(error));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a snaphsot was raised.
|
||||
*/
|
||||
bool QueryListener::OnOnlineStateChanged(OnlineState online_state) {
|
||||
online_state_ = online_state;
|
||||
bool raised_event = false;
|
||||
if (snapshot_.has_value() && !raised_initial_event_ &&
|
||||
ShouldRaiseInitialEvent(snapshot_.value(), online_state)) {
|
||||
RaiseInitialEvent(snapshot_.value());
|
||||
raised_event = true;
|
||||
}
|
||||
return raised_event;
|
||||
}
|
||||
|
||||
bool QueryListener::ShouldRaiseInitialEvent(const ViewSnapshot& snapshot,
|
||||
OnlineState online_state) const {
|
||||
HARD_ASSERT(!raised_initial_event_,
|
||||
"Determining whether to raise initial event, but already had "
|
||||
"first event.");
|
||||
|
||||
// Always raise the first event when we're synced
|
||||
if (!snapshot.from_cache()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// NOTE: We consider OnlineState::Unknown as online (it should become Offline
|
||||
// or Online if we wait long enough).
|
||||
bool maybe_online = online_state != OnlineState::Offline;
|
||||
|
||||
// Don't raise the event if we're online, aren't synced yet (checked
|
||||
// above) and are waiting for a sync.
|
||||
if (options_.wait_for_sync_when_online() && maybe_online) {
|
||||
HARD_ASSERT(snapshot.from_cache(),
|
||||
"Waiting for sync, but snapshot is not from cache.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Raise data from cache if we have any documents or we are offline
|
||||
return !snapshot.documents().empty() || online_state == OnlineState::Offline;
|
||||
}
|
||||
|
||||
bool QueryListener::ShouldRaiseEvent(const ViewSnapshot& snapshot) const {
|
||||
// We don't need to handle include_document_metadata_changes() here because
|
||||
// the Metadata only changes have already been stripped out if needed. At this
|
||||
// point the only changes we will see are the ones we should propagate.
|
||||
if (!snapshot.document_changes().empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool has_pending_writes_changed =
|
||||
snapshot_.has_value() &&
|
||||
snapshot_.value().has_pending_writes() != snapshot.has_pending_writes();
|
||||
if (snapshot.sync_state_changed() || has_pending_writes_changed) {
|
||||
return options_.include_query_metadata_changes();
|
||||
}
|
||||
|
||||
// Generally we should have hit one of the cases above, but it's possible to
|
||||
// get here if there were only metadata document changes and they got stripped
|
||||
// out.
|
||||
return false;
|
||||
}
|
||||
|
||||
void QueryListener::RaiseInitialEvent(const ViewSnapshot& snapshot) {
|
||||
HARD_ASSERT(!raised_initial_event_,
|
||||
"Trying to raise initial events for second time");
|
||||
|
||||
ViewSnapshot modified_snapshot = ViewSnapshot::FromInitialDocuments(
|
||||
snapshot.query(), snapshot.documents(), snapshot.mutated_keys(),
|
||||
snapshot.from_cache(), snapshot.excludes_metadata_changes());
|
||||
raised_initial_event_ = true;
|
||||
listener_->OnEvent(std::move(modified_snapshot));
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2019 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 FIRESTORE_CORE_SRC_CORE_QUERY_LISTENER_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_QUERY_LISTENER_H_
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/core/listen_options.h"
|
||||
#include "Firestore/core/src/core/query.h"
|
||||
#include "Firestore/core/src/core/view_snapshot.h"
|
||||
#include "Firestore/core/src/model/types.h"
|
||||
#include "Firestore/core/src/util/status_fwd.h"
|
||||
#include "absl/types/optional.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* QueryListener takes a series of internal view snapshots and determines when
|
||||
* to raise user-facing events.
|
||||
*/
|
||||
class QueryListener {
|
||||
public:
|
||||
static std::shared_ptr<QueryListener> Create(
|
||||
Query query,
|
||||
ListenOptions options,
|
||||
ViewSnapshotSharedListener&& listener);
|
||||
|
||||
static std::shared_ptr<QueryListener> Create(
|
||||
Query query, ViewSnapshotSharedListener&& listener);
|
||||
|
||||
static std::shared_ptr<QueryListener> Create(
|
||||
Query query,
|
||||
ListenOptions options,
|
||||
util::StatusOrCallback<ViewSnapshot>&& listener);
|
||||
|
||||
static std::shared_ptr<QueryListener> Create(
|
||||
Query query, util::StatusOrCallback<ViewSnapshot>&& listener);
|
||||
|
||||
QueryListener(Query query,
|
||||
ListenOptions options,
|
||||
ViewSnapshotSharedListener&& listener);
|
||||
|
||||
virtual ~QueryListener() = default;
|
||||
|
||||
const Query& query() const {
|
||||
return query_;
|
||||
}
|
||||
|
||||
/** The last received view snapshot. */
|
||||
const absl::optional<ViewSnapshot>& snapshot() const {
|
||||
return snapshot_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the new ViewSnapshot to this listener, raising a user-facing event
|
||||
* if applicable (depending on what changed, whether the user has opted into
|
||||
* metadata-only changes, etc.). Returns true if a user-facing event was
|
||||
* indeed raised.
|
||||
*/
|
||||
virtual bool OnViewSnapshot(ViewSnapshot snapshot);
|
||||
|
||||
virtual void OnError(util::Status error);
|
||||
|
||||
/** Returns whether a snapshot was raised. */
|
||||
virtual bool OnOnlineStateChanged(model::OnlineState online_state);
|
||||
|
||||
private:
|
||||
bool ShouldRaiseInitialEvent(const ViewSnapshot& snapshot,
|
||||
model::OnlineState online_state) const;
|
||||
bool ShouldRaiseEvent(const ViewSnapshot& snapshot) const;
|
||||
void RaiseInitialEvent(const ViewSnapshot& snapshot);
|
||||
|
||||
Query query_;
|
||||
ListenOptions options_;
|
||||
|
||||
/**
|
||||
* The EventListener that will process ViewSnapshots associated with this
|
||||
* query listener.
|
||||
*/
|
||||
ViewSnapshotSharedListener listener_;
|
||||
|
||||
/**
|
||||
* Initial snapshots (e.g. from cache) may not be propagated to the
|
||||
* ViewSnapshotHandler. This flag is set to true once we've actually raised an
|
||||
* event.
|
||||
*/
|
||||
bool raised_initial_event_ = false;
|
||||
|
||||
/** The last online state this query listener got. */
|
||||
model::OnlineState online_state_ = model::OnlineState::Unknown;
|
||||
|
||||
absl::optional<ViewSnapshot> snapshot_;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_QUERY_LISTENER_H_
|
||||
@@ -0,0 +1,658 @@
|
||||
/*
|
||||
* Copyright 2019 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 "Firestore/core/src/core/sync_engine.h"
|
||||
|
||||
#include "Firestore/core/include/firebase/firestore/firestore_errors.h"
|
||||
#include "Firestore/core/src/bundle/bundle_element.h"
|
||||
#include "Firestore/core/src/bundle/bundle_loader.h"
|
||||
#include "Firestore/core/src/core/sync_engine_callback.h"
|
||||
#include "Firestore/core/src/core/transaction.h"
|
||||
#include "Firestore/core/src/core/transaction_runner.h"
|
||||
#include "Firestore/core/src/local/local_documents_view.h"
|
||||
#include "Firestore/core/src/local/local_store.h"
|
||||
#include "Firestore/core/src/local/local_view_changes.h"
|
||||
#include "Firestore/core/src/local/local_write_result.h"
|
||||
#include "Firestore/core/src/local/query_result.h"
|
||||
#include "Firestore/core/src/local/target_data.h"
|
||||
#include "Firestore/core/src/model/document_key.h"
|
||||
#include "Firestore/core/src/model/document_key_set.h"
|
||||
#include "Firestore/core/src/model/document_set.h"
|
||||
#include "Firestore/core/src/model/mutable_document.h"
|
||||
#include "Firestore/core/src/model/mutation_batch_result.h"
|
||||
#include "Firestore/core/src/util/async_queue.h"
|
||||
#include "Firestore/core/src/util/log.h"
|
||||
#include "Firestore/core/src/util/status.h"
|
||||
#include "absl/strings/match.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
namespace {
|
||||
|
||||
using bundle::BundleElement;
|
||||
using bundle::BundleLoader;
|
||||
using bundle::InitialProgress;
|
||||
using bundle::SuccessProgress;
|
||||
using credentials::User;
|
||||
using firestore::Error;
|
||||
using local::LocalStore;
|
||||
using local::LocalViewChanges;
|
||||
using local::LocalWriteResult;
|
||||
using local::QueryPurpose;
|
||||
using local::QueryResult;
|
||||
using local::TargetData;
|
||||
using model::BatchId;
|
||||
using model::DocumentKey;
|
||||
using model::DocumentKeySet;
|
||||
using model::DocumentMap;
|
||||
using model::DocumentUpdateMap;
|
||||
using model::kBatchIdUnknown;
|
||||
using model::ListenSequenceNumber;
|
||||
using model::MutableDocument;
|
||||
using model::SnapshotVersion;
|
||||
using model::TargetId;
|
||||
using remote::RemoteEvent;
|
||||
using remote::TargetChange;
|
||||
using util::AsyncQueue;
|
||||
using util::Status;
|
||||
using util::StatusCallback;
|
||||
|
||||
// Limbo documents don't use persistence, and are eagerly GC'd. So, listens for
|
||||
// them don't need real sequence numbers.
|
||||
const ListenSequenceNumber kIrrelevantSequenceNumber = -1;
|
||||
|
||||
bool ErrorIsInteresting(const Status& error) {
|
||||
bool missing_index =
|
||||
(error.code() == Error::kErrorFailedPrecondition &&
|
||||
absl::StrContains(error.error_message(), "requires an index"));
|
||||
bool no_permission = (error.code() == Error::kErrorPermissionDenied);
|
||||
return missing_index || no_permission;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SyncEngine::SyncEngine(LocalStore* local_store,
|
||||
remote::RemoteStore* remote_store,
|
||||
const credentials::User& initial_user,
|
||||
size_t max_concurrent_limbo_resolutions)
|
||||
: local_store_(local_store),
|
||||
remote_store_(remote_store),
|
||||
current_user_(initial_user),
|
||||
target_id_generator_(TargetIdGenerator::SyncEngineTargetIdGenerator()),
|
||||
max_concurrent_limbo_resolutions_(max_concurrent_limbo_resolutions) {
|
||||
}
|
||||
|
||||
void SyncEngine::AssertCallbackExists(absl::string_view source) {
|
||||
HARD_ASSERT(sync_engine_callback_,
|
||||
"Tried to call '%s' before callback was registered.", source);
|
||||
}
|
||||
|
||||
TargetId SyncEngine::Listen(Query query) {
|
||||
AssertCallbackExists("Listen");
|
||||
|
||||
HARD_ASSERT(query_views_by_query_.find(query) == query_views_by_query_.end(),
|
||||
"We already listen to query: %s", query.ToString());
|
||||
|
||||
TargetData target_data = local_store_->AllocateTarget(query.ToTarget());
|
||||
ViewSnapshot view_snapshot =
|
||||
InitializeViewAndComputeSnapshot(query, target_data.target_id());
|
||||
std::vector<ViewSnapshot> snapshots;
|
||||
// Not using the `std::initializer_list` constructor to avoid extra copies.
|
||||
snapshots.push_back(std::move(view_snapshot));
|
||||
sync_engine_callback_->OnViewSnapshots(std::move(snapshots));
|
||||
|
||||
// TODO(wuandy): move `target_data` into `Listen`.
|
||||
remote_store_->Listen(target_data);
|
||||
return target_data.target_id();
|
||||
}
|
||||
|
||||
ViewSnapshot SyncEngine::InitializeViewAndComputeSnapshot(const Query& query,
|
||||
TargetId target_id) {
|
||||
QueryResult query_result =
|
||||
local_store_->ExecuteQuery(query, /* use_previous_results= */ true);
|
||||
|
||||
// If there are already queries mapped to the target id, create a synthesized
|
||||
// target change to apply the sync state from those queries to the new query.
|
||||
auto current_sync_state = SyncState::None;
|
||||
absl::optional<TargetChange> synthesized_current_change;
|
||||
if (queries_by_target_.find(target_id) != queries_by_target_.end()) {
|
||||
const Query& mirror_query = queries_by_target_[target_id][0];
|
||||
current_sync_state =
|
||||
query_views_by_query_[mirror_query]->view().sync_state();
|
||||
synthesized_current_change = TargetChange::CreateSynthesizedTargetChange(
|
||||
current_sync_state == SyncState::Synced);
|
||||
}
|
||||
|
||||
View view(query, query_result.remote_keys());
|
||||
ViewDocumentChanges view_doc_changes =
|
||||
view.ComputeDocumentChanges(query_result.documents());
|
||||
ViewChange view_change =
|
||||
view.ApplyChanges(view_doc_changes, synthesized_current_change);
|
||||
UpdateTrackedLimboDocuments(view_change.limbo_changes(), target_id);
|
||||
|
||||
auto query_view =
|
||||
std::make_shared<QueryView>(query, target_id, std::move(view));
|
||||
query_views_by_query_[query] = query_view;
|
||||
|
||||
queries_by_target_[target_id].push_back(query);
|
||||
|
||||
HARD_ASSERT(
|
||||
view_change.snapshot().has_value(),
|
||||
"ApplyChanges to documents for new view should always return a snapshot");
|
||||
return view_change.snapshot().value();
|
||||
}
|
||||
|
||||
void SyncEngine::StopListening(const Query& query) {
|
||||
AssertCallbackExists("StopListening");
|
||||
|
||||
auto query_view = query_views_by_query_[query];
|
||||
HARD_ASSERT(query_view, "Trying to stop listening to a query not found");
|
||||
|
||||
query_views_by_query_.erase(query);
|
||||
|
||||
TargetId target_id = query_view->target_id();
|
||||
auto& queries = queries_by_target_[target_id];
|
||||
queries.erase(std::remove(queries.begin(), queries.end(), query),
|
||||
queries.end());
|
||||
|
||||
if (queries.empty()) {
|
||||
local_store_->ReleaseTarget(target_id);
|
||||
remote_store_->StopListening(target_id);
|
||||
RemoveAndCleanupTarget(target_id, Status::OK());
|
||||
}
|
||||
}
|
||||
|
||||
void SyncEngine::RemoveAndCleanupTarget(TargetId target_id, Status status) {
|
||||
for (const Query& query : queries_by_target_.at(target_id)) {
|
||||
query_views_by_query_.erase(query);
|
||||
if (!status.ok()) {
|
||||
sync_engine_callback_->OnError(query, status);
|
||||
if (ErrorIsInteresting(status)) {
|
||||
LOG_WARN("Listen for query at %s failed: %s",
|
||||
query.path().CanonicalString(), status.error_message());
|
||||
}
|
||||
}
|
||||
}
|
||||
queries_by_target_.erase(target_id);
|
||||
|
||||
DocumentKeySet limbo_keys = limbo_document_refs_.ReferencedKeys(target_id);
|
||||
limbo_document_refs_.RemoveReferences(target_id);
|
||||
for (const DocumentKey& key : limbo_keys) {
|
||||
if (!limbo_document_refs_.ContainsKey(key)) {
|
||||
// We removed the last reference for this key.
|
||||
RemoveLimboTarget(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SyncEngine::WriteMutations(std::vector<model::Mutation>&& mutations,
|
||||
StatusCallback callback) {
|
||||
AssertCallbackExists("WriteMutations");
|
||||
|
||||
LocalWriteResult result = local_store_->WriteLocally(std::move(mutations));
|
||||
mutation_callbacks_[current_user_].insert(
|
||||
std::make_pair(result.batch_id(), std::move(callback)));
|
||||
|
||||
EmitNewSnapshotsAndNotifyLocalStore(result.changes(), absl::nullopt);
|
||||
remote_store_->FillWritePipeline();
|
||||
}
|
||||
|
||||
void SyncEngine::RegisterPendingWritesCallback(StatusCallback callback) {
|
||||
if (!remote_store_->CanUseNetwork()) {
|
||||
LOG_DEBUG(
|
||||
"The network is disabled. The task returned by "
|
||||
"'waitForPendingWrites()' will not "
|
||||
"complete until the network is enabled.");
|
||||
}
|
||||
|
||||
int largest_pending_batch_id =
|
||||
local_store_->GetHighestUnacknowledgedBatchId();
|
||||
|
||||
if (largest_pending_batch_id == kBatchIdUnknown) {
|
||||
// Trigger the callback right away if there is no pending writes at the
|
||||
// moment.
|
||||
callback(Status::OK());
|
||||
return;
|
||||
}
|
||||
|
||||
pending_writes_callbacks_[largest_pending_batch_id].push_back(
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
void SyncEngine::Transaction(int retries,
|
||||
const std::shared_ptr<AsyncQueue>& worker_queue,
|
||||
TransactionUpdateCallback update_callback,
|
||||
TransactionResultCallback result_callback) {
|
||||
worker_queue->VerifyIsCurrentQueue();
|
||||
HARD_ASSERT(retries >= 0, "Got negative number of retries for transaction");
|
||||
|
||||
// Allocate a shared_ptr so that the TransactionRunner can outlive this frame.
|
||||
auto runner = std::make_shared<TransactionRunner>(worker_queue, remote_store_,
|
||||
std::move(update_callback),
|
||||
std::move(result_callback));
|
||||
runner->Run();
|
||||
}
|
||||
|
||||
void SyncEngine::HandleCredentialChange(const credentials::User& user) {
|
||||
bool user_changed = (current_user_ != user);
|
||||
current_user_ = user;
|
||||
|
||||
if (user_changed) {
|
||||
// Fails callbacks waiting for pending writes requested by previous user.
|
||||
FailOutstandingPendingWriteCallbacks(
|
||||
"'waitForPendingWrites' callback is cancelled due to a user change.");
|
||||
// Notify local store and emit any resulting events from swapping out the
|
||||
// mutation queue.
|
||||
DocumentMap changes = local_store_->HandleUserChange(user);
|
||||
EmitNewSnapshotsAndNotifyLocalStore(changes, absl::nullopt);
|
||||
}
|
||||
|
||||
// Notify remote store so it can restart its streams.
|
||||
remote_store_->HandleCredentialChange();
|
||||
}
|
||||
|
||||
void SyncEngine::ApplyRemoteEvent(const RemoteEvent& remote_event) {
|
||||
AssertCallbackExists("HandleRemoteEvent");
|
||||
|
||||
// Update received document as appropriate for any limbo targets.
|
||||
for (const auto& entry : remote_event.target_changes()) {
|
||||
TargetId target_id = entry.first;
|
||||
const TargetChange& change = entry.second;
|
||||
auto it = active_limbo_resolutions_by_target_.find(target_id);
|
||||
if (it == active_limbo_resolutions_by_target_.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LimboResolution& limbo_resolution = it->second;
|
||||
// Since this is a limbo resolution lookup, it's for a single document and
|
||||
// it could be added, modified, or removed, but not a combination.
|
||||
auto changed_documents_count = change.added_documents().size() +
|
||||
change.modified_documents().size() +
|
||||
change.removed_documents().size();
|
||||
HARD_ASSERT(
|
||||
changed_documents_count <= 1,
|
||||
"Limbo resolution for single document contains multiple changes.");
|
||||
|
||||
if (!change.added_documents().empty()) {
|
||||
limbo_resolution.document_received = true;
|
||||
} else if (!change.modified_documents().empty()) {
|
||||
HARD_ASSERT(limbo_resolution.document_received,
|
||||
"Received change for limbo target document without add.");
|
||||
} else if (!change.removed_documents().empty()) {
|
||||
HARD_ASSERT(limbo_resolution.document_received,
|
||||
"Received remove for limbo target document without add.");
|
||||
limbo_resolution.document_received = false;
|
||||
} else {
|
||||
// This was probably just a CURRENT target change or similar.
|
||||
}
|
||||
}
|
||||
|
||||
DocumentMap changes = local_store_->ApplyRemoteEvent(remote_event);
|
||||
EmitNewSnapshotsAndNotifyLocalStore(changes, remote_event);
|
||||
}
|
||||
|
||||
void SyncEngine::HandleRejectedListen(TargetId target_id, Status error) {
|
||||
AssertCallbackExists("HandleRejectedListen");
|
||||
|
||||
auto it = active_limbo_resolutions_by_target_.find(target_id);
|
||||
if (it != active_limbo_resolutions_by_target_.end()) {
|
||||
DocumentKey limbo_key = it->second.key;
|
||||
// Since this query failed, we won't want to manually unlisten to it.
|
||||
// So go ahead and remove it from bookkeeping.
|
||||
active_limbo_targets_by_key_.erase(limbo_key);
|
||||
active_limbo_resolutions_by_target_.erase(target_id);
|
||||
PumpEnqueuedLimboResolutions();
|
||||
|
||||
// TODO(dimond): Retry on transient errors?
|
||||
|
||||
// It's a limbo doc. Create a synthetic event saying it was deleted. This is
|
||||
// kind of a hack. Ideally, we would have a method in the local store to
|
||||
// purge a document. However, it would be tricky to keep all of the local
|
||||
// store's invariants with another method.
|
||||
MutableDocument doc =
|
||||
MutableDocument::NoDocument(limbo_key, SnapshotVersion::None());
|
||||
|
||||
// Explicitly instantiate these to work around a bug in the default
|
||||
// constructor of the std::unordered_map that comes with GCC 4.8. Without
|
||||
// this GCC emits a spurious "chosen constructor is explicit in
|
||||
// copy-initialization" error.
|
||||
DocumentKeySet limbo_documents{limbo_key};
|
||||
RemoteEvent::TargetChangeMap target_changes;
|
||||
RemoteEvent::TargetSet target_mismatches;
|
||||
DocumentUpdateMap document_updates{{limbo_key, doc}};
|
||||
|
||||
RemoteEvent event{SnapshotVersion::None(), std::move(target_changes),
|
||||
std::move(target_mismatches), std::move(document_updates),
|
||||
std::move(limbo_documents)};
|
||||
ApplyRemoteEvent(event);
|
||||
} else {
|
||||
local_store_->ReleaseTarget(target_id);
|
||||
RemoveAndCleanupTarget(target_id, error);
|
||||
}
|
||||
}
|
||||
|
||||
void SyncEngine::HandleSuccessfulWrite(
|
||||
model::MutationBatchResult batch_result) {
|
||||
AssertCallbackExists("HandleSuccessfulWrite");
|
||||
|
||||
// The local store may or may not be able to apply the write result and
|
||||
// raise events immediately (depending on whether the watcher is caught up),
|
||||
// so we raise user callbacks first so that they consistently happen before
|
||||
// listen events.
|
||||
NotifyUser(batch_result.batch().batch_id(), Status::OK());
|
||||
|
||||
TriggerPendingWriteCallbacks(batch_result.batch().batch_id());
|
||||
|
||||
DocumentMap changes = local_store_->AcknowledgeBatch(batch_result);
|
||||
EmitNewSnapshotsAndNotifyLocalStore(changes, absl::nullopt);
|
||||
}
|
||||
|
||||
void SyncEngine::HandleRejectedWrite(
|
||||
firebase::firestore::model::BatchId batch_id, Status error) {
|
||||
AssertCallbackExists("HandleRejectedWrite");
|
||||
|
||||
DocumentMap changes = local_store_->RejectBatch(batch_id);
|
||||
|
||||
if (!changes.empty() && ErrorIsInteresting(error)) {
|
||||
const DocumentKey& min_key = changes.min()->first;
|
||||
LOG_WARN("Write at %s failed: %s", min_key.ToString(),
|
||||
error.error_message());
|
||||
}
|
||||
|
||||
// The local store may or may not be able to apply the write result and
|
||||
// raise events immediately (depending on whether the watcher is caught up),
|
||||
// so we raise user callbacks first so that they consistently happen before
|
||||
// listen events.
|
||||
NotifyUser(batch_id, std::move(error));
|
||||
|
||||
TriggerPendingWriteCallbacks(batch_id);
|
||||
|
||||
EmitNewSnapshotsAndNotifyLocalStore(changes, absl::nullopt);
|
||||
}
|
||||
|
||||
void SyncEngine::HandleOnlineStateChange(model::OnlineState online_state) {
|
||||
AssertCallbackExists("HandleOnlineStateChange");
|
||||
|
||||
std::vector<ViewSnapshot> new_view_snapshot;
|
||||
for (const auto& entry : query_views_by_query_) {
|
||||
const auto& query_view = entry.second;
|
||||
ViewChange view_change =
|
||||
query_view->view().ApplyOnlineStateChange(online_state);
|
||||
HARD_ASSERT(view_change.limbo_changes().empty(),
|
||||
"OnlineState should not affect limbo documents.");
|
||||
if (view_change.snapshot().has_value()) {
|
||||
new_view_snapshot.push_back(*std::move(view_change).snapshot());
|
||||
}
|
||||
}
|
||||
|
||||
sync_engine_callback_->OnViewSnapshots(std::move(new_view_snapshot));
|
||||
sync_engine_callback_->HandleOnlineStateChange(online_state);
|
||||
}
|
||||
|
||||
DocumentKeySet SyncEngine::GetRemoteKeys(TargetId target_id) const {
|
||||
auto it = active_limbo_resolutions_by_target_.find(target_id);
|
||||
if (it != active_limbo_resolutions_by_target_.end() &&
|
||||
it->second.document_received) {
|
||||
return DocumentKeySet{it->second.key};
|
||||
} else {
|
||||
DocumentKeySet keys;
|
||||
if (queries_by_target_.count(target_id) == 0) {
|
||||
return keys;
|
||||
}
|
||||
|
||||
for (const auto& query : queries_by_target_.at(target_id)) {
|
||||
keys = keys.union_with(
|
||||
query_views_by_query_.at(query)->view().synced_documents());
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
|
||||
void SyncEngine::NotifyUser(BatchId batch_id, Status status) {
|
||||
auto it = mutation_callbacks_.find(current_user_);
|
||||
|
||||
// NOTE: Mutations restored from persistence won't have callbacks, so
|
||||
// it's okay for this (or the callback below) to not exist.
|
||||
if (it == mutation_callbacks_.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unordered_map<BatchId, StatusCallback>& callbacks = it->second;
|
||||
auto callback_it = callbacks.find(batch_id);
|
||||
if (callback_it != callbacks.end()) {
|
||||
callback_it->second(std::move(status));
|
||||
callbacks.erase(callback_it);
|
||||
}
|
||||
}
|
||||
|
||||
void SyncEngine::TriggerPendingWriteCallbacks(BatchId batch_id) {
|
||||
auto it = pending_writes_callbacks_.find(batch_id);
|
||||
if (it != pending_writes_callbacks_.end()) {
|
||||
for (const auto& callback : it->second) {
|
||||
callback(Status::OK());
|
||||
}
|
||||
|
||||
pending_writes_callbacks_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void SyncEngine::FailOutstandingPendingWriteCallbacks(
|
||||
const std::string& message) {
|
||||
for (const auto& entry : pending_writes_callbacks_) {
|
||||
for (const auto& callback : entry.second) {
|
||||
callback(Status(Error::kErrorCancelled, message));
|
||||
}
|
||||
}
|
||||
|
||||
pending_writes_callbacks_.clear();
|
||||
}
|
||||
|
||||
void SyncEngine::EmitNewSnapshotsAndNotifyLocalStore(
|
||||
const DocumentMap& changes,
|
||||
const absl::optional<RemoteEvent>& maybe_remote_event) {
|
||||
std::vector<ViewSnapshot> new_snapshots;
|
||||
std::vector<LocalViewChanges> document_changes_in_all_views;
|
||||
|
||||
for (const auto& entry : query_views_by_query_) {
|
||||
const auto& query_view = entry.second;
|
||||
View& view = query_view->view();
|
||||
ViewDocumentChanges view_doc_changes = view.ComputeDocumentChanges(changes);
|
||||
if (view_doc_changes.needs_refill()) {
|
||||
// The query has a limit and some docs were removed/updated, so we need to
|
||||
// re-run the query against the local store to make sure we didn't lose
|
||||
// any good docs that had been past the limit.
|
||||
QueryResult query_result = local_store_->ExecuteQuery(
|
||||
query_view->query(), /* use_previous_results= */ false);
|
||||
view_doc_changes = view.ComputeDocumentChanges(query_result.documents(),
|
||||
view_doc_changes);
|
||||
}
|
||||
|
||||
absl::optional<TargetChange> target_changes;
|
||||
if (maybe_remote_event.has_value()) {
|
||||
const RemoteEvent& remote_event = maybe_remote_event.value();
|
||||
auto it = remote_event.target_changes().find(query_view->target_id());
|
||||
if (it != remote_event.target_changes().end()) {
|
||||
target_changes = it->second;
|
||||
}
|
||||
}
|
||||
ViewChange view_change =
|
||||
view.ApplyChanges(view_doc_changes, target_changes);
|
||||
|
||||
UpdateTrackedLimboDocuments(view_change.limbo_changes(),
|
||||
query_view->target_id());
|
||||
|
||||
if (view_change.snapshot().has_value()) {
|
||||
new_snapshots.push_back(*view_change.snapshot());
|
||||
LocalViewChanges doc_changes = LocalViewChanges::FromViewSnapshot(
|
||||
*view_change.snapshot(), query_view->target_id());
|
||||
document_changes_in_all_views.push_back(std::move(doc_changes));
|
||||
}
|
||||
}
|
||||
|
||||
sync_engine_callback_->OnViewSnapshots(std::move(new_snapshots));
|
||||
local_store_->NotifyLocalViewChanges(document_changes_in_all_views);
|
||||
}
|
||||
|
||||
void SyncEngine::UpdateTrackedLimboDocuments(
|
||||
const std::vector<LimboDocumentChange>& limbo_changes, TargetId target_id) {
|
||||
for (const LimboDocumentChange& limbo_change : limbo_changes) {
|
||||
switch (limbo_change.type()) {
|
||||
case LimboDocumentChange::Type::Added:
|
||||
limbo_document_refs_.AddReference(limbo_change.key(), target_id);
|
||||
TrackLimboChange(limbo_change);
|
||||
break;
|
||||
|
||||
case LimboDocumentChange::Type::Removed:
|
||||
LOG_DEBUG("Document no longer in limbo: %s",
|
||||
limbo_change.key().ToString());
|
||||
limbo_document_refs_.RemoveReference(limbo_change.key(), target_id);
|
||||
if (!limbo_document_refs_.ContainsKey(limbo_change.key())) {
|
||||
// We removed the last reference for this key
|
||||
RemoveLimboTarget(limbo_change.key());
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
HARD_FAIL("Unknown limbo change type: %s", limbo_change.type());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SyncEngine::TrackLimboChange(const LimboDocumentChange& limbo_change) {
|
||||
const DocumentKey& key = limbo_change.key();
|
||||
if (active_limbo_targets_by_key_.find(key) ==
|
||||
active_limbo_targets_by_key_.end() &&
|
||||
enqueued_limbo_resolutions_.push_back(key)) {
|
||||
LOG_DEBUG("New document in limbo: %s", key.ToString());
|
||||
PumpEnqueuedLimboResolutions();
|
||||
}
|
||||
}
|
||||
|
||||
void SyncEngine::PumpEnqueuedLimboResolutions() {
|
||||
while (!enqueued_limbo_resolutions_.empty() &&
|
||||
active_limbo_targets_by_key_.size() <
|
||||
max_concurrent_limbo_resolutions_) {
|
||||
DocumentKey key = enqueued_limbo_resolutions_.front();
|
||||
enqueued_limbo_resolutions_.pop_front();
|
||||
TargetId limbo_target_id = target_id_generator_.NextId();
|
||||
active_limbo_resolutions_by_target_.emplace(limbo_target_id,
|
||||
LimboResolution{key});
|
||||
active_limbo_targets_by_key_.emplace(key, limbo_target_id);
|
||||
remote_store_->Listen(TargetData(Query(key.path()).ToTarget(),
|
||||
limbo_target_id, kIrrelevantSequenceNumber,
|
||||
QueryPurpose::LimboResolution));
|
||||
}
|
||||
}
|
||||
|
||||
void SyncEngine::RemoveLimboTarget(const DocumentKey& key) {
|
||||
enqueued_limbo_resolutions_.remove(key);
|
||||
auto it = active_limbo_targets_by_key_.find(key);
|
||||
if (it == active_limbo_targets_by_key_.end()) {
|
||||
// This target already got removed, because the query failed.
|
||||
return;
|
||||
}
|
||||
|
||||
TargetId limbo_target_id = it->second;
|
||||
remote_store_->StopListening(limbo_target_id);
|
||||
active_limbo_targets_by_key_.erase(key);
|
||||
active_limbo_resolutions_by_target_.erase(limbo_target_id);
|
||||
PumpEnqueuedLimboResolutions();
|
||||
}
|
||||
|
||||
absl::optional<BundleLoader> SyncEngine::ReadIntoLoader(
|
||||
const bundle::BundleMetadata& metadata,
|
||||
bundle::BundleReader& reader,
|
||||
api::LoadBundleTask& result_task) {
|
||||
BundleLoader loader(local_store_, metadata);
|
||||
int64_t current_bytes_read = 0;
|
||||
// Breaks when either error happened, or when there is no more element to
|
||||
// read.
|
||||
while (true) {
|
||||
auto element = reader.GetNextElement();
|
||||
if (!reader.reader_status().ok()) {
|
||||
LOG_WARN("Failed to GetNextElement() from bundle with error %s",
|
||||
reader.reader_status().error_message());
|
||||
result_task.SetError(reader.reader_status());
|
||||
return absl::nullopt;
|
||||
}
|
||||
|
||||
// No more elements from reader.
|
||||
if (element == nullptr) {
|
||||
break;
|
||||
}
|
||||
|
||||
int64_t old_bytes_read = current_bytes_read;
|
||||
current_bytes_read = reader.bytes_read();
|
||||
auto maybe_progress = loader.AddElement(
|
||||
std::move(element), current_bytes_read - old_bytes_read);
|
||||
if (!maybe_progress.ok()) {
|
||||
LOG_WARN("Failed to AddElement() to bundle loader with error %s",
|
||||
maybe_progress.status().error_message());
|
||||
result_task.SetError(maybe_progress.status());
|
||||
return absl::nullopt;
|
||||
}
|
||||
|
||||
if (maybe_progress.ValueOrDie().has_value()) {
|
||||
result_task.UpdateProgress(maybe_progress.ConsumeValueOrDie().value());
|
||||
}
|
||||
}
|
||||
|
||||
return loader;
|
||||
}
|
||||
|
||||
void SyncEngine::LoadBundle(std::shared_ptr<bundle::BundleReader> reader,
|
||||
std::shared_ptr<api::LoadBundleTask> result_task) {
|
||||
auto bundle_metadata = reader->GetBundleMetadata();
|
||||
if (!reader->reader_status().ok()) {
|
||||
LOG_WARN("Failed to GetBundleMetadata() for bundle with error %s",
|
||||
reader->reader_status().error_message());
|
||||
result_task->SetError(reader->reader_status());
|
||||
return;
|
||||
}
|
||||
|
||||
bool has_newer_bundle = local_store_->HasNewerBundle(bundle_metadata);
|
||||
if (has_newer_bundle) {
|
||||
result_task->SetSuccess(SuccessProgress(bundle_metadata));
|
||||
return;
|
||||
}
|
||||
|
||||
result_task->UpdateProgress(InitialProgress(bundle_metadata));
|
||||
auto maybe_loader = ReadIntoLoader(bundle_metadata, *reader, *result_task);
|
||||
if (!maybe_loader.has_value()) {
|
||||
// `ReadIntoLoader` would call `result_task.SetError` should there be an
|
||||
// error, so we do not need set it here.
|
||||
return;
|
||||
}
|
||||
|
||||
util::StatusOr<DocumentMap> changes = maybe_loader.value().ApplyChanges();
|
||||
if (!changes.ok()) {
|
||||
LOG_WARN("Failed to ApplyChanges() for bundle elements with error %s",
|
||||
changes.status().error_message());
|
||||
result_task->SetError(changes.status());
|
||||
return;
|
||||
}
|
||||
|
||||
EmitNewSnapshotsAndNotifyLocalStore(changes.ConsumeValueOrDie(),
|
||||
absl::nullopt);
|
||||
|
||||
result_task->SetSuccess(SuccessProgress(bundle_metadata));
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,338 @@
|
||||
/*
|
||||
* Copyright 2019 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 FIRESTORE_CORE_SRC_CORE_SYNC_ENGINE_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_SYNC_ENGINE_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Firestore/core/src/api/load_bundle_task.h"
|
||||
#include "Firestore/core/src/bundle/bundle_loader.h"
|
||||
#include "Firestore/core/src/bundle/bundle_reader.h"
|
||||
#include "Firestore/core/src/core/query.h"
|
||||
#include "Firestore/core/src/core/target_id_generator.h"
|
||||
#include "Firestore/core/src/core/view.h"
|
||||
#include "Firestore/core/src/local/reference_set.h"
|
||||
#include "Firestore/core/src/model/model_fwd.h"
|
||||
#include "Firestore/core/src/remote/remote_store.h"
|
||||
#include "Firestore/core/src/util/random_access_queue.h"
|
||||
#include "Firestore/core/src/util/status.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
|
||||
namespace local {
|
||||
class LocalStore;
|
||||
class TargetData;
|
||||
} // namespace local
|
||||
|
||||
namespace core {
|
||||
|
||||
class SyncEngineCallback;
|
||||
class ViewSnapshot;
|
||||
|
||||
/**
|
||||
* Interface implemented by `SyncEngine` to receive requests from
|
||||
* `EventManager`.
|
||||
// PORTING NOTE: This is extracted as an interface to allow gmock to mock
|
||||
// sync engine.
|
||||
*/
|
||||
class QueryEventSource {
|
||||
public:
|
||||
virtual ~QueryEventSource() = default;
|
||||
|
||||
virtual void SetCallback(SyncEngineCallback* callback) = 0;
|
||||
|
||||
/**
|
||||
* Initiates a new listen. The LocalStore will be queried for initial data
|
||||
* and the listen will be sent to the `RemoteStore` to get remote data. The
|
||||
* registered SyncEngineCallback will be notified of resulting view
|
||||
* snapshots and/or listen errors.
|
||||
*
|
||||
* @return the target ID assigned to the query.
|
||||
*/
|
||||
virtual model::TargetId Listen(Query query) = 0;
|
||||
|
||||
/** Stops listening to a query previously listened to via `Listen`. */
|
||||
virtual void StopListening(const Query& query) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* SyncEngine is the central controller in the client SDK architecture. It is
|
||||
* the glue code between the EventManager, LocalStore, and RemoteStore. Some of
|
||||
* SyncEngine's responsibilities include:
|
||||
* 1. Coordinating client requests and remote events between the EventManager
|
||||
* and the local and remote data stores.
|
||||
* 2. Managing a View object for each query, providing the unified view between
|
||||
* the local and remote data stores.
|
||||
* 3. Notifying the RemoteStore when the LocalStore has new mutations in its
|
||||
* queue that need sending to the backend.
|
||||
*
|
||||
* The SyncEngine’s methods should only ever be called by methods running on our
|
||||
* own worker queue.
|
||||
*/
|
||||
class SyncEngine : public remote::RemoteStoreCallback, public QueryEventSource {
|
||||
public:
|
||||
SyncEngine(local::LocalStore* local_store,
|
||||
remote::RemoteStore* remote_store,
|
||||
const credentials::User& initial_user,
|
||||
size_t max_concurrent_limbo_resolutions);
|
||||
|
||||
// Implements `QueryEventSource`.
|
||||
void SetCallback(SyncEngineCallback* callback) override {
|
||||
sync_engine_callback_ = callback;
|
||||
}
|
||||
model::TargetId Listen(Query query) override;
|
||||
void StopListening(const Query& query) override;
|
||||
|
||||
/**
|
||||
* Initiates the write of local mutation batch which involves adding the
|
||||
* writes to the mutation queue, notifying the remote store about new
|
||||
* mutations, and raising events for any changes this write caused. The
|
||||
* provided callback will be called once the write has been acked or
|
||||
* rejected by the backend (or failed locally for any other reason).
|
||||
*/
|
||||
void WriteMutations(std::vector<model::Mutation>&& mutations,
|
||||
util::StatusCallback callback);
|
||||
|
||||
/**
|
||||
* Registers a user callback that is called when all pending mutations at the
|
||||
* moment of calling are acknowledged .
|
||||
*/
|
||||
void RegisterPendingWritesCallback(util::StatusCallback callback);
|
||||
|
||||
/**
|
||||
* Runs the given transaction block up to retries times and then calls
|
||||
* completion.
|
||||
*
|
||||
* @param retries The number of times to try before giving up.
|
||||
* @param worker_queue The queue to dispatch sync engine calls to.
|
||||
* @param update_callback The callback to call to execute the user's
|
||||
* transaction.
|
||||
* @param result_callback The callback to call when the transaction is
|
||||
* finished or failed.
|
||||
*/
|
||||
void Transaction(int retries,
|
||||
const std::shared_ptr<util::AsyncQueue>& worker_queue,
|
||||
core::TransactionUpdateCallback update_callback,
|
||||
core::TransactionResultCallback result_callback);
|
||||
|
||||
void HandleCredentialChange(const credentials::User& user);
|
||||
|
||||
// Implements `RemoteStoreCallback`
|
||||
void ApplyRemoteEvent(const remote::RemoteEvent& remote_event) override;
|
||||
void HandleRejectedListen(model::TargetId target_id,
|
||||
util::Status error) override;
|
||||
void HandleSuccessfulWrite(model::MutationBatchResult batch_result) override;
|
||||
void HandleRejectedWrite(model::BatchId batch_id,
|
||||
util::Status error) override;
|
||||
void HandleOnlineStateChange(model::OnlineState online_state) override;
|
||||
model::DocumentKeySet GetRemoteKeys(model::TargetId target_id) const override;
|
||||
|
||||
void LoadBundle(std::shared_ptr<bundle::BundleReader> reader,
|
||||
std::shared_ptr<api::LoadBundleTask> result_task);
|
||||
|
||||
// For tests only
|
||||
std::map<model::DocumentKey, model::TargetId>
|
||||
GetActiveLimboDocumentResolutions() const {
|
||||
// Return defensive copy
|
||||
return active_limbo_targets_by_key_;
|
||||
}
|
||||
|
||||
// For tests only
|
||||
std::vector<model::DocumentKey> GetEnqueuedLimboDocumentResolutions() const {
|
||||
return enqueued_limbo_resolutions_.elements();
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* QueryView contains all of the info that SyncEngine needs to track for a
|
||||
* particular query and view.
|
||||
*/
|
||||
class QueryView {
|
||||
public:
|
||||
QueryView(Query query, model::TargetId target_id, View view)
|
||||
: query_(std::move(query)),
|
||||
target_id_(target_id),
|
||||
view_(std::move(view)) {
|
||||
}
|
||||
|
||||
const Query& query() const {
|
||||
return query_;
|
||||
}
|
||||
|
||||
/**
|
||||
* The target ID created by the client that is used in the watch stream to
|
||||
* identify this query.
|
||||
*/
|
||||
model::TargetId target_id() const {
|
||||
return target_id_;
|
||||
}
|
||||
|
||||
/**
|
||||
* The view is responsible for computing the final merged truth of what docs
|
||||
* are in the query. It gets notified of local and remote changes, and
|
||||
* applies the query filters and limits to determine the most correct
|
||||
* possible results.
|
||||
*/
|
||||
View& view() {
|
||||
return view_;
|
||||
}
|
||||
|
||||
private:
|
||||
Query query_;
|
||||
model::TargetId target_id_;
|
||||
View view_;
|
||||
};
|
||||
|
||||
/** Tracks a limbo resolution. */
|
||||
class LimboResolution {
|
||||
public:
|
||||
LimboResolution() = default;
|
||||
|
||||
explicit LimboResolution(const model::DocumentKey& key) : key{key} {
|
||||
}
|
||||
|
||||
model::DocumentKey key;
|
||||
|
||||
/**
|
||||
* Set to true once we've received a document. This is used in
|
||||
* RemoteKeysForTarget and ultimately used by `WatchChangeAggregator` to
|
||||
* decide whether it needs to manufacture a delete event for the target once
|
||||
* the target is CURRENT.
|
||||
*/
|
||||
bool document_received = false;
|
||||
};
|
||||
|
||||
void AssertCallbackExists(absl::string_view source);
|
||||
|
||||
ViewSnapshot InitializeViewAndComputeSnapshot(const Query& query,
|
||||
model::TargetId target_id);
|
||||
|
||||
void RemoveAndCleanupTarget(model::TargetId target_id, util::Status status);
|
||||
|
||||
void RemoveLimboTarget(const model::DocumentKey& key);
|
||||
|
||||
void EmitNewSnapshotsAndNotifyLocalStore(
|
||||
const model::DocumentMap& changes,
|
||||
const absl::optional<remote::RemoteEvent>& maybe_remote_event);
|
||||
|
||||
/** Updates the limbo document state for the given target_id. */
|
||||
void UpdateTrackedLimboDocuments(
|
||||
const std::vector<LimboDocumentChange>& limbo_changes,
|
||||
model::TargetId target_id);
|
||||
|
||||
void TrackLimboChange(const LimboDocumentChange& limbo_change);
|
||||
|
||||
/**
|
||||
* Starts listens for documents in limbo that are enqueued for resolution,
|
||||
* subject to a maximum number of concurrent resolutions.
|
||||
*
|
||||
* The maximum number of concurrent limbo resolutions is defined in
|
||||
* max_concurrent_limbo_resolutions_.
|
||||
*
|
||||
* Without bounding the number of concurrent resolutions, the server can fail
|
||||
* with "resource exhausted" errors which can lead to pathological client
|
||||
* behavior as seen in https://github.com/firebase/firebase-js-sdk/issues/2683
|
||||
*/
|
||||
void PumpEnqueuedLimboResolutions();
|
||||
|
||||
void NotifyUser(model::BatchId batch_id, util::Status status);
|
||||
|
||||
/**
|
||||
* Triggers callbacks waiting for this batch id to get acknowledged by
|
||||
* server, if there are any.
|
||||
*/
|
||||
void TriggerPendingWriteCallbacks(model::BatchId batch_id);
|
||||
void FailOutstandingPendingWriteCallbacks(const std::string& message);
|
||||
|
||||
absl::optional<bundle::BundleLoader> ReadIntoLoader(
|
||||
const bundle::BundleMetadata& metadata,
|
||||
bundle::BundleReader& reader,
|
||||
api::LoadBundleTask& result_task);
|
||||
|
||||
/** The local store, used to persist mutations and cached documents. */
|
||||
local::LocalStore* local_store_ = nullptr;
|
||||
|
||||
/** The remote store for sending writes, watches, etc. to the backend. */
|
||||
remote::RemoteStore* remote_store_ = nullptr;
|
||||
|
||||
credentials::User current_user_;
|
||||
SyncEngineCallback* sync_engine_callback_ = nullptr;
|
||||
|
||||
/**
|
||||
* Used for creating the TargetId for the listens used to resolve limbo
|
||||
* documents.
|
||||
*/
|
||||
TargetIdGenerator target_id_generator_;
|
||||
|
||||
/** Stores user completion blocks, indexed by User and BatchId. */
|
||||
std::unordered_map<credentials::User,
|
||||
std::unordered_map<model::BatchId, util::StatusCallback>,
|
||||
credentials::HashUser>
|
||||
mutation_callbacks_;
|
||||
|
||||
/** Stores user callbacks waiting for pending writes to be acknowledged. */
|
||||
std::unordered_map<model::BatchId, std::vector<util::StatusCallback>>
|
||||
pending_writes_callbacks_;
|
||||
|
||||
// Shared pointers are used to avoid creating and storing two copies of the
|
||||
// same `QueryView` and for consistency with other platforms.
|
||||
/** QueryViews for all active queries, indexed by query. */
|
||||
std::unordered_map<Query, std::shared_ptr<QueryView>> query_views_by_query_;
|
||||
|
||||
/** Queries mapped to Targets, indexed by target ID. */
|
||||
std::unordered_map<model::TargetId, std::vector<Query>> queries_by_target_;
|
||||
|
||||
const size_t max_concurrent_limbo_resolutions_;
|
||||
|
||||
/**
|
||||
* The keys of documents that are in limbo for which we haven't yet started a
|
||||
* limbo resolution query.
|
||||
*/
|
||||
util::RandomAccessQueue<model::DocumentKey, model::DocumentKeyHash>
|
||||
enqueued_limbo_resolutions_;
|
||||
|
||||
/**
|
||||
* Keeps track of the target ID for each document that is in limbo with an
|
||||
* active target.
|
||||
*/
|
||||
std::map<model::DocumentKey, model::TargetId> active_limbo_targets_by_key_;
|
||||
|
||||
/**
|
||||
* Keeps track of the information about an active limbo resolution for each
|
||||
* active target ID that was started for the purpose of limbo resolution.
|
||||
*/
|
||||
std::map<model::TargetId, LimboResolution>
|
||||
active_limbo_resolutions_by_target_;
|
||||
|
||||
/** Used to track any documents that are currently in limbo. */
|
||||
local::ReferenceSet limbo_document_refs_;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_SYNC_ENGINE_H_
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_CORE_SYNC_ENGINE_CALLBACK_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_SYNC_ENGINE_CALLBACK_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "Firestore/core/src/core/core_fwd.h"
|
||||
#include "Firestore/core/src/model/types.h"
|
||||
#include "Firestore/core/src/util/status_fwd.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* Interface implemented by `EventManager` to handle notifications from
|
||||
* `SyncEngine`.
|
||||
*/
|
||||
class SyncEngineCallback {
|
||||
public:
|
||||
virtual ~SyncEngineCallback() = default;
|
||||
|
||||
/** Handles a change in online state. */
|
||||
virtual void HandleOnlineStateChange(model::OnlineState online_state) = 0;
|
||||
/** Handles new view snapshots. */
|
||||
virtual void OnViewSnapshots(std::vector<core::ViewSnapshot>&& snapshots) = 0;
|
||||
/** Handles the failure of a query. */
|
||||
virtual void OnError(const core::Query& query, const util::Status& error) = 0;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_SYNC_ENGINE_CALLBACK_H_
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/core/target.h"
|
||||
|
||||
#include <ostream>
|
||||
|
||||
#include "Firestore/core/src/core/field_filter.h"
|
||||
#include "Firestore/core/src/core/operator.h"
|
||||
#include "Firestore/core/src/model/document_key.h"
|
||||
#include "Firestore/core/src/model/field_path.h"
|
||||
#include "Firestore/core/src/model/resource_path.h"
|
||||
#include "Firestore/core/src/util/equality.h"
|
||||
#include "Firestore/core/src/util/hard_assert.h"
|
||||
#include "Firestore/core/src/util/hashing.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
using model::DocumentKey;
|
||||
|
||||
// MARK: - Accessors
|
||||
|
||||
bool Target::IsDocumentQuery() const {
|
||||
return DocumentKey::IsDocumentKey(path_) && !collection_group_ &&
|
||||
filters_.empty();
|
||||
}
|
||||
|
||||
const std::string& Target::CanonicalId() const {
|
||||
if (!canonical_id_.empty()) return canonical_id_;
|
||||
|
||||
std::string result;
|
||||
absl::StrAppend(&result, path_.CanonicalString());
|
||||
|
||||
if (collection_group_) {
|
||||
absl::StrAppend(&result, "|cg:", *collection_group_);
|
||||
}
|
||||
|
||||
// Add filters.
|
||||
absl::StrAppend(&result, "|f:");
|
||||
for (const auto& filter : filters_) {
|
||||
absl::StrAppend(&result, filter.CanonicalId());
|
||||
}
|
||||
|
||||
// Add order by.
|
||||
absl::StrAppend(&result, "|ob:");
|
||||
for (const OrderBy& order_by : order_bys()) {
|
||||
absl::StrAppend(&result, order_by.CanonicalId());
|
||||
}
|
||||
|
||||
// Add limit.
|
||||
if (limit_ != kNoLimit) {
|
||||
absl::StrAppend(&result, "|l:", limit_);
|
||||
}
|
||||
|
||||
if (start_at_) {
|
||||
absl::StrAppend(&result, "|lb:", start_at_->CanonicalId());
|
||||
}
|
||||
|
||||
if (end_at_) {
|
||||
absl::StrAppend(&result, "|ub:", end_at_->CanonicalId());
|
||||
}
|
||||
|
||||
canonical_id_ = std::move(result);
|
||||
return canonical_id_;
|
||||
}
|
||||
|
||||
size_t Target::Hash() const {
|
||||
return util::Hash(CanonicalId());
|
||||
}
|
||||
|
||||
std::string Target::ToString() const {
|
||||
return absl::StrCat("Target(canonical_id=", CanonicalId(), ")");
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Target& target) {
|
||||
return os << target.ToString();
|
||||
}
|
||||
|
||||
bool operator==(const Target& lhs, const Target& rhs) {
|
||||
return lhs.path() == rhs.path() &&
|
||||
util::Equals(lhs.collection_group(), rhs.collection_group()) &&
|
||||
lhs.filters() == rhs.filters() && lhs.order_bys() == rhs.order_bys() &&
|
||||
lhs.limit() == rhs.limit() && lhs.start_at() == rhs.start_at() &&
|
||||
lhs.end_at() == rhs.end_at();
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_CORE_TARGET_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_TARGET_H_
|
||||
|
||||
#include <iosfwd>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/core/bound.h"
|
||||
#include "Firestore/core/src/core/filter.h"
|
||||
#include "Firestore/core/src/core/order_by.h"
|
||||
#include "Firestore/core/src/immutable/append_only_list.h"
|
||||
#include "Firestore/core/src/model/resource_path.h"
|
||||
#include "Firestore/core/src/remote/serializer.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace bundle {
|
||||
class BundleSerializer;
|
||||
}
|
||||
namespace core {
|
||||
|
||||
using CollectionGroupId = std::shared_ptr<const std::string>;
|
||||
|
||||
/**
|
||||
* A Target represents the WatchTarget representation of a Query, which is
|
||||
* used by the LocalStore and the RemoteStore to keep track of and to execute
|
||||
* backend queries. While multiple Queries can map to the same Target, each
|
||||
* Target maps to a single WatchTarget in RemoteStore and a single TargetData
|
||||
* entry in persistence.
|
||||
*/
|
||||
class Target {
|
||||
public:
|
||||
static constexpr int32_t kNoLimit = std::numeric_limits<int32_t>::max();
|
||||
|
||||
Target() = default;
|
||||
|
||||
// MARK: - Accessors
|
||||
|
||||
/** The base path of the target. */
|
||||
const model::ResourcePath& path() const {
|
||||
return path_;
|
||||
}
|
||||
|
||||
/** The collection group of the target, if any. */
|
||||
const std::shared_ptr<const std::string>& collection_group() const {
|
||||
return collection_group_;
|
||||
}
|
||||
|
||||
/** Returns true if this Target is for a specific document. */
|
||||
bool IsDocumentQuery() const;
|
||||
|
||||
/** The filters on the documents returned by the target. */
|
||||
const FilterList& filters() const {
|
||||
return filters_;
|
||||
}
|
||||
|
||||
/** Returns the list of ordering constraints by the target. */
|
||||
const OrderByList& order_bys() const {
|
||||
return order_bys_;
|
||||
}
|
||||
|
||||
int32_t limit() const {
|
||||
return limit_;
|
||||
}
|
||||
|
||||
const absl::optional<Bound>& start_at() const {
|
||||
return start_at_;
|
||||
}
|
||||
|
||||
const absl::optional<Bound>& end_at() const {
|
||||
return end_at_;
|
||||
}
|
||||
|
||||
const std::string& CanonicalId() const;
|
||||
|
||||
std::string ToString() const;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const Target& target);
|
||||
|
||||
size_t Hash() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Initializes a Target with a path and additional query constraints.
|
||||
* Path must currently be empty if this is a collection group query.
|
||||
*
|
||||
* NOTE: This is made private and only accessible by `Query` and `Serializer`.
|
||||
* You should always construct Target from `Query.toTarget` because Query
|
||||
* provides an implicit `orderBy` property.
|
||||
*/
|
||||
Target(model::ResourcePath path,
|
||||
CollectionGroupId collection_group,
|
||||
FilterList filters,
|
||||
OrderByList order_bys,
|
||||
int32_t limit,
|
||||
absl::optional<Bound> start_at,
|
||||
absl::optional<Bound> end_at)
|
||||
: path_(std::move(path)),
|
||||
collection_group_(std::move(collection_group)),
|
||||
filters_(std::move(filters)),
|
||||
order_bys_(std::move(order_bys)),
|
||||
limit_(limit),
|
||||
start_at_(std::move(start_at)),
|
||||
end_at_(std::move(end_at)) {
|
||||
}
|
||||
friend class Query;
|
||||
friend class remote::Serializer;
|
||||
friend class bundle::BundleSerializer;
|
||||
|
||||
model::ResourcePath path_;
|
||||
std::shared_ptr<const std::string> collection_group_;
|
||||
FilterList filters_;
|
||||
OrderByList order_bys_;
|
||||
int32_t limit_ = kNoLimit;
|
||||
absl::optional<Bound> start_at_;
|
||||
absl::optional<Bound> end_at_;
|
||||
|
||||
mutable std::string canonical_id_;
|
||||
};
|
||||
|
||||
bool operator==(const Target& lhs, const Target& rhs);
|
||||
|
||||
inline bool operator!=(const Target& lhs, const Target& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<firebase::firestore::core::Target> {
|
||||
size_t operator()(const firebase::firestore::core::Target& target) const {
|
||||
return target.Hash();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_TARGET_H_
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2018 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 "Firestore/core/src/core/target_id_generator.h"
|
||||
|
||||
#include "Firestore/core/src/util/hard_assert.h"
|
||||
|
||||
using firebase::firestore::model::TargetId;
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
TargetIdGenerator::TargetIdGenerator(TargetIdGeneratorId generator_id,
|
||||
TargetId seed)
|
||||
: generator_id_(generator_id) {
|
||||
seek(seed);
|
||||
}
|
||||
|
||||
void TargetIdGenerator::seek(TargetId target_id) {
|
||||
const TargetId generator = static_cast<TargetId>(generator_id_);
|
||||
HARD_ASSERT((target_id & generator) == generator,
|
||||
"Cannot supply target ID from different generator ID");
|
||||
next_id_ = target_id;
|
||||
}
|
||||
|
||||
TargetId TargetIdGenerator::NextId() {
|
||||
int next_id = next_id_;
|
||||
next_id_ += 1 << kReservedBits;
|
||||
return next_id;
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2018 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 FIRESTORE_CORE_SRC_CORE_TARGET_ID_GENERATOR_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_TARGET_ID_GENERATOR_H_
|
||||
|
||||
#include "Firestore/core/src/model/types.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
/** The set of all valid generators. */
|
||||
enum class TargetIdGeneratorId { TargetCache = 0, SyncEngine = 1 };
|
||||
|
||||
/**
|
||||
* Generates monotonically increasing target IDs for sending targets to the
|
||||
* watch stream.
|
||||
*
|
||||
* The client constructs two generators, one for the query cache (via
|
||||
* `QueryCacheTargetIdGenerator(int after)`), and one for limbo documents (via
|
||||
* `SyncEngineTargetIdGenerator()`). These two generators produce
|
||||
* non-overlapping IDs (by using even and odd IDs respectively).
|
||||
*
|
||||
* By separating the target ID space, the query cache can generate target IDs
|
||||
* that persist across client restarts, while sync engine can independently
|
||||
* generate in-memory target IDs that are transient and can be reused after a
|
||||
* restart.
|
||||
*
|
||||
* Not thread-safe.
|
||||
*/
|
||||
// TODO(mrschmidt): Explore removing this class in favor of generating these IDs
|
||||
// directly in SyncEngine and LocalStore.
|
||||
class TargetIdGenerator {
|
||||
public:
|
||||
TargetIdGenerator() = default;
|
||||
|
||||
/**
|
||||
* Creates and returns the TargetIdGenerator for the local store.
|
||||
*
|
||||
* @param after An ID to start at. Every call to NextId returns a larger id.
|
||||
* @return An instance of TargetIdGenerator.
|
||||
*/
|
||||
static TargetIdGenerator TargetCacheTargetIdGenerator(model::TargetId after) {
|
||||
TargetIdGenerator generator(TargetIdGeneratorId::TargetCache, after);
|
||||
// Make sure that the next call to `NextId()` returns the first value after
|
||||
// 'after'.
|
||||
generator.NextId();
|
||||
return generator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns the TargetIdGenerator for the sync engine.
|
||||
*
|
||||
* @return An instance of TargetIdGenerator.
|
||||
*/
|
||||
static TargetIdGenerator SyncEngineTargetIdGenerator() {
|
||||
// Sync engine assigns target IDs for limbo document detection.
|
||||
return TargetIdGenerator(TargetIdGeneratorId::SyncEngine, 1);
|
||||
}
|
||||
|
||||
TargetIdGeneratorId generator_id() {
|
||||
return generator_id_;
|
||||
}
|
||||
|
||||
model::TargetId NextId();
|
||||
|
||||
private:
|
||||
TargetIdGenerator(TargetIdGeneratorId generator_id, model::TargetId seed);
|
||||
void seek(model::TargetId target_id);
|
||||
|
||||
TargetIdGeneratorId generator_id_ = TargetIdGeneratorId::TargetCache;
|
||||
model::TargetId next_id_ = 0;
|
||||
|
||||
static const int kReservedBits = 1;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_TARGET_ID_GENERATOR_H_
|
||||
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/core/transaction.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/include/firebase/firestore/firestore_errors.h"
|
||||
#include "Firestore/core/src/core/user_data.h"
|
||||
#include "Firestore/core/src/model/delete_mutation.h"
|
||||
#include "Firestore/core/src/model/document.h"
|
||||
#include "Firestore/core/src/model/verify_mutation.h"
|
||||
#include "Firestore/core/src/remote/datastore.h"
|
||||
#include "Firestore/core/src/util/hard_assert.h"
|
||||
|
||||
using firebase::firestore::Error;
|
||||
using firebase::firestore::core::ParsedSetData;
|
||||
using firebase::firestore::core::ParsedUpdateData;
|
||||
using firebase::firestore::model::DeleteMutation;
|
||||
using firebase::firestore::model::Document;
|
||||
using firebase::firestore::model::DocumentKey;
|
||||
using firebase::firestore::model::DocumentKeyHash;
|
||||
using firebase::firestore::model::Mutation;
|
||||
using firebase::firestore::model::Precondition;
|
||||
using firebase::firestore::model::SnapshotVersion;
|
||||
using firebase::firestore::model::VerifyMutation;
|
||||
using firebase::firestore::remote::Datastore;
|
||||
using firebase::firestore::util::Status;
|
||||
using firebase::firestore::util::StatusOr;
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
Transaction::Transaction(std::shared_ptr<Datastore> datastore)
|
||||
: datastore_{datastore} {
|
||||
}
|
||||
|
||||
Status Transaction::RecordVersion(const Document& doc) {
|
||||
SnapshotVersion doc_version;
|
||||
|
||||
if (doc->is_found_document()) {
|
||||
doc_version = doc->version();
|
||||
} else if (doc->is_no_document()) {
|
||||
// For deleted docs, we must record an explicit no version to build the
|
||||
// right precondition when writing.
|
||||
doc_version = SnapshotVersion::None();
|
||||
} else {
|
||||
HARD_FAIL("Unexpected document type in transaction: %s", doc.ToString());
|
||||
}
|
||||
|
||||
absl::optional<SnapshotVersion> existing_version = GetVersion(doc->key());
|
||||
if (existing_version.has_value()) {
|
||||
if (doc_version != existing_version.value()) {
|
||||
// This transaction will fail no matter what.
|
||||
return Status{Error::kErrorAborted,
|
||||
"Document version changed between two reads."};
|
||||
}
|
||||
return Status::OK();
|
||||
} else {
|
||||
read_versions_[doc->key()] = doc_version;
|
||||
return Status::OK();
|
||||
}
|
||||
}
|
||||
|
||||
void Transaction::Lookup(const std::vector<DocumentKey>& keys,
|
||||
LookupCallback&& callback) {
|
||||
EnsureCommitNotCalled();
|
||||
|
||||
if (!mutations_.empty()) {
|
||||
Status lookup_error = Status{Error::kErrorInvalidArgument,
|
||||
"Firestore transactions require all reads to "
|
||||
"be executed before all writes"};
|
||||
callback(lookup_error);
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<Datastore> datastore = datastore_.lock();
|
||||
if (!datastore) {
|
||||
callback(Status(Error::kErrorFailedPrecondition,
|
||||
"The client has already been terminated."));
|
||||
return;
|
||||
}
|
||||
|
||||
datastore->LookupDocuments(
|
||||
keys,
|
||||
[this, callback](const StatusOr<std::vector<Document>>& maybe_documents) {
|
||||
if (!maybe_documents.ok()) {
|
||||
callback(maybe_documents.status());
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& documents = maybe_documents.ValueOrDie();
|
||||
for (const Document& doc : documents) {
|
||||
Status record_error = RecordVersion(doc);
|
||||
if (!record_error.ok()) {
|
||||
callback(record_error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(varconst): see if `maybe_documents` can be moved into the
|
||||
// callback.
|
||||
callback(maybe_documents);
|
||||
});
|
||||
}
|
||||
|
||||
void Transaction::WriteMutations(std::vector<Mutation>&& mutations) {
|
||||
EnsureCommitNotCalled();
|
||||
// `move` will become appropriate once `Mutation` is replaced by the C++
|
||||
// equivalent.
|
||||
std::move(mutations.begin(), mutations.end(), std::back_inserter(mutations_));
|
||||
}
|
||||
|
||||
Precondition Transaction::CreatePrecondition(const DocumentKey& key) {
|
||||
absl::optional<SnapshotVersion> version = GetVersion(key);
|
||||
if (written_docs_.count(key) == 0 && version.has_value()) {
|
||||
return Precondition::UpdateTime(version.value());
|
||||
} else {
|
||||
return Precondition::None();
|
||||
}
|
||||
}
|
||||
|
||||
StatusOr<Precondition> Transaction::CreateUpdatePrecondition(
|
||||
const DocumentKey& key) {
|
||||
absl::optional<SnapshotVersion> version = GetVersion(key);
|
||||
// The first time a document is written, we want to take into account the
|
||||
// read time and existence.
|
||||
if (written_docs_.count(key) == 0 && version.has_value()) {
|
||||
if (version.value() == SnapshotVersion::None()) {
|
||||
// The document doesn't exist, so fail the transaction.
|
||||
//
|
||||
// This has to be validated locally because you can't send a
|
||||
// precondition that a document does not exist without changing the
|
||||
// semantics of the backend write to be an insert. This is the reverse
|
||||
// of what we want, since we want to assert that the document doesn't
|
||||
// exist but then send the update and have it fail. Since we can't
|
||||
// express that to the backend, we have to validate locally.
|
||||
//
|
||||
// Note: this can change once we can send separate verify writes in the
|
||||
// transaction.
|
||||
return Status{Error::kErrorInvalidArgument,
|
||||
"Can't update a document that doesn't exist."};
|
||||
}
|
||||
// Document exists, just base precondition on document update time.
|
||||
return Precondition::UpdateTime(version.value());
|
||||
} else {
|
||||
// Document was not read, so we just use the preconditions for a blind
|
||||
// update.
|
||||
return Precondition::Exists(true);
|
||||
}
|
||||
}
|
||||
|
||||
void Transaction::Set(const DocumentKey& key, ParsedSetData&& data) {
|
||||
WriteMutations({std::move(data).ToMutation(key, CreatePrecondition(key))});
|
||||
written_docs_.insert(key);
|
||||
}
|
||||
|
||||
void Transaction::Update(const DocumentKey& key, ParsedUpdateData&& data) {
|
||||
StatusOr<Precondition> maybe_precondition = CreateUpdatePrecondition(key);
|
||||
if (!maybe_precondition.ok()) {
|
||||
last_write_error_ = maybe_precondition.status();
|
||||
} else {
|
||||
WriteMutations(
|
||||
{std::move(data).ToMutation(key, maybe_precondition.ValueOrDie())});
|
||||
}
|
||||
written_docs_.insert(key);
|
||||
}
|
||||
|
||||
void Transaction::Delete(const DocumentKey& key) {
|
||||
Mutation mutation = DeleteMutation(key, CreatePrecondition(key));
|
||||
WriteMutations({mutation});
|
||||
written_docs_.insert(key);
|
||||
}
|
||||
|
||||
void Transaction::Commit(util::StatusCallback&& callback) {
|
||||
EnsureCommitNotCalled();
|
||||
|
||||
// If there was an error writing, raise that error now
|
||||
if (!last_write_error_.ok()) {
|
||||
callback(last_write_error_);
|
||||
return;
|
||||
}
|
||||
|
||||
// Make a list of read documents that haven't been written.
|
||||
std::unordered_set<DocumentKey, DocumentKeyHash> unwritten;
|
||||
for (const auto& kv : read_versions_) {
|
||||
unwritten.insert(kv.first);
|
||||
}
|
||||
// For each mutation, note that the doc was written.
|
||||
for (const Mutation& mutation : mutations_) {
|
||||
unwritten.erase(mutation.key());
|
||||
}
|
||||
|
||||
// For each document that was read but not written to, we want to perform a
|
||||
// `verify` operation.
|
||||
for (const DocumentKey& key : unwritten) {
|
||||
mutations_.push_back(VerifyMutation(key, CreatePrecondition(key)));
|
||||
}
|
||||
committed_ = true;
|
||||
|
||||
std::shared_ptr<Datastore> datastore = datastore_.lock();
|
||||
if (!datastore) {
|
||||
callback(Status(Error::kErrorFailedPrecondition,
|
||||
"The client has already been terminated."));
|
||||
return;
|
||||
}
|
||||
|
||||
datastore->CommitMutations(mutations_, std::move(callback));
|
||||
}
|
||||
|
||||
void Transaction::MarkPermanentlyFailed() {
|
||||
permanent_error_ = true;
|
||||
}
|
||||
|
||||
bool Transaction::IsPermanentlyFailed() const {
|
||||
return permanent_error_;
|
||||
}
|
||||
|
||||
void Transaction::EnsureCommitNotCalled() {
|
||||
HARD_ASSERT(!committed_,
|
||||
"A transaction object cannot be used after its "
|
||||
"update callback has been invoked.");
|
||||
}
|
||||
|
||||
absl::optional<SnapshotVersion> Transaction::GetVersion(
|
||||
const DocumentKey& key) const {
|
||||
auto found = read_versions_.find(key);
|
||||
if (found != read_versions_.end()) {
|
||||
return found->second;
|
||||
}
|
||||
return absl::nullopt;
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_CORE_TRANSACTION_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_TRANSACTION_H_
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "Firestore/core/src/model/document_key.h"
|
||||
#include "Firestore/core/src/model/mutation.h"
|
||||
#include "Firestore/core/src/model/snapshot_version.h"
|
||||
#include "Firestore/core/src/util/status.h"
|
||||
#include "Firestore/core/src/util/statusor.h"
|
||||
#include "absl/types/any.h"
|
||||
#include "absl/types/optional.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
|
||||
namespace model {
|
||||
class Precondition;
|
||||
class Document;
|
||||
} // namespace model
|
||||
|
||||
namespace remote {
|
||||
class Datastore;
|
||||
} // namespace remote
|
||||
|
||||
namespace core {
|
||||
|
||||
class ParsedSetData;
|
||||
class ParsedUpdateData;
|
||||
|
||||
class Transaction {
|
||||
public:
|
||||
using LookupCallback =
|
||||
std::function<void(const util::StatusOr<std::vector<model::Document>>&)>;
|
||||
|
||||
Transaction() = default;
|
||||
explicit Transaction(std::shared_ptr<remote::Datastore> datastore);
|
||||
|
||||
/**
|
||||
* Takes a set of keys and asynchronously attempts to fetch all the documents
|
||||
* from the backend, ignoring any local changes.
|
||||
*/
|
||||
void Lookup(const std::vector<model::DocumentKey>& keys,
|
||||
LookupCallback&& callback);
|
||||
|
||||
/**
|
||||
* Stores mutation for the given key and set data, to be committed when
|
||||
* `Commit` is called.
|
||||
*/
|
||||
void Set(const model::DocumentKey& key, ParsedSetData&& data);
|
||||
|
||||
/**
|
||||
* Stores mutations for the given key and update data, to be committed when
|
||||
* `Commit` is called.
|
||||
*/
|
||||
void Update(const model::DocumentKey& key, ParsedUpdateData&& data);
|
||||
|
||||
/**
|
||||
* Stores a delete mutation for the given key, to be committed when `Commit`
|
||||
* is called.
|
||||
*/
|
||||
void Delete(const model::DocumentKey& key);
|
||||
|
||||
/**
|
||||
* Attempts to commit the mutations set on this transaction. Invokes the given
|
||||
* callback when finished. Once this is called, no other mutations or
|
||||
* commits are allowed on the transaction.
|
||||
*/
|
||||
void Commit(util::StatusCallback&& callback);
|
||||
|
||||
/**
|
||||
* Marks the transaction as permanently failed, so the transaction will not
|
||||
* retry.
|
||||
*/
|
||||
void MarkPermanentlyFailed();
|
||||
|
||||
/**
|
||||
* Checks if the transaction is permanently failed.
|
||||
*/
|
||||
bool IsPermanentlyFailed() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Every time a document is read, this should be called to record its version.
|
||||
* If we read two different versions of the same document, this will return an
|
||||
* error. When the transaction is committed, the versions recorded will be set
|
||||
* as preconditions on the writes sent to the backend.
|
||||
*/
|
||||
util::Status RecordVersion(const model::Document& doc);
|
||||
|
||||
/** Stores mutations to be written when `Commit` is called. */
|
||||
void WriteMutations(std::vector<model::Mutation>&& mutations);
|
||||
|
||||
/**
|
||||
* Returns version of this doc when it was read in this transaction as a
|
||||
* precondition, or no precondition if it was not read.
|
||||
*/
|
||||
model::Precondition CreatePrecondition(const model::DocumentKey& key);
|
||||
|
||||
/**
|
||||
* Returns the precondition for a document if the operation is an update. Will
|
||||
* return a failed status if an error occurred.
|
||||
*/
|
||||
util::StatusOr<model::Precondition> CreateUpdatePrecondition(
|
||||
const model::DocumentKey& key);
|
||||
|
||||
void EnsureCommitNotCalled();
|
||||
|
||||
absl::optional<model::SnapshotVersion> GetVersion(
|
||||
const model::DocumentKey& key) const;
|
||||
|
||||
std::weak_ptr<remote::Datastore> datastore_;
|
||||
|
||||
std::vector<model::Mutation> mutations_;
|
||||
bool committed_ = false;
|
||||
bool permanent_error_ = false;
|
||||
|
||||
/**
|
||||
* A deferred usage error that occurred previously in this transaction that
|
||||
* will cause the transaction to fail once it actually commits.
|
||||
*/
|
||||
util::Status last_write_error_;
|
||||
|
||||
/**
|
||||
* Set of documents that have been written in the transaction.
|
||||
*
|
||||
* When there's more than one write to the same key in a transaction, any
|
||||
* writes after the first are handled differently.
|
||||
*/
|
||||
std::unordered_set<model::DocumentKey, model::DocumentKeyHash> written_docs_;
|
||||
|
||||
std::unordered_map<model::DocumentKey,
|
||||
model::SnapshotVersion,
|
||||
model::DocumentKeyHash>
|
||||
read_versions_;
|
||||
};
|
||||
|
||||
using TransactionResultCallback = util::StatusCallback;
|
||||
|
||||
/**
|
||||
* TransactionUpdateCallback is a block that wraps a user's transaction update
|
||||
* block internally.
|
||||
*
|
||||
* The update block will be called with two parameters:
|
||||
* * The transaction: an object with methods for performing reads and writes
|
||||
* within the transaction.
|
||||
* * The callback: to be called by the block once the user's code is finished.
|
||||
*/
|
||||
using TransactionUpdateCallback = std::function<void(
|
||||
std::shared_ptr<Transaction>, TransactionResultCallback)>;
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_TRANSACTION_H_
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/core/transaction_runner.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/remote/exponential_backoff.h"
|
||||
#include "absl/algorithm/container.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
namespace {
|
||||
|
||||
using remote::RemoteStore;
|
||||
using util::AsyncQueue;
|
||||
using util::Status;
|
||||
using util::TimerId;
|
||||
|
||||
/** Maximum number of times a transaction can be attempted before failing. */
|
||||
constexpr int kMaxAttemptsCount = 5;
|
||||
|
||||
bool IsRetryableTransactionError(const util::Status& error) {
|
||||
// In transactions, the backend will fail outdated reads with
|
||||
// FAILED_PRECONDITION and non-matching document versions with ABORTED. These
|
||||
// errors should be retried.
|
||||
Error code = error.code();
|
||||
return code == Error::kErrorAborted ||
|
||||
code == Error::kErrorFailedPrecondition ||
|
||||
!remote::Datastore::IsPermanentError(error);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TransactionRunner::TransactionRunner(const std::shared_ptr<AsyncQueue>& queue,
|
||||
RemoteStore* remote_store,
|
||||
TransactionUpdateCallback update_callback,
|
||||
TransactionResultCallback result_callback)
|
||||
: queue_{queue},
|
||||
remote_store_{remote_store},
|
||||
update_callback_{std::move(update_callback)},
|
||||
result_callback_{std::move(result_callback)},
|
||||
backoff_{queue_, TimerId::RetryTransaction},
|
||||
attempts_remaining_{kMaxAttemptsCount} {
|
||||
}
|
||||
|
||||
void TransactionRunner::Run() {
|
||||
queue_->VerifyIsCurrentQueue();
|
||||
attempts_remaining_ -= 1;
|
||||
|
||||
auto shared_this = this->shared_from_this();
|
||||
backoff_.BackoffAndRun([shared_this] {
|
||||
std::shared_ptr<Transaction> transaction =
|
||||
shared_this->remote_store_->CreateTransaction();
|
||||
shared_this->update_callback_(
|
||||
transaction, [transaction, shared_this](const util::Status& status) {
|
||||
shared_this->queue_->Enqueue([transaction, shared_this, status] {
|
||||
shared_this->ContinueCommit(transaction, status);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void TransactionRunner::ContinueCommit(
|
||||
const std::shared_ptr<Transaction>& transaction, util::Status status) {
|
||||
if (!status.ok()) {
|
||||
HandleTransactionError(transaction, std::move(status));
|
||||
} else {
|
||||
auto shared_this = this->shared_from_this();
|
||||
transaction->Commit([shared_this, transaction](Status commit_status) {
|
||||
shared_this->DispatchResult(transaction, std::move(commit_status));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void TransactionRunner::DispatchResult(
|
||||
const std::shared_ptr<Transaction>& transaction, Status status) {
|
||||
if (status.ok()) {
|
||||
result_callback_(std::move(status));
|
||||
} else {
|
||||
HandleTransactionError(transaction, std::move(status));
|
||||
}
|
||||
}
|
||||
|
||||
void TransactionRunner::HandleTransactionError(
|
||||
const std::shared_ptr<Transaction>& transaction, Status status) {
|
||||
if (attempts_remaining_ > 0 && IsRetryableTransactionError(status) &&
|
||||
!transaction->IsPermanentlyFailed()) {
|
||||
Run();
|
||||
} else {
|
||||
result_callback_(std::move(status));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_CORE_TRANSACTION_RUNNER_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_TRANSACTION_RUNNER_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "Firestore/core/src/core/transaction.h"
|
||||
#include "Firestore/core/src/remote/exponential_backoff.h"
|
||||
#include "Firestore/core/src/remote/remote_store.h"
|
||||
#include "Firestore/core/src/util/async_queue.h"
|
||||
#include "Firestore/core/src/util/status_fwd.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* TransactionRunner encapsulates the logic needed to run and retry transactions
|
||||
* with backoff.
|
||||
*
|
||||
* TransactionRunner manages its own lifetime by keeping itself alive until all
|
||||
* retries are completed. It must be allocated via
|
||||
* std::make_shared<TransactionRunner> because the implementation expects to be
|
||||
* able to call std::shared_from_this to create additional references that will
|
||||
* keep it alive.
|
||||
*/
|
||||
class TransactionRunner
|
||||
: public std::enable_shared_from_this<TransactionRunner> {
|
||||
public:
|
||||
TransactionRunner(const std::shared_ptr<util::AsyncQueue>& queue,
|
||||
remote::RemoteStore* remote_store,
|
||||
core::TransactionUpdateCallback update_callback,
|
||||
core::TransactionResultCallback result_callback);
|
||||
|
||||
/**
|
||||
* Runs the transaction and calls the result_callback_ with the result.
|
||||
*/
|
||||
void Run();
|
||||
|
||||
private:
|
||||
void ContinueCommit(const std::shared_ptr<Transaction>& transaction,
|
||||
util::Status status);
|
||||
|
||||
void DispatchResult(const std::shared_ptr<Transaction>& transaction,
|
||||
util::Status status);
|
||||
|
||||
void HandleTransactionError(const std::shared_ptr<Transaction>& transaction,
|
||||
util::Status status);
|
||||
|
||||
std::shared_ptr<util::AsyncQueue> queue_;
|
||||
remote::RemoteStore* remote_store_;
|
||||
core::TransactionUpdateCallback update_callback_;
|
||||
core::TransactionResultCallback result_callback_;
|
||||
remote::ExponentialBackoff backoff_;
|
||||
int attempts_remaining_;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_TRANSACTION_RUNNER_H_
|
||||
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
* Copyright 2018 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/core/user_data.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/model/document.h"
|
||||
#include "Firestore/core/src/model/document_key.h"
|
||||
#include "Firestore/core/src/model/mutation.h"
|
||||
#include "Firestore/core/src/model/patch_mutation.h"
|
||||
#include "Firestore/core/src/model/set_mutation.h"
|
||||
#include "Firestore/core/src/model/transform_operation.h"
|
||||
#include "Firestore/core/src/util/exception.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/strings/match.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
using model::DocumentKey;
|
||||
using model::FieldMask;
|
||||
using model::FieldPath;
|
||||
using model::FieldTransform;
|
||||
using model::Mutation;
|
||||
using model::ObjectValue;
|
||||
using model::PatchMutation;
|
||||
using model::Precondition;
|
||||
using model::SetMutation;
|
||||
using model::TransformOperation;
|
||||
using util::ThrowInvalidArgument;
|
||||
|
||||
// MARK: - ParseAccumulator
|
||||
|
||||
ParseContext ParseAccumulator::RootContext() {
|
||||
return ParseContext{
|
||||
this, absl::make_unique<FieldPath>(FieldPath::EmptyPath()), false};
|
||||
}
|
||||
|
||||
bool ParseAccumulator::Contains(const FieldPath& field_path) const {
|
||||
for (const FieldPath& field : field_mask_) {
|
||||
if (field_path.IsPrefixOf(field)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (const FieldTransform& field_transform : field_transforms_) {
|
||||
if (field_path.IsPrefixOf(field_transform.path())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ParseAccumulator::AddToFieldMask(FieldPath field_path) {
|
||||
field_mask_.insert(std::move(field_path));
|
||||
}
|
||||
|
||||
void ParseAccumulator::AddToFieldTransforms(
|
||||
FieldPath field_path, TransformOperation transform_operation) {
|
||||
// TODO(mrschmidt): Validate that the paths are unique
|
||||
field_transforms_.emplace_back(std::move(field_path),
|
||||
std::move(transform_operation));
|
||||
}
|
||||
|
||||
ParsedSetData ParseAccumulator::MergeData(ObjectValue data) && {
|
||||
return ParsedSetData{std::move(data), FieldMask{std::move(field_mask_)},
|
||||
std::move(field_transforms_)};
|
||||
}
|
||||
|
||||
ParsedSetData ParseAccumulator::MergeData(ObjectValue data,
|
||||
model::FieldMask user_field_mask) && {
|
||||
std::vector<FieldTransform> covered_field_transforms;
|
||||
|
||||
for (FieldTransform& field_transform : field_transforms_) {
|
||||
if (user_field_mask.covers(field_transform.path())) {
|
||||
covered_field_transforms.push_back(std::move(field_transform));
|
||||
}
|
||||
}
|
||||
|
||||
return ParsedSetData{std::move(data), std::move(user_field_mask),
|
||||
std::move(covered_field_transforms)};
|
||||
}
|
||||
|
||||
ParsedSetData ParseAccumulator::SetData(ObjectValue data) && {
|
||||
return ParsedSetData{std::move(data), std::move(field_transforms_)};
|
||||
}
|
||||
|
||||
ParsedUpdateData ParseAccumulator::UpdateData(ObjectValue data) && {
|
||||
return ParsedUpdateData{std::move(data), FieldMask{std::move(field_mask_)},
|
||||
std::move(field_transforms_)};
|
||||
}
|
||||
|
||||
// MARK: - ParseContext
|
||||
|
||||
namespace {
|
||||
|
||||
const char* RESERVED_FIELD_DESIGNATOR = "__";
|
||||
|
||||
} // namespace
|
||||
|
||||
ParseContext ParseContext::ChildContext(const std::string& field_name) {
|
||||
std::unique_ptr<FieldPath> path;
|
||||
if (path_) {
|
||||
path = absl::make_unique<FieldPath>(path_->Append(field_name));
|
||||
}
|
||||
|
||||
ParseContext context{accumulator_, std::move(path), false};
|
||||
context.ValidatePathSegment(field_name);
|
||||
return context;
|
||||
}
|
||||
|
||||
ParseContext ParseContext::ChildContext(const FieldPath& field_path) {
|
||||
std::unique_ptr<FieldPath> path;
|
||||
if (path_) {
|
||||
path = absl::make_unique<FieldPath>(path_->Append(field_path));
|
||||
}
|
||||
|
||||
ParseContext context{accumulator_, std::move(path), false};
|
||||
context.ValidatePath();
|
||||
return context;
|
||||
}
|
||||
|
||||
ParseContext ParseContext::ChildContext(size_t array_index) {
|
||||
// TODO(b/34871131): We don't support array paths right now; make path null.
|
||||
(void)array_index;
|
||||
return {accumulator_, /* path= */ nullptr, /* array_element= */ true};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string that can be appended to error messages indicating what field
|
||||
* caused the error.
|
||||
*/
|
||||
std::string ParseContext::FieldDescription() const {
|
||||
// TODO(b/34871131): Remove nullptr check once we have proper paths for fields
|
||||
// within arrays.
|
||||
if (!path_ || path_->empty()) {
|
||||
return "";
|
||||
} else {
|
||||
return util::StringFormat(" (found in field %s)", path_->CanonicalString());
|
||||
}
|
||||
}
|
||||
|
||||
bool ParseContext::write() const {
|
||||
switch (accumulator_->data_source()) {
|
||||
case UserDataSource::Set: // Falls through.
|
||||
case UserDataSource::MergeSet: // Falls through.
|
||||
case UserDataSource::Update:
|
||||
return true;
|
||||
case UserDataSource::Argument:
|
||||
case UserDataSource::ArrayArgument:
|
||||
return false;
|
||||
default:
|
||||
ThrowInvalidArgument("Unexpected case for UserDataSource: %s",
|
||||
accumulator_->data_source());
|
||||
}
|
||||
}
|
||||
|
||||
void ParseContext::ValidatePath() const {
|
||||
// TODO(b/34871131): Remove nullptr check once we have proper paths for fields
|
||||
// within arrays.
|
||||
if (!path_) {
|
||||
return;
|
||||
}
|
||||
for (const std::string& segment : *path_) {
|
||||
ValidatePathSegment(segment);
|
||||
}
|
||||
}
|
||||
|
||||
void ParseContext::ValidatePathSegment(absl::string_view segment) const {
|
||||
absl::string_view designator{RESERVED_FIELD_DESIGNATOR};
|
||||
if (segment.empty()) {
|
||||
ThrowInvalidArgument("Invalid data. Document fields must not be empty%s",
|
||||
FieldDescription());
|
||||
}
|
||||
if (write() && absl::StartsWith(segment, designator) &&
|
||||
absl::EndsWith(segment, designator)) {
|
||||
ThrowInvalidArgument(
|
||||
"Invalid data. Document fields cannot begin and end with \"%s\"%s",
|
||||
RESERVED_FIELD_DESIGNATOR, FieldDescription());
|
||||
}
|
||||
}
|
||||
|
||||
void ParseContext::AddToFieldMask(FieldPath field_path) {
|
||||
accumulator_->AddToFieldMask(std::move(field_path));
|
||||
}
|
||||
|
||||
void ParseContext::AddToFieldTransforms(
|
||||
FieldPath field_path, TransformOperation transform_operation) {
|
||||
accumulator_->AddToFieldTransforms(std::move(field_path),
|
||||
std::move(transform_operation));
|
||||
}
|
||||
|
||||
// MARK: - ParsedSetData
|
||||
|
||||
ParsedSetData::ParsedSetData(ObjectValue data,
|
||||
std::vector<FieldTransform> field_transforms)
|
||||
: data_{std::move(data)},
|
||||
field_transforms_{std::move(field_transforms)},
|
||||
patch_{false} {
|
||||
}
|
||||
|
||||
ParsedSetData::ParsedSetData(ObjectValue data,
|
||||
FieldMask field_mask,
|
||||
std::vector<FieldTransform> field_transforms)
|
||||
: data_{std::move(data)},
|
||||
field_mask_{std::move(field_mask)},
|
||||
field_transforms_{std::move(field_transforms)},
|
||||
patch_{true} {
|
||||
}
|
||||
|
||||
Mutation ParsedSetData::ToMutation(const DocumentKey& key,
|
||||
const Precondition& precondition) && {
|
||||
if (patch_) {
|
||||
return PatchMutation(key, std::move(data_), std::move(field_mask_),
|
||||
precondition, std::move(field_transforms_));
|
||||
} else {
|
||||
return SetMutation(key, std::move(data_), precondition,
|
||||
std::move(field_transforms_));
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ParsedUpdateData
|
||||
|
||||
ParsedUpdateData::ParsedUpdateData(
|
||||
ObjectValue data,
|
||||
model::FieldMask field_mask,
|
||||
std::vector<model::FieldTransform> field_transforms)
|
||||
: data_{std::move(data)},
|
||||
field_mask_{std::move(field_mask)},
|
||||
field_transforms_{std::move(field_transforms)} {
|
||||
}
|
||||
|
||||
Mutation ParsedUpdateData::ToMutation(const DocumentKey& key,
|
||||
const Precondition& precondition) && {
|
||||
return PatchMutation(key, std::move(data_), std::move(field_mask_),
|
||||
precondition, std::move(field_transforms_));
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
@@ -0,0 +1,338 @@
|
||||
/*
|
||||
* Copyright 2018 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_CORE_USER_DATA_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_USER_DATA_H_
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Firestore/core/src/model/field_mask.h"
|
||||
#include "Firestore/core/src/model/field_path.h"
|
||||
#include "Firestore/core/src/model/field_transform.h"
|
||||
#include "Firestore/core/src/model/object_value.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace model {
|
||||
|
||||
class Precondition;
|
||||
class Mutation;
|
||||
class DocumentKey;
|
||||
|
||||
} // namespace model
|
||||
|
||||
namespace core {
|
||||
|
||||
class ParseContext;
|
||||
class ParsedSetData;
|
||||
class ParsedUpdateData;
|
||||
|
||||
/**
|
||||
* Represents what type of API method provided the data being parsed; useful for
|
||||
* determining which error conditions apply during parsing and providing better
|
||||
* error messages.
|
||||
*/
|
||||
enum class UserDataSource {
|
||||
/** The data comes from a regular Set operation, without merge. */
|
||||
Set,
|
||||
/** The data comes from a Set operation with merge enabled. */
|
||||
MergeSet,
|
||||
/** The data comes from an Update operation. */
|
||||
Update,
|
||||
/**
|
||||
* Indicates the source is a where clause, cursor bound, array union element,
|
||||
* etc. In particular, this will result in ParseContext.write() returning
|
||||
* false.
|
||||
*/
|
||||
Argument,
|
||||
/**
|
||||
* Indicates that the source is an Argument that may directly contain nested
|
||||
* arrays (e.g. the operand of a `in` query).
|
||||
*/
|
||||
ArrayArgument
|
||||
};
|
||||
|
||||
/**
|
||||
* Accumulates the side-effect results of parsing user input. These include:
|
||||
*
|
||||
* * The field mask naming all the fields that have values.
|
||||
* * The transform operations that must be applied in the batch to implement
|
||||
* server-generated behavior. In the wire protocol these are encoded
|
||||
* separately from the Value.
|
||||
*/
|
||||
class ParseAccumulator {
|
||||
public:
|
||||
/**
|
||||
* @param data_source Indicates what kind of API method this data came from.
|
||||
*/
|
||||
explicit ParseAccumulator(UserDataSource data_source)
|
||||
: data_source_{data_source} {
|
||||
}
|
||||
|
||||
/**
|
||||
* What type of API method provided the data being parsed; useful for
|
||||
* determining which error conditions apply during parsing and providing
|
||||
* better error messages.
|
||||
*/
|
||||
UserDataSource data_source() const {
|
||||
return data_source_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current list of transforms.
|
||||
*/
|
||||
const std::vector<model::FieldTransform>& field_transforms() const {
|
||||
return field_transforms_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new ParseContext representing the root of a user document.
|
||||
*/
|
||||
ParseContext RootContext();
|
||||
|
||||
/**
|
||||
* Returns `true` if the given `field_path` was encountered in the current
|
||||
* document.
|
||||
*/
|
||||
bool Contains(const model::FieldPath& field_path) const;
|
||||
|
||||
/**
|
||||
* Adds the given `field_path` to the accumulated FieldMask.
|
||||
*/
|
||||
void AddToFieldMask(model::FieldPath field_path);
|
||||
|
||||
/**
|
||||
* Adds a transformation for the given field path.
|
||||
*/
|
||||
void AddToFieldTransforms(model::FieldPath field_path,
|
||||
model::TransformOperation transform_operation);
|
||||
|
||||
/**
|
||||
* Wraps the given `data` along with any accumulated field mask and transforms
|
||||
* into a ParsedSetData representing a user-issued merge.
|
||||
*
|
||||
* @return ParsedSetData that has consumed the contents of this
|
||||
* ParseAccumulator.
|
||||
*/
|
||||
ParsedSetData MergeData(model::ObjectValue data) &&;
|
||||
|
||||
/**
|
||||
* Wraps the given `data` and `user_field_mask` along with any accumulated
|
||||
* transforms that are covered by the given field mask into a ParsedSetData
|
||||
* that represents a user-issued merge.
|
||||
*
|
||||
* @param data The converted user data.
|
||||
* @param user_field_mask The user-supplied field mask that masks out any
|
||||
* changes that have been accumulated so far.
|
||||
*
|
||||
* @return ParsedSetData that has consumed the contents of this
|
||||
* ParseAccumulator. The field mask in the result will be the user_field_mask
|
||||
* and only transforms that are covered by the mask will be included.
|
||||
*/
|
||||
ParsedSetData MergeData(model::ObjectValue data,
|
||||
model::FieldMask user_field_mask) &&;
|
||||
|
||||
/**
|
||||
* Wraps the given `data` along with any accumulated transforms into a
|
||||
* ParsedSetData that represents a user-issued Set.
|
||||
*
|
||||
* @return ParsedSetData that has consumed the contents of this
|
||||
* ParseAccumulator.
|
||||
*/
|
||||
ParsedSetData SetData(model::ObjectValue data) &&;
|
||||
|
||||
/**
|
||||
* Wraps the given `data` along with any accumulated field mask and transforms
|
||||
* into a ParsedUpdateData that represents a user-issued Update.
|
||||
*
|
||||
* @return ParsedSetData that has consumed the contents of this
|
||||
* ParseAccumulator.
|
||||
*/
|
||||
ParsedUpdateData UpdateData(model::ObjectValue data) &&;
|
||||
|
||||
private:
|
||||
friend class ParseContext;
|
||||
|
||||
UserDataSource data_source_;
|
||||
|
||||
// field_mask_ and field_transforms_ are shared across all active context
|
||||
// objects to accumulate the result. All ChildContext objects append their
|
||||
// results here.
|
||||
std::set<model::FieldPath> field_mask_;
|
||||
std::vector<model::FieldTransform> field_transforms_;
|
||||
};
|
||||
|
||||
/**
|
||||
* A "context" object that wraps a ParseAccumulator and refers to a specific
|
||||
* location in a user-supplied document. Instances are created and passed around
|
||||
* while traversing user data during parsing in order to conveniently accumulate
|
||||
* data in the ParseAccumulator.
|
||||
*/
|
||||
class ParseContext {
|
||||
public:
|
||||
/**
|
||||
* Initializes a ParseContext with the given source and path.
|
||||
*
|
||||
* @param path A path within the object being parsed. This could be an empty
|
||||
* path (in which case the context represents the root of the data being
|
||||
* parsed), or a nonempty path (indicating the context represents a nested
|
||||
* location within the data).
|
||||
*
|
||||
* TODO(b/34871131): We don't support array paths right now, so path can be
|
||||
* nullptr to indicate the context represents any location within an array (in
|
||||
* which case certain features will not work and errors will be somewhat
|
||||
* compromised).
|
||||
*/
|
||||
ParseContext(ParseAccumulator* accumulator,
|
||||
std::unique_ptr<model::FieldPath> path,
|
||||
bool array_element)
|
||||
: accumulator_{accumulator},
|
||||
path_{std::move(path)},
|
||||
array_element_{array_element} {
|
||||
}
|
||||
|
||||
/** Whether or not this context corresponds to an element of an array. */
|
||||
bool array_element() const {
|
||||
return array_element_;
|
||||
}
|
||||
|
||||
/**
|
||||
* What type of API method provided the data being parsed; useful for
|
||||
* determining which error conditions apply during parsing and providing
|
||||
* better error messages.
|
||||
*/
|
||||
UserDataSource data_source() const {
|
||||
return accumulator_->data_source_;
|
||||
}
|
||||
|
||||
const model::FieldPath* path() const {
|
||||
return path_.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true for the non-query parse contexts (Set, MergeSet and Update).
|
||||
*/
|
||||
bool write() const;
|
||||
|
||||
std::string FieldDescription() const;
|
||||
|
||||
// Helpers to get a ParseContext for a child field.
|
||||
ParseContext ChildContext(const std::string& field_name);
|
||||
ParseContext ChildContext(const model::FieldPath& field_path);
|
||||
ParseContext ChildContext(size_t array_index);
|
||||
|
||||
void AddToFieldMask(model::FieldPath field_path);
|
||||
|
||||
void AddToFieldTransforms(model::FieldPath field_path,
|
||||
model::TransformOperation transform_operation);
|
||||
|
||||
private:
|
||||
void ValidatePath() const;
|
||||
void ValidatePathSegment(absl::string_view segment) const;
|
||||
|
||||
ParseAccumulator* accumulator_; // Non owning
|
||||
|
||||
/** The current path being parsed. */
|
||||
// TODO(b/34871131): path should never be nullptr, but we don't support array
|
||||
// paths right now.
|
||||
std::unique_ptr<model::FieldPath> path_;
|
||||
|
||||
bool array_element_ = false;
|
||||
};
|
||||
|
||||
/** The result of parsing document data (e.g. for a SetData call). */
|
||||
class ParsedSetData {
|
||||
public:
|
||||
ParsedSetData(model::ObjectValue data,
|
||||
std::vector<model::FieldTransform> field_transforms);
|
||||
ParsedSetData(model::ObjectValue data,
|
||||
model::FieldMask field_mask,
|
||||
std::vector<model::FieldTransform> field_transforms);
|
||||
|
||||
/**
|
||||
* Converts the parsed document data into 1 or 2 mutations (depending on
|
||||
* whether there are any field transforms) using the specified document key
|
||||
* and precondition.
|
||||
*
|
||||
* This method consumes the values stored in the ParsedSetData
|
||||
*/
|
||||
model::Mutation ToMutation(const model::DocumentKey& key,
|
||||
const model::Precondition& precondition) &&;
|
||||
|
||||
const model::ObjectValue& data() const {
|
||||
return data_;
|
||||
}
|
||||
|
||||
const model::FieldMask& fieldMask() const {
|
||||
return field_mask_;
|
||||
}
|
||||
|
||||
const std::vector<model::FieldTransform>& field_transforms() const {
|
||||
return field_transforms_;
|
||||
}
|
||||
|
||||
private:
|
||||
model::ObjectValue data_;
|
||||
model::FieldMask field_mask_;
|
||||
std::vector<model::FieldTransform> field_transforms_;
|
||||
bool patch_;
|
||||
};
|
||||
|
||||
/** The result of parsing "update" data (i.e. for an UpdateData call). */
|
||||
class ParsedUpdateData {
|
||||
public:
|
||||
ParsedUpdateData(model::ObjectValue data,
|
||||
model::FieldMask field_mask,
|
||||
std::vector<model::FieldTransform> field_transforms);
|
||||
|
||||
const model::ObjectValue& data() const {
|
||||
return data_;
|
||||
}
|
||||
|
||||
const model::FieldMask& fieldMask() const {
|
||||
return field_mask_;
|
||||
}
|
||||
|
||||
const std::vector<model::FieldTransform>& field_transforms() const {
|
||||
return field_transforms_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the parsed update data into 1 or 2 mutations (depending on whether
|
||||
* there are any field transforms) using the specified document key and
|
||||
* precondition.
|
||||
*
|
||||
* This method consumes the values stored in the ParsedUpdateData
|
||||
*/
|
||||
model::Mutation ToMutation(const model::DocumentKey& key,
|
||||
const model::Precondition& precondition) &&;
|
||||
|
||||
private:
|
||||
model::ObjectValue data_;
|
||||
// The field mask does not include document transforms.
|
||||
model::FieldMask field_mask_;
|
||||
std::vector<model::FieldTransform> field_transforms_;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_USER_DATA_H_
|
||||
+399
@@ -0,0 +1,399 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/core/view.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "Firestore/core/src/core/target.h"
|
||||
#include "Firestore/core/src/model/document_set.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
using model::Document;
|
||||
using model::DocumentKey;
|
||||
using model::DocumentKeySet;
|
||||
using model::DocumentMap;
|
||||
using model::DocumentSet;
|
||||
using model::OnlineState;
|
||||
using remote::TargetChange;
|
||||
using util::ComparisonResult;
|
||||
|
||||
// MARK: - LimboDocumentChange
|
||||
|
||||
LimboDocumentChange::LimboDocumentChange(
|
||||
firebase::firestore::core::LimboDocumentChange::Type type,
|
||||
firebase::firestore::model::DocumentKey key)
|
||||
: type_(type), key_(std::move(key)) {
|
||||
}
|
||||
|
||||
bool operator==(const LimboDocumentChange& lhs,
|
||||
const LimboDocumentChange& rhs) {
|
||||
return lhs.type() == rhs.type() && lhs.key() == rhs.key();
|
||||
}
|
||||
|
||||
// MARK: - ViewDocumentChanges
|
||||
|
||||
ViewDocumentChanges::ViewDocumentChanges(model::DocumentSet new_documents,
|
||||
DocumentViewChangeSet changes,
|
||||
model::DocumentKeySet mutated_keys,
|
||||
bool needs_refill)
|
||||
: document_set_(std::move(new_documents)),
|
||||
change_set_(std::move(changes)),
|
||||
mutated_keys_(std::move(mutated_keys)),
|
||||
needs_refill_(needs_refill) {
|
||||
}
|
||||
|
||||
// MARK: - View
|
||||
|
||||
namespace {
|
||||
|
||||
int GetDocumentViewChangeTypePosition(DocumentViewChange::Type change_type) {
|
||||
switch (change_type) {
|
||||
case DocumentViewChange::Type::Removed:
|
||||
return 0;
|
||||
case DocumentViewChange::Type::Added:
|
||||
return 1;
|
||||
case DocumentViewChange::Type::Modified:
|
||||
return 2;
|
||||
case DocumentViewChange::Type::Metadata:
|
||||
// A metadata change is converted to a modified change at the public API
|
||||
// layer. Since we sort by document key and then change type, metadata and
|
||||
// modified changes must be sorted equivalently.
|
||||
return 2;
|
||||
}
|
||||
HARD_FAIL("Unknown DocumentViewChange::Type %s", change_type);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
View::View(Query query, DocumentKeySet remote_documents)
|
||||
: query_(std::move(query)),
|
||||
document_set_(query_.Comparator()),
|
||||
synced_documents_(std::move(remote_documents)) {
|
||||
}
|
||||
|
||||
ComparisonResult View::Compare(const Document& lhs, const Document& rhs) const {
|
||||
return document_set_.comparator().Compare(lhs, rhs);
|
||||
}
|
||||
|
||||
ViewDocumentChanges View::ComputeDocumentChanges(
|
||||
const DocumentMap& doc_changes,
|
||||
const absl::optional<ViewDocumentChanges>& previous_changes) const {
|
||||
DocumentViewChangeSet change_set;
|
||||
if (previous_changes) {
|
||||
change_set = previous_changes->change_set();
|
||||
}
|
||||
DocumentSet old_document_set =
|
||||
previous_changes ? previous_changes->document_set() : document_set_;
|
||||
|
||||
DocumentKeySet new_mutated_keys =
|
||||
previous_changes ? previous_changes->mutated_keys() : mutated_keys_;
|
||||
DocumentKeySet old_mutated_keys = mutated_keys_;
|
||||
DocumentSet new_document_set = old_document_set;
|
||||
bool needs_refill = false;
|
||||
|
||||
// Track the last doc in a (full) limit. This is necessary, because some
|
||||
// update (a delete, or an update moving a doc past the old limit) might mean
|
||||
// there is some other document in the local cache that either should come (1)
|
||||
// between the old last limit doc and the new last document, in the case of
|
||||
// updates, or (2) after the new last document, in the case of deletes. So we
|
||||
// keep this doc at the old limit to compare the updates to.
|
||||
//
|
||||
// Note that this should never get used in a refill (when previous_changes is
|
||||
// set), because there will only be adds -- no deletes or updates.
|
||||
absl::optional<Document> last_doc_in_limit;
|
||||
if (query_.has_limit_to_first() &&
|
||||
old_document_set.size() == static_cast<size_t>(query_.limit())) {
|
||||
last_doc_in_limit = old_document_set.GetLastDocument();
|
||||
}
|
||||
absl::optional<Document> first_doc_in_limit;
|
||||
if (query_.has_limit_to_last() &&
|
||||
old_document_set.size() == static_cast<size_t>(query_.limit())) {
|
||||
first_doc_in_limit = old_document_set.GetFirstDocument();
|
||||
}
|
||||
|
||||
for (const auto& kv : doc_changes) {
|
||||
const DocumentKey& key = kv.first;
|
||||
|
||||
absl::optional<Document> old_doc = old_document_set.GetDocument(key);
|
||||
absl::optional<Document> new_doc = query_.Matches(kv.second)
|
||||
? absl::optional<Document>{kv.second}
|
||||
: absl::nullopt;
|
||||
|
||||
bool old_doc_had_pending_mutations =
|
||||
old_doc && old_mutated_keys.contains(key);
|
||||
|
||||
// We only consider committed mutations for documents that were mutated
|
||||
// during the lifetime of the view.
|
||||
bool new_doc_has_pending_mutations =
|
||||
new_doc && ((*new_doc)->has_local_mutations() ||
|
||||
(old_mutated_keys.contains(key) &&
|
||||
(*new_doc)->has_committed_mutations()));
|
||||
|
||||
bool change_applied = false;
|
||||
// Calculate change
|
||||
if (old_doc && new_doc) {
|
||||
bool docs_equal = (*old_doc)->value() == (*new_doc)->value();
|
||||
if (!docs_equal) {
|
||||
if (!ShouldWaitForSyncedDocument(*new_doc, *old_doc)) {
|
||||
change_set.AddChange(
|
||||
DocumentViewChange{*new_doc, DocumentViewChange::Type::Modified});
|
||||
change_applied = true;
|
||||
|
||||
bool outside_limit =
|
||||
last_doc_in_limit &&
|
||||
util::Descending(Compare(*new_doc, *last_doc_in_limit));
|
||||
bool outside_limit_to_last =
|
||||
first_doc_in_limit &&
|
||||
util::Ascending(Compare(*new_doc, *first_doc_in_limit));
|
||||
if (outside_limit || outside_limit_to_last) {
|
||||
// This doc moved from inside the limit to after the limit. That
|
||||
// means there may be some doc in the local cache that's actually
|
||||
// less than this one.
|
||||
needs_refill = true;
|
||||
}
|
||||
}
|
||||
} else if (old_doc_had_pending_mutations !=
|
||||
new_doc_has_pending_mutations) {
|
||||
change_set.AddChange(
|
||||
DocumentViewChange{*new_doc, DocumentViewChange::Type::Metadata});
|
||||
change_applied = true;
|
||||
}
|
||||
|
||||
} else if (!old_doc && new_doc) {
|
||||
change_set.AddChange(
|
||||
DocumentViewChange{*new_doc, DocumentViewChange::Type::Added});
|
||||
change_applied = true;
|
||||
} else if (old_doc && !new_doc) {
|
||||
change_set.AddChange(
|
||||
DocumentViewChange{*old_doc, DocumentViewChange::Type::Removed});
|
||||
change_applied = true;
|
||||
|
||||
if (last_doc_in_limit || first_doc_in_limit) {
|
||||
// A doc was removed from a full limit query. We'll need to re-query
|
||||
// from the local cache to see if we know about some other doc that
|
||||
// should be in the results.
|
||||
needs_refill = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (change_applied) {
|
||||
if (new_doc) {
|
||||
new_document_set = new_document_set.insert(new_doc);
|
||||
if ((*new_doc)->has_local_mutations()) {
|
||||
new_mutated_keys = new_mutated_keys.insert(key);
|
||||
} else {
|
||||
new_mutated_keys = new_mutated_keys.erase(key);
|
||||
}
|
||||
} else {
|
||||
new_document_set = new_document_set.erase(key);
|
||||
new_mutated_keys = new_mutated_keys.erase(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Drop documents out to meet limitToFirst/limitToLast requirement.
|
||||
if (query_.limit_type() != LimitType::None) {
|
||||
auto limit = static_cast<size_t>(query_.limit());
|
||||
if (limit < new_document_set.size()) {
|
||||
for (size_t i = new_document_set.size() - limit; i > 0; --i) {
|
||||
absl::optional<Document> found =
|
||||
query_.has_limit_to_first() ? new_document_set.GetLastDocument()
|
||||
: new_document_set.GetFirstDocument();
|
||||
const Document& old_doc = *found;
|
||||
new_document_set = new_document_set.erase(old_doc->key());
|
||||
new_mutated_keys = new_mutated_keys.erase(old_doc->key());
|
||||
change_set.AddChange(
|
||||
DocumentViewChange{old_doc, DocumentViewChange::Type::Removed});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HARD_ASSERT(!needs_refill || !previous_changes,
|
||||
"View was refilled using docs that themselves needed refilling.");
|
||||
|
||||
return ViewDocumentChanges(std::move(new_document_set), std::move(change_set),
|
||||
new_mutated_keys, needs_refill);
|
||||
}
|
||||
|
||||
bool View::ShouldWaitForSyncedDocument(const Document& new_doc,
|
||||
const Document& old_doc) const {
|
||||
// We suppress the initial change event for documents that were modified as
|
||||
// part of a write acknowledgment (e.g. when the value of a server transform
|
||||
// is applied) as Watch will send us the same document again. By suppressing
|
||||
// the event, we only raise two user visible events (one with
|
||||
// `has_pending_writes` and the final state of the document) instead of three
|
||||
// (one with `has_pending_writes`, the modified document with
|
||||
// `has_pending_writes` and the final state of the document).
|
||||
return (old_doc->has_local_mutations() &&
|
||||
new_doc->has_committed_mutations() &&
|
||||
!new_doc->has_local_mutations());
|
||||
}
|
||||
|
||||
ViewChange View::ApplyChanges(const ViewDocumentChanges& doc_changes) {
|
||||
return ApplyChanges(doc_changes, {});
|
||||
}
|
||||
|
||||
ViewChange View::ApplyChanges(
|
||||
const ViewDocumentChanges& doc_changes,
|
||||
const absl::optional<TargetChange>& target_change) {
|
||||
HARD_ASSERT(!doc_changes.needs_refill(),
|
||||
"Cannot apply changes that need a refill");
|
||||
|
||||
DocumentSet old_documents = document_set_;
|
||||
document_set_ = doc_changes.document_set();
|
||||
mutated_keys_ = doc_changes.mutated_keys();
|
||||
|
||||
// Sort changes based on type and query comparator.
|
||||
std::vector<DocumentViewChange> changes =
|
||||
doc_changes.change_set().GetChanges();
|
||||
std::sort(
|
||||
changes.begin(), changes.end(),
|
||||
[this](const DocumentViewChange& lhs, const DocumentViewChange& rhs) {
|
||||
int pos1 = GetDocumentViewChangeTypePosition(lhs.type());
|
||||
int pos2 = GetDocumentViewChangeTypePosition(rhs.type());
|
||||
if (pos1 != pos2) {
|
||||
return pos1 < pos2;
|
||||
}
|
||||
return util::Ascending(Compare(lhs.document(), rhs.document()));
|
||||
});
|
||||
|
||||
ApplyTargetChange(target_change);
|
||||
std::vector<LimboDocumentChange> limbo_changes = UpdateLimboDocuments();
|
||||
bool synced = limbo_documents_.empty() && current_;
|
||||
SyncState new_sync_state = synced ? SyncState::Synced : SyncState::Local;
|
||||
bool sync_state_changed = new_sync_state != sync_state_;
|
||||
sync_state_ = new_sync_state;
|
||||
|
||||
if (changes.empty() && !sync_state_changed) {
|
||||
// No changes.
|
||||
return ViewChange(absl::nullopt, std::move(limbo_changes));
|
||||
} else {
|
||||
ViewSnapshot snapshot{query_,
|
||||
doc_changes.document_set(),
|
||||
old_documents,
|
||||
std::move(changes),
|
||||
doc_changes.mutated_keys(),
|
||||
/*from_cache=*/new_sync_state == SyncState::Local,
|
||||
sync_state_changed,
|
||||
/*excludes_metadata_changes=*/false};
|
||||
|
||||
return ViewChange(std::move(snapshot), std::move(limbo_changes));
|
||||
}
|
||||
}
|
||||
|
||||
ViewChange View::ApplyOnlineStateChange(OnlineState online_state) {
|
||||
if (current_ && online_state == OnlineState::Offline) {
|
||||
// If we're offline, set `current_` to false and then call ApplyChanges to
|
||||
// refresh our sync state and generate a ViewChange as appropriate. We are
|
||||
// guaranteed to get a new `TargetChange` that sets `current_` back to true
|
||||
// once the client is back online.
|
||||
current_ = false;
|
||||
return ApplyChanges(
|
||||
ViewDocumentChanges(document_set_, DocumentViewChangeSet{},
|
||||
mutated_keys_, /* needs_refill= */ false));
|
||||
} else {
|
||||
// No effect, just return a no-op ViewChange.
|
||||
return ViewChange(absl::nullopt, {});
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Private Methods
|
||||
|
||||
/** Returns whether the doc for the given key should be in limbo. */
|
||||
bool View::ShouldBeInLimbo(const DocumentKey& key) const {
|
||||
// If the remote end says it's part of this query, it's not in limbo.
|
||||
if (synced_documents_.contains(key)) {
|
||||
return false;
|
||||
}
|
||||
// The local store doesn't think it's a result, so it shouldn't be in limbo.
|
||||
if (!document_set_.ContainsKey(key)) {
|
||||
return false;
|
||||
}
|
||||
// If there are local changes to the doc, they might explain why the server
|
||||
// doesn't know that it's part of the query. So don't put it in limbo.
|
||||
// TODO(klimt): Ideally, we would only consider changes that might actually
|
||||
// affect this specific query.
|
||||
if ((*document_set_.GetDocument(key))->has_local_mutations()) {
|
||||
return false;
|
||||
}
|
||||
// Everything else is in limbo.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates synced_documents_ and current based on the given change.
|
||||
*/
|
||||
void View::ApplyTargetChange(
|
||||
const absl::optional<TargetChange>& maybe_target_change) {
|
||||
if (maybe_target_change.has_value()) {
|
||||
const TargetChange& target_change = maybe_target_change.value();
|
||||
|
||||
for (const DocumentKey& key : target_change.added_documents()) {
|
||||
synced_documents_ = synced_documents_.insert(key);
|
||||
}
|
||||
for (const DocumentKey& key : target_change.modified_documents()) {
|
||||
HARD_ASSERT(synced_documents_.find(key) != synced_documents_.end(),
|
||||
"Modified document %s not found in view.", key.ToString());
|
||||
}
|
||||
for (const DocumentKey& key : target_change.removed_documents()) {
|
||||
synced_documents_ = synced_documents_.erase(key);
|
||||
}
|
||||
|
||||
current_ = target_change.current();
|
||||
}
|
||||
}
|
||||
|
||||
/** Updates limbo_documents_ and returns any changes as LimboDocumentChanges. */
|
||||
std::vector<LimboDocumentChange> View::UpdateLimboDocuments() {
|
||||
// We can only determine limbo documents when we're in-sync with the server.
|
||||
if (!current_) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// TODO(klimt): Do this incrementally so that it's not quadratic when updating
|
||||
// many documents.
|
||||
DocumentKeySet old_limbo_documents = std::move(limbo_documents_);
|
||||
limbo_documents_ = DocumentKeySet{};
|
||||
for (const Document& doc : document_set_) {
|
||||
if (ShouldBeInLimbo(doc->key())) {
|
||||
limbo_documents_ = limbo_documents_.insert(doc->key());
|
||||
}
|
||||
}
|
||||
|
||||
// Diff the new limbo docs with the old limbo docs.
|
||||
std::vector<LimboDocumentChange> changes;
|
||||
changes.reserve(old_limbo_documents.size() + limbo_documents_.size());
|
||||
|
||||
for (const DocumentKey& key : old_limbo_documents) {
|
||||
if (!limbo_documents_.contains(key)) {
|
||||
changes.push_back(LimboDocumentChange::Removed(key));
|
||||
}
|
||||
}
|
||||
for (const DocumentKey& key : limbo_documents_) {
|
||||
if (!old_limbo_documents.contains(key)) {
|
||||
changes.push_back(LimboDocumentChange::Added(key));
|
||||
}
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
+237
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRESTORE_CORE_SRC_CORE_VIEW_H_
|
||||
#define FIRESTORE_CORE_SRC_CORE_VIEW_H_
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Firestore/core/src/core/view_snapshot.h"
|
||||
#include "Firestore/core/src/model/document_key_set.h"
|
||||
#include "Firestore/core/src/model/document_set.h"
|
||||
#include "Firestore/core/src/model/types.h"
|
||||
#include "Firestore/core/src/remote/remote_event.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
/** A change to a particular document wrt whether it is in "limbo". */
|
||||
class LimboDocumentChange {
|
||||
public:
|
||||
enum class Type {
|
||||
Added,
|
||||
Removed,
|
||||
};
|
||||
|
||||
static LimboDocumentChange Added(model::DocumentKey key) {
|
||||
return {Type::Added, std::move(key)};
|
||||
}
|
||||
|
||||
static LimboDocumentChange Removed(model::DocumentKey key) {
|
||||
return {Type::Removed, std::move(key)};
|
||||
}
|
||||
|
||||
LimboDocumentChange(Type type, model::DocumentKey key);
|
||||
|
||||
Type type() const {
|
||||
return type_;
|
||||
}
|
||||
|
||||
const model::DocumentKey& key() const {
|
||||
return key_;
|
||||
}
|
||||
|
||||
friend bool operator==(const LimboDocumentChange& lhs,
|
||||
const LimboDocumentChange& rhs);
|
||||
|
||||
private:
|
||||
Type type_;
|
||||
model::DocumentKey key_;
|
||||
};
|
||||
|
||||
/** The result of applying a set of doc changes to a view. */
|
||||
class ViewDocumentChanges {
|
||||
public:
|
||||
ViewDocumentChanges(model::DocumentSet new_documents,
|
||||
DocumentViewChangeSet changes,
|
||||
model::DocumentKeySet mutated_keys,
|
||||
bool needs_refill);
|
||||
|
||||
/** The new set of docs that should be in the view. */
|
||||
const model::DocumentSet& document_set() const {
|
||||
return document_set_;
|
||||
}
|
||||
|
||||
/** The diff of these docs with the previous set of docs. */
|
||||
const core::DocumentViewChangeSet& change_set() const {
|
||||
return change_set_;
|
||||
}
|
||||
|
||||
const model::DocumentKeySet& mutated_keys() const {
|
||||
return mutated_keys_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the set of documents passed in was not sufficient to calculate the
|
||||
* new state of the view and there needs to be another pass based on the local
|
||||
* cache.
|
||||
*/
|
||||
bool needs_refill() const {
|
||||
return needs_refill_;
|
||||
}
|
||||
|
||||
private:
|
||||
model::DocumentSet document_set_;
|
||||
core::DocumentViewChangeSet change_set_;
|
||||
model::DocumentKeySet mutated_keys_;
|
||||
bool needs_refill_ = false;
|
||||
};
|
||||
|
||||
/** A set of changes to a view. */
|
||||
class ViewChange {
|
||||
public:
|
||||
ViewChange(absl::optional<ViewSnapshot> snapshot,
|
||||
std::vector<LimboDocumentChange> limbo_changes)
|
||||
: snapshot_(std::move(snapshot)),
|
||||
limbo_changes_(std::move(limbo_changes)) {
|
||||
}
|
||||
|
||||
const absl::optional<ViewSnapshot> snapshot() const& {
|
||||
return snapshot_;
|
||||
}
|
||||
|
||||
absl::optional<ViewSnapshot>&& snapshot() && {
|
||||
return std::move(snapshot_);
|
||||
}
|
||||
|
||||
const std::vector<LimboDocumentChange> limbo_changes() const {
|
||||
return limbo_changes_;
|
||||
}
|
||||
|
||||
private:
|
||||
absl::optional<ViewSnapshot> snapshot_;
|
||||
std::vector<LimboDocumentChange> limbo_changes_;
|
||||
};
|
||||
|
||||
/**
|
||||
* View is responsible for computing the final merged truth of what docs are in
|
||||
* a query. It gets notified of local and remote changes to docs, and applies
|
||||
* the query filters and limits to determine the most correct possible results.
|
||||
*/
|
||||
class View {
|
||||
public:
|
||||
View(Query query, model::DocumentKeySet remote_documents);
|
||||
|
||||
/**
|
||||
* The set of remote documents that the server has told us belongs to the
|
||||
* target associated with this view.
|
||||
*/
|
||||
const model::DocumentKeySet& synced_documents() const {
|
||||
return synced_documents_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over a set of doc changes, applies the query limit, and computes
|
||||
* what the new results should be, what the changes were, and whether we may
|
||||
* need to go back to the local cache for more results. Does not make any
|
||||
* changes to the view.
|
||||
*
|
||||
* @param doc_changes The doc changes to apply to this view.
|
||||
* @param previous_changes If this is being called with a refill, then start
|
||||
* with this set of docs and changes instead of the current view.
|
||||
* @return a new set of docs, changes, and refill flag.
|
||||
*/
|
||||
core::ViewDocumentChanges ComputeDocumentChanges(
|
||||
const model::DocumentMap& doc_changes,
|
||||
const absl::optional<core::ViewDocumentChanges>& previous_changes =
|
||||
absl::nullopt) const;
|
||||
|
||||
/**
|
||||
* Updates the view with the given ViewDocumentChanges.
|
||||
*
|
||||
* @param doc_changes The set of changes to make to the view's docs.
|
||||
* @return A new ViewChange with the given docs, changes, and sync state.
|
||||
*/
|
||||
ViewChange ApplyChanges(const core::ViewDocumentChanges& doc_changes);
|
||||
|
||||
/**
|
||||
* Updates the view with the given ViewDocumentChanges and updates limbo docs
|
||||
* and sync state from the given (optional) target change.
|
||||
*
|
||||
* @param doc_changes The set of changes to make to the view's docs.
|
||||
* @param target_change A target change to apply for computing limbo docs and
|
||||
* sync state.
|
||||
* @return A new ViewChange with the given docs, changes, and sync state.
|
||||
*/
|
||||
ViewChange ApplyChanges(
|
||||
const core::ViewDocumentChanges& doc_changes,
|
||||
const absl::optional<remote::TargetChange>& target_change);
|
||||
|
||||
/**
|
||||
* Applies an OnlineState change to the view, potentially generating an
|
||||
* ViewChange if the view's sync_state_ changes as a result.
|
||||
*/
|
||||
core::ViewChange ApplyOnlineStateChange(model::OnlineState online_state);
|
||||
|
||||
core::SyncState sync_state() const {
|
||||
return sync_state_;
|
||||
}
|
||||
|
||||
private:
|
||||
util::ComparisonResult Compare(const model::Document& lhs,
|
||||
const model::Document& rhs) const;
|
||||
|
||||
bool ShouldBeInLimbo(const model::DocumentKey& key) const;
|
||||
|
||||
bool ShouldWaitForSyncedDocument(const model::Document& new_doc,
|
||||
const model::Document& old_doc) const;
|
||||
|
||||
void ApplyTargetChange(
|
||||
const absl::optional<remote::TargetChange>& maybe_target_change);
|
||||
|
||||
std::vector<LimboDocumentChange> UpdateLimboDocuments();
|
||||
|
||||
Query query_;
|
||||
|
||||
model::DocumentSet document_set_;
|
||||
|
||||
/** Documents included in the remote target. */
|
||||
model::DocumentKeySet synced_documents_;
|
||||
|
||||
/** Documents in the view but not in the remote target */
|
||||
model::DocumentKeySet limbo_documents_;
|
||||
|
||||
/** Document Keys that have local changes. */
|
||||
model::DocumentKeySet mutated_keys_;
|
||||
|
||||
SyncState sync_state_ = SyncState::None;
|
||||
|
||||
/**
|
||||
* A flag whether the view is current with the backend. A view is considered
|
||||
* current after it has seen the current flag from the backend and did not
|
||||
* lose consistency within the watch stream (e.g. because of an existence
|
||||
* filter mismatch).
|
||||
*/
|
||||
bool current_ = false;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
|
||||
#endif // FIRESTORE_CORE_SRC_CORE_VIEW_H_
|
||||
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Firestore/core/src/core/view_snapshot.h"
|
||||
|
||||
#include <ostream>
|
||||
|
||||
#include "Firestore/core/src/model/document_set.h"
|
||||
#include "Firestore/core/src/util/hashing.h"
|
||||
#include "Firestore/core/src/util/string_format.h"
|
||||
#include "Firestore/core/src/util/to_string.h"
|
||||
|
||||
namespace firebase {
|
||||
namespace firestore {
|
||||
namespace core {
|
||||
|
||||
using model::Document;
|
||||
using model::DocumentKey;
|
||||
using model::DocumentKeySet;
|
||||
using model::DocumentSet;
|
||||
using util::StringFormat;
|
||||
|
||||
// DocumentViewChange
|
||||
|
||||
DocumentViewChange::DocumentViewChange(Document document, Type type)
|
||||
: document_{std::move(document)}, type_{type} {
|
||||
}
|
||||
|
||||
const Document& DocumentViewChange::document() const {
|
||||
return document_;
|
||||
}
|
||||
|
||||
std::string DocumentViewChange::ToString() const {
|
||||
return StringFormat("<DocumentViewChange doc:%s type:%s>",
|
||||
util::ToString(document()), type());
|
||||
}
|
||||
|
||||
size_t DocumentViewChange::Hash() const {
|
||||
return util::Hash(document(), type());
|
||||
}
|
||||
|
||||
bool operator==(const DocumentViewChange& lhs, const DocumentViewChange& rhs) {
|
||||
return lhs.document() == rhs.document() && lhs.type() == rhs.type();
|
||||
}
|
||||
|
||||
// DocumentViewChangeSet
|
||||
|
||||
void DocumentViewChangeSet::AddChange(DocumentViewChange&& change) {
|
||||
const DocumentKey& key = change.document()->key();
|
||||
auto old_change_iter = change_map_.find(key);
|
||||
if (old_change_iter == change_map_.end()) {
|
||||
change_map_ = change_map_.insert(key, change);
|
||||
return;
|
||||
}
|
||||
|
||||
const DocumentViewChange& old = old_change_iter->second;
|
||||
DocumentViewChange::Type old_type = old.type();
|
||||
DocumentViewChange::Type new_type = change.type();
|
||||
|
||||
// Merge the new change with the existing change.
|
||||
if (new_type != DocumentViewChange::Type::Added &&
|
||||
old_type == DocumentViewChange::Type::Metadata) {
|
||||
change_map_ = change_map_.insert(key, change);
|
||||
|
||||
} else if (new_type == DocumentViewChange::Type::Metadata &&
|
||||
old_type != DocumentViewChange::Type::Removed) {
|
||||
DocumentViewChange new_change{change.document(), old_type};
|
||||
change_map_ = change_map_.insert(key, new_change);
|
||||
|
||||
} else if (new_type == DocumentViewChange::Type::Modified &&
|
||||
old_type == DocumentViewChange::Type::Modified) {
|
||||
DocumentViewChange new_change{change.document(),
|
||||
DocumentViewChange::Type::Modified};
|
||||
change_map_ = change_map_.insert(key, new_change);
|
||||
|
||||
} else if (new_type == DocumentViewChange::Type::Modified &&
|
||||
old_type == DocumentViewChange::Type::Added) {
|
||||
DocumentViewChange new_change{change.document(),
|
||||
DocumentViewChange::Type::Added};
|
||||
change_map_ = change_map_.insert(key, new_change);
|
||||
|
||||
} else if (new_type == DocumentViewChange::Type::Removed &&
|
||||
old_type == DocumentViewChange::Type::Added) {
|
||||
change_map_ = change_map_.erase(key);
|
||||
|
||||
} else if (new_type == DocumentViewChange::Type::Removed &&
|
||||
old_type == DocumentViewChange::Type::Modified) {
|
||||
DocumentViewChange new_change{old.document(),
|
||||
DocumentViewChange::Type::Removed};
|
||||
change_map_ = change_map_.insert(key, new_change);
|
||||
|
||||
} else if (new_type == DocumentViewChange::Type::Added &&
|
||||
old_type == DocumentViewChange::Type::Removed) {
|
||||
DocumentViewChange new_change{change.document(),
|
||||
DocumentViewChange::Type::Modified};
|
||||
change_map_ = change_map_.insert(key, new_change);
|
||||
|
||||
} else {
|
||||
// This includes these cases, which don't make sense:
|
||||
// Added -> Added
|
||||
// Removed -> Removed
|
||||
// Modified -> Added
|
||||
// Removed -> Modified
|
||||
// Metadata -> Added
|
||||
// Removed -> Metadata
|
||||
HARD_FAIL("Unsupported combination of changes: %s after %s", new_type,
|
||||
old_type);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<DocumentViewChange> DocumentViewChangeSet::GetChanges() const {
|
||||
std::vector<DocumentViewChange> changes;
|
||||
for (const auto& kv : change_map_) {
|
||||
const DocumentViewChange& change = kv.second;
|
||||
changes.push_back(change);
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
|
||||
std::string DocumentViewChangeSet::ToString() const {
|
||||
return util::ToString(change_map_);
|
||||
}
|
||||
|
||||
// ViewSnapshot
|
||||
|
||||
ViewSnapshot::ViewSnapshot(Query query,
|
||||
DocumentSet documents,
|
||||
DocumentSet old_documents,
|
||||
std::vector<DocumentViewChange> document_changes,
|
||||
model::DocumentKeySet mutated_keys,
|
||||
bool from_cache,
|
||||
bool sync_state_changed,
|
||||
bool excludes_metadata_changes)
|
||||
: query_{std::move(query)},
|
||||
documents_{std::move(documents)},
|
||||
old_documents_{std::move(old_documents)},
|
||||
document_changes_{std::move(document_changes)},
|
||||
mutated_keys_{std::move(mutated_keys)},
|
||||
from_cache_{from_cache},
|
||||
sync_state_changed_{sync_state_changed},
|
||||
excludes_metadata_changes_{excludes_metadata_changes} {
|
||||
}
|
||||
|
||||
ViewSnapshot ViewSnapshot::FromInitialDocuments(
|
||||
Query query,
|
||||
DocumentSet documents,
|
||||
DocumentKeySet mutated_keys,
|
||||
bool from_cache,
|
||||
bool excludes_metadata_changes) {
|
||||
std::vector<DocumentViewChange> view_changes;
|
||||
for (const Document& doc : documents) {
|
||||
view_changes.emplace_back(doc, DocumentViewChange::Type::Added);
|
||||
}
|
||||
|
||||
DocumentSet old_documents(query.Comparator());
|
||||
return ViewSnapshot{std::move(query),
|
||||
documents,
|
||||
old_documents,
|
||||
std::move(view_changes),
|
||||
std::move(mutated_keys),
|
||||
from_cache,
|
||||
/*sync_state_changed=*/true,
|
||||
excludes_metadata_changes};
|
||||
}
|
||||
|
||||
const Query& ViewSnapshot::query() const {
|
||||
return query_;
|
||||
}
|
||||
|
||||
std::string ViewSnapshot::ToString() const {
|
||||
return StringFormat(
|
||||
"<ViewSnapshot query: %s documents: %s old_documents: %s changes: %s "
|
||||
"from_cache: %s mutated_keys: %s sync_state_changed: %s "
|
||||
"excludes_metadata_changes: %s>",
|
||||
query_.ToString(), documents_.ToString(), old_documents_.ToString(),
|
||||
util::ToString(document_changes()), from_cache(), mutated_keys().size(),
|
||||
sync_state_changed(), excludes_metadata_changes());
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const ViewSnapshot& value) {
|
||||
return out << value.ToString();
|
||||
}
|
||||
|
||||
size_t ViewSnapshot::Hash() const {
|
||||
// Note: We are omitting `mutated_keys_` from the hash, since we don't have a
|
||||
// straightforward way to compute its hash value. Since `ViewSnapshot` is
|
||||
// currently not stored in any dictionaries, this has no side effects.
|
||||
|
||||
return util::Hash(query(), documents(), old_documents(), document_changes(),
|
||||
from_cache(), sync_state_changed(),
|
||||
excludes_metadata_changes());
|
||||
}
|
||||
|
||||
bool operator==(const ViewSnapshot& lhs, const ViewSnapshot& rhs) {
|
||||
return lhs.query() == rhs.query() && lhs.documents() == rhs.documents() &&
|
||||
lhs.old_documents() == rhs.old_documents() &&
|
||||
lhs.document_changes() == rhs.document_changes() &&
|
||||
lhs.from_cache() == rhs.from_cache() &&
|
||||
lhs.mutated_keys() == rhs.mutated_keys() &&
|
||||
lhs.sync_state_changed() == rhs.sync_state_changed() &&
|
||||
lhs.excludes_metadata_changes() == rhs.excludes_metadata_changes();
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace firestore
|
||||
} // namespace firebase
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user