internal static InternalFilter Create(FieldPath fieldPath, FieldOp op, object value) { GaxPreconditions.CheckNotNull(fieldPath, nameof(fieldPath)); var unaryOperator = GetUnaryOperator(value); if (unaryOperator != UnaryOp.Unspecified) { if (op == FieldOp.Equal) { return(new InternalFilter(fieldPath, (int)unaryOperator, null)); } else { throw new ArgumentException(nameof(value), "null and NaN values can only be used with the Equal operator"); } } else { var convertedValue = ValueSerializer.Serialize(value); if (SentinelValue.GetKind(convertedValue) != SentinelValue.SentinelKind.None) { throw new ArgumentException(nameof(value), "Sentinel values cannot be specified in filters"); } return(new InternalFilter(fieldPath, (int)op, convertedValue)); } }
internal static InternalFilter Create(FieldPath fieldPath, QueryOperator op, object value) { GaxPreconditions.CheckNotNull(fieldPath, nameof(fieldPath)); FieldOp queryOp = GetOperator(op); var unaryOperator = GetUnaryOperator(value); if (unaryOperator != UnaryOp.Unspecified) { if (queryOp == FieldOp.Equal) { return(new InternalFilter(fieldPath, (int)unaryOperator, null)); } else { throw new ArgumentException(nameof(value), "null and NaN values can only be used with the Equal operator"); } } else { var convertedValue = ValueSerializer.Serialize(value); if (convertedValue.IsDeleteSentinel() || convertedValue.IsServerTimestampSentinel()) { throw new ArgumentException(nameof(value), "Sentinel values cannot be specified in filters"); } return(new InternalFilter(fieldPath, (int)queryOp, convertedValue)); } }
/// <summary> /// Add a filter for the given field path. /// </summary> /// <remarks> /// This call adds additional filters to any previously-specified ones. /// </remarks> /// <param name="fieldPath">The field path to filter on. Must not be null.</param> /// <param name="op">The filter operator.</param> /// <param name="value">The value to compare in the filter.</param> /// <returns>A new query based on the current one, but with the additional specified filter applied.</returns> public Query Where(FieldPath fieldPath, QueryOperator op, object value) { FieldOp queryOp = GetOperator(op); var unaryOperator = GetUnaryOperator(value); Filter filter; if (unaryOperator != UnaryFilter.Types.Operator.Unspecified) { if (queryOp == FieldOp.Equal) { filter = new Filter { UnaryFilter = new UnaryFilter { Field = fieldPath.ToFieldReference(), Op = unaryOperator } }; } else { throw new ArgumentException(nameof(value), "null and NaN values can only be used with the Equal operator"); } } else { var convertedValue = ValueSerializer.Serialize(value); filter = new Filter { FieldFilter = new FieldFilter { Field = fieldPath.ToFieldReference(), Op = queryOp, Value = convertedValue } }; } return(WithFilter(filter)); }
private Cursor CreateCursor(object[] fieldValues, bool before) { GaxPreconditions.CheckNotNull(fieldValues, nameof(fieldValues)); GaxPreconditions.CheckArgument(fieldValues.Length != 0, nameof(fieldValues), "Cannot specify an empty set of values for a start/end query cursor."); GaxPreconditions.CheckArgument( fieldValues.Length <= _orderings.Count, nameof(fieldValues), "Too many cursor values specified. The specified values must match the ordering constraints of the query. {0} specified for a query with {1} ordering constraints.", fieldValues.Length, _orderings.Count); var cursor = new Cursor { Before = before }; for (int i = 0; i < fieldValues.Length; i++) { object value = fieldValues[i]; // The DocumentId field path is handled differently to other fields. We accept a string (relative path) or // a DocumentReference (absolute path that must be a descendant of this collection). if (Equals(_orderings[i].Field, FieldPath.DocumentId)) { switch (fieldValues[i]) { case string relativePath: // Note: this assumes querying over a single collection at the moment. // Convert to a DocumentReference for the cursor PathUtilities.ValidateId(relativePath, nameof(fieldValues)); value = Collection.Document(relativePath); break; case DocumentReference absoluteRef: // Just validate that the given document is a direct child of the parent collection. GaxPreconditions.CheckArgument(absoluteRef.Parent.Equals(Collection), nameof(fieldValues), "A DocumentReference cursor value for a document ID must be a descendant of the collection of the query"); break; default: throw new ArgumentException($"A cursor value for a document ID must be a string (relative path) or a DocumentReference", nameof(fieldValues)); } } var convertedValue = ValueSerializer.Serialize(value); if (SentinelValue.GetKind(convertedValue) != SentinelValue.SentinelKind.None) { throw new ArgumentException("Snapshot ordering contained a sentinel value"); } cursor.Values.Add(convertedValue); } return(cursor); }
internal static SentinelValue ForArrayValue(SentinelKind sentinelKind, object[] values) { GaxPreconditions.CheckNotNull(values, nameof(values)); ArrayValue array = ValueSerializer.Serialize(values).ArrayValue; // This is just checking that the simple approach we've taken in the previous line // really did what we expect. GaxPreconditions.CheckState(array != null, "Input wasn't serialized as an array"); AugmentedValue augmented = new AugmentedValue { Kind = sentinelKind, Array = array }; return(new SentinelValue(augmented)); }
internal static SentinelValue ForArrayValue(SerializationContext context, SentinelKind sentinelKind, object[] values) { GaxPreconditions.CheckNotNull(values, nameof(values)); ArrayValue array = ValueSerializer.Serialize(context, values).ArrayValue; // This is just checking that the simple approach we've taken in the previous line // really did what we expect. GaxPreconditions.CheckState(array != null, "Input wasn't serialized as an array"); GaxPreconditions.CheckState(!array.Values.Any(FindNestedSentinels), "Sentinel values cannot be nested in array union/remove values."); AugmentedValue augmented = new AugmentedValue { Kind = sentinelKind, Array = array }; return(new SentinelValue(augmented)); }
/// <summary> /// Adds an update operation that updates just the specified fields paths in the document, with the corresponding values. /// </summary> /// <param name="documentReference">A document reference indicating the path of the document to update. Must not be null.</param> /// <param name="updates">The updates to perform on the document, keyed by the field path to update. Fields not present in this dictionary are not updated. Must not be null or empty.</param> /// <param name="precondition">Optional precondition for updating the document. May be null, which is equivalent to <see cref="Precondition.MustExist"/>.</param> /// <returns>This batch, for the purposes of method chaining.</returns> public WriteBatch Update(DocumentReference documentReference, IDictionary <FieldPath, object> updates, Precondition precondition = null) { GaxPreconditions.CheckNotNull(documentReference, nameof(documentReference)); GaxPreconditions.CheckNotNull(updates, nameof(updates)); GaxPreconditions.CheckArgument(updates.Count != 0, nameof(updates), "Empty set of updates specified"); GaxPreconditions.CheckArgument(precondition?.Proto.Exists != true, nameof(precondition), "Cannot specify a must-exist precondition for update"); var serializedUpdates = updates.ToDictionary(pair => pair.Key, pair => ValueSerializer.Serialize(pair.Value)); var expanded = ExpandObject(serializedUpdates); var serverTimestamps = new List <FieldPath>(); var deletes = new List <FieldPath>(); FindSentinels(expanded, FieldPath.Empty, serverTimestamps, deletes); // This effectively validates that a delete wasn't part of a map. It could still be a multi-segment field path, but it can't be within something else. GaxPreconditions.CheckArgument(deletes.All(fp => updates.ContainsKey(fp)), nameof(updates), "Deletes cannot be nested within update calls"); RemoveSentinels(expanded, deletes); RemoveSentinels(expanded, serverTimestamps); AddUpdateWrites(documentReference, expanded, updates.Keys.ToList(), precondition ?? Precondition.MustExist, serverTimestamps, false); return(this); }
/// <summary> /// Adds an update operation that updates just the specified fields paths in the document, with the corresponding values. /// </summary> /// <param name="documentReference">A document reference indicating the path of the document to update. Must not be null.</param> /// <param name="updates">The updates to perform on the document, keyed by the field path to update. Fields not present in this dictionary are not updated. Must not be null or empty.</param> /// <param name="precondition">Optional precondition for updating the document. May be null, which is equivalent to <see cref="Precondition.MustExist"/>.</param> /// <returns>This batch, for the purposes of method chaining.</returns> public WriteBatch Update(DocumentReference documentReference, IDictionary <FieldPath, object> updates, Precondition precondition = null) { GaxPreconditions.CheckNotNull(documentReference, nameof(documentReference)); GaxPreconditions.CheckNotNull(updates, nameof(updates)); GaxPreconditions.CheckArgument(updates.Count != 0, nameof(updates), "Empty set of updates specified"); GaxPreconditions.CheckArgument(precondition?.Exists != true, nameof(precondition), "Cannot specify a must-exist precondition for update"); var serializedUpdates = updates.ToDictionary(pair => pair.Key, pair => ValueSerializer.Serialize(documentReference.Database.SerializationContext, pair.Value)); var expanded = ExpandObject(serializedUpdates); var sentinels = FindSentinels(expanded); // This effectively validates that a delete wasn't part of a map. It could still be a multi-segment field path, but it can't be within something else. var deletePaths = sentinels.Where(sf => sf.IsDelete).Select(sf => sf.FieldPath); GaxPreconditions.CheckArgument(deletePaths.All(fp => updates.ContainsKey(fp)), nameof(updates), "Deletes cannot be nested within update calls"); RemoveSentinels(expanded, sentinels); var nonDeletes = sentinels.Where(sf => !sf.IsDelete).ToList(); AddUpdateWrite(documentReference, expanded, updates.Keys.ToList(), precondition ?? Precondition.MustExist, sentinelFields: nonDeletes); return(this); }
private Cursor CreateCursorFromSnapshot(DocumentSnapshot snapshot, bool before, out IReadOnlyList <InternalOrdering> newOrderings) { GaxPreconditions.CheckArgument(Equals(snapshot.Reference.Parent, Collection), nameof(snapshot), "Snapshot was from incorrect collection"); GaxPreconditions.CheckNotNull(snapshot, nameof(snapshot)); var cursor = new Cursor { Before = before }; bool hasDocumentId = false; // We may or may not need to add some orderings; this is communicated through the out parameter. newOrderings = _orderings; // Only used when we need to add orderings; set newOrderings to this at the same time. List <InternalOrdering> modifiedOrderings = null; if (_orderings.Count == 0 && _filters != null) { // If no explicit ordering is specified, use the first inequality to define an implicit order. foreach (var filter in _filters) { if (!filter.IsEqualityFilter()) { modifiedOrderings = new List <InternalOrdering>(newOrderings) { new InternalOrdering(filter.Field, Direction.Ascending) }; newOrderings = modifiedOrderings; } } } else { hasDocumentId = _orderings.Any(order => Equals(order.Field, FieldPath.DocumentId)); } if (!hasDocumentId) { // Add implicit sorting by name, using the last specified direction. Direction lastDirection = _orderings.Count == 0 ? Direction.Ascending : _orderings.Last().Direction; // Clone iff this is the first new ordering. if (modifiedOrderings == null) { modifiedOrderings = new List <InternalOrdering>(newOrderings); newOrderings = modifiedOrderings; } modifiedOrderings.Add(new InternalOrdering(FieldPath.DocumentId, lastDirection)); } foreach (var ordering in newOrderings) { var field = ordering.Field; var value = Equals(field, FieldPath.DocumentId) ? ValueSerializer.Serialize(snapshot.Reference) : snapshot.ExtractValue(field); if (value == null) { throw new ArgumentException($"Snapshot does not contain field {field}", nameof(snapshot)); } cursor.Values.Add(ValueSerializer.Serialize(value)); } return(cursor); }