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 FieldTransform ToFieldTransform() { var transform = new FieldTransform { FieldPath = FieldPath.EncodedPath }; switch (Kind) { case SentinelKind.ServerTimestamp: transform.SetToServerValue = ServerValue.RequestTime; break; case SentinelKind.ArrayRemove: transform.RemoveAllFromArray = SentinelValue.GetArrayValue(Value); break; case SentinelKind.ArrayUnion: transform.AppendMissingElements = SentinelValue.GetArrayValue(Value); break; case SentinelKind.NumericIncrement: transform.Increment = SentinelValue.GetIncrement(Value); break; default: throw new InvalidOperationException($"Cannot convert sentinel value of kind {Kind} to field transform"); } return(transform); }
internal AttributedProperty(PropertyInfo property, FirestorePropertyAttribute attribute) { _propertyInfo = property; FirestoreName = attribute.Name ?? property.Name; SentinelValue = SentinelValue.FromPropertyAttributes(property); // Note that the error messages in here don't use nameof, as we don't have an overload of CheckState accepting three // format arguments. // TODO: Put that in GAX and use nameof. string typeName = property.DeclaringType.FullName; GaxPreconditions.CheckState(property.GetIndexParameters().Length == 0, "{0}.{1} is an indexer, and should not be decorated with FirestorePropertyAttribute.", typeName, property.Name); // Annoyingly, we can't easily check whether the property is static - we have to check the individual methods. var getMethod = property.GetGetMethod(nonPublic: true); var setMethod = property.GetSetMethod(nonPublic: true); GaxPreconditions.CheckState(getMethod == null || !getMethod.IsStatic, "{0}.{1} is static, and should not be decorated with FirestorePropertyAttribute.", typeName, property.Name); GaxPreconditions.CheckState(setMethod == null || !setMethod.IsStatic, "{0}.{1} is static, and should not be decorated with FirestorePropertyAttribute.", typeName, property.Name); }
internal static Value ToProtoValue(this SentinelValue value) { switch (value) { case SentinelValue.ServerTimestamp: return(ServerTimestampSentinel); case SentinelValue.Delete: return(DeleteSentinel); default: throw new ArgumentException($"Unable to convert {nameof(SentinelValue)} value {value}"); } }
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); } } } }
private static Dictionary <string, Value> ConvertFirestoreAttributedType(object value) { var typeInfo = value.GetType().GetTypeInfo(); var ret = new Dictionary <string, Value>(); // TODO(optimization): We almost certainly want to cache this. foreach (var property in typeInfo.DeclaredProperties.Where(p => p.CanRead && p.GetGetMethod().IsPublic&& !p.GetGetMethod().IsStatic)) { var attribute = property.GetCustomAttribute <FirestorePropertyAttribute>(); if (attribute != null) { var sentinel = SentinelValue.FromPropertyAttributes(property); Value protoValue = sentinel == null?Serialize(property.GetValue(value)) : sentinel.ToProtoValue(); ret[attribute.Name ?? property.Name] = protoValue; } } return(ret); }
/// <summary> /// Creates a sentinel value to indicate the removal of the given values with an array. /// </summary> /// <param name="values">The values to include in the resulting sentinel value. Must not be null.</param> /// <returns>A sentinel value representing an array removal.</returns> public static object ArrayRemove(params object[] values) => SentinelValue.ForArrayValue(SentinelKind.ArrayRemove, values);
/// <summary> /// Creates a sentinel value to indicate the union of the given values with an array. /// </summary> /// <param name="values">The values to include in the resulting sentinel value. Must not be null.</param> /// <returns>A sentinel value representing an array union.</returns> public static object ArrayUnion(params object[] values) => SentinelValue.ForArrayValue(SentinelKind.ArrayUnion, values);
/// <summary> /// Creates a sentinel value to indicate the removal of the given values with an array. This over /// </summary> /// <param name="database">Database to check for custom serialization.</param> /// <param name="values">The values to include in the resulting sentinel value. Must not be null.</param> /// <returns>A sentinel value representing an array removal.</returns> public static object ArrayRemove(FirestoreDb database, params object[] values) => SentinelValue.ForArrayValue(GaxPreconditions.CheckNotNull(database, nameof(database)).SerializationContext, SentinelKind.ArrayRemove, values);
/// <summary> /// Creates a sentinel value to indicate the removal of the given values with an array. /// This overload assumes that any custom serializers are configured via attributes. Use the overload /// accepting a <see cref="FirestoreDb" /> if you need to use database-registered custom serializers. /// </summary> /// <param name="values">The values to include in the resulting sentinel value. Must not be null.</param> /// <returns>A sentinel value representing an array removal.</returns> public static object ArrayRemove(params object[] values) => SentinelValue.ForArrayValue(SerializationContext.Default, SentinelKind.ArrayRemove, values);
/// <summary> /// Creates a sentinel value to indicate an increment by the given value. /// </summary> /// <remarks> /// <para> /// If the current value is an integer or a double, both the current and the given value will be /// interpreted as doubles and all arithmetic will follow IEEE 754 semantics.Otherwise, the /// transformation will set the field to the given value. /// </para> /// </remarks> /// <param name="amount">The amount to increment the field by.</param> /// <returns>A sentinel value representing a field increment.</returns> public static object Increment(double amount) => SentinelValue.ForIncrement(new Value { DoubleValue = amount });
/// <summary> /// Creates a sentinel value to indicate an increment by the given value. /// </summary> /// <remarks> /// <para> /// If the current field value is an integer, possible integer overflows are resolved to /// <see cref="long.MaxValue"/> or <see cref="long.MinValue"/>. If the current field value /// is a double, both values will be interpreted as doubles and the arithmetic will follow IEEE 754 semantics. /// </para> /// <para> /// If the current field is not an integer or double, or if the field does not yet exist, the /// transformation will set the field to the given value. /// </para> /// </remarks> /// <param name="amount">The amount to increment the field by.</param> /// <returns>A sentinel value representing a field increment.</returns> public static object Increment(long amount) => SentinelValue.ForIncrement(new Value { IntegerValue = amount });