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)); } }
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); }
/// <summary> /// Finds all the sentinel values in a field map. /// Additionally, this validates that no sentinels exist in arrays (even nested). /// </summary> /// <param name="fields">The field map to find sentinels within.</param> /// <returns>The sentinel fields in the field map: both the value and the corresponding field path.</returns> private static List <SentinelField> FindSentinels(IDictionary <string, Value> fields) { List <SentinelField> result = new List <SentinelField>(); FindSentinelsRecursively(fields, FieldPath.Empty); return(result); void FindSentinelsRecursively(IDictionary <string, Value> currentFields, FieldPath currentParentPath) { foreach (var pair in currentFields) { Value value = pair.Value; string key = pair.Key; SentinelKind sentinelKind = SentinelValue.GetKind(value); if (sentinelKind != SentinelKind.None) { result.Add(new SentinelField(currentParentPath.Append(key), value)); } else if (value.MapValue != null) { FindSentinelsRecursively(value.MapValue.Fields, currentParentPath.Append(pair.Key)); } else if (value.ArrayValue != null) { ValidateNoSentinelValues(value.ArrayValue.Values); } } } void ValidateNoSentinelValues(IEnumerable <Value> values) { foreach (var value in values) { if (SentinelValue.GetKind(value) != SentinelKind.None) { // We don't know what parameter name to use here throw new ArgumentException("Sentinel values must not appear directly or indirectly within array values"); } else if (value.MapValue != null) { ValidateNoSentinelValues(value.MapValue.Fields.Values); } else if (value.ArrayValue != null) { ValidateNoSentinelValues(value.ArrayValue.Values); } } } }
/// <summary> /// Finds all the sentinel values in a field map, adding them to lists based on their type. /// Additionally, this validates that no sentinels exist in arrays (even nested). /// </summary> /// <param name="fields">The field map</param> /// <param name="parentPath">The path of this map within the document. (So FieldPath.Empty for a top-level call.)</param> /// <param name="serverTimestamps">The list to add any discovered server timestamp sentinels to</param> /// <param name="deletes">The list to add any discovered delete sentinels to</param> private static void FindSentinels(IDictionary <string, Value> fields, FieldPath parentPath, List <FieldPath> serverTimestamps, List <FieldPath> deletes) { foreach (var pair in fields) { Value value = pair.Value; string key = pair.Key; SentinelValue.SentinelKind sentinelKind = SentinelValue.GetKind(value); if (sentinelKind == SentinelValue.SentinelKind.ServerTimestamp) { serverTimestamps.Add(parentPath.Append(key)); } else if (sentinelKind == SentinelValue.SentinelKind.Delete) { deletes.Add(parentPath.Append(key)); } else if (value.MapValue != null) { FindSentinels(value.MapValue.Fields, parentPath.Append(pair.Key), serverTimestamps, deletes); } else if (value.ArrayValue != null) { ValidateNoSentinelValues(value.ArrayValue.Values); } } void ValidateNoSentinelValues(IEnumerable <Value> values) { foreach (var value in values) { if (SentinelValue.GetKind(value) != SentinelValue.SentinelKind.None) { // We don't know what parameter name to use here throw new ArgumentException("Sentinel values must not appear directly or indirectly within array values"); } else if (value.MapValue != null) { ValidateNoSentinelValues(value.MapValue.Fields.Values); } else if (value.ArrayValue != null) { ValidateNoSentinelValues(value.ArrayValue.Values); } } } }