adding pods method of package managing

This commit is contained in:
talksik
2021-12-13 12:34:20 -08:00
parent dad674aca7
commit 705203d7bd
5871 changed files with 1259393 additions and 3 deletions
@@ -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_
@@ -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_
@@ -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_
@@ -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_
+62
View File
@@ -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_
+260
View File
@@ -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
+145
View File
@@ -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
+212
View File
@@ -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_
@@ -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
+92
View File
@@ -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_
@@ -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
@@ -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_
+39
View File
@@ -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_
@@ -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
View File
@@ -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
View File
@@ -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_
+37
View File
@@ -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
View File
@@ -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
+107
View File
@@ -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
View File
@@ -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
View File
@@ -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 SyncEngines 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_
+105
View File
@@ -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
View File
@@ -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
+338
View File
@@ -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
View File
@@ -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
View File
@@ -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