Ejemplo n.º 1
0
        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);
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Adds an operation that sets data in a document, either replacing it completely or merging fields.
        /// </summary>
        /// <param name="documentReference">A document reference indicating the path of the document to update. Must not be null.</param>
        /// <param name="documentData">The data to store in the document. Must not be null.</param>
        /// <param name="options">The options to use when setting data in the document. May be null, which is equivalent to <see cref="SetOptions.Overwrite"/>.</param>
        /// <returns>This batch, for the purposes of method chaining.</returns>
        public WriteBatch Set(DocumentReference documentReference, object documentData, SetOptions options = null)
        {
            GaxPreconditions.CheckNotNull(documentReference, nameof(documentReference));
            GaxPreconditions.CheckNotNull(documentData, nameof(documentData));

            var fields = ValueSerializer.SerializeMap(documentReference.Database.SerializationContext, documentData);

            options = options ?? SetOptions.Overwrite;
            var sentinels  = FindSentinels(fields);
            var deletes    = sentinels.Where(sf => sf.IsDelete).ToList();
            var nonDeletes = sentinels.Where(sf => !sf.IsDelete).ToList();

            IDictionary <FieldPath, Value> updates;
            IReadOnlyList <FieldPath>      updatePaths;

            if (options.Merge)
            {
                var mask = options.FieldMask;
                if (mask.Count == 0)
                {
                    // Merge all:
                    // - If the data is empty, we force a write
                    // - Deletes are allowed anywhere
                    // - All timestamps converted to transforms
                    // - Each top-level entry becomes a FieldPath
                    RemoveSentinels(fields, nonDeletes);
                    // Work out the update paths after removing server timestamps but before removing deletes,
                    // so that we correctly perform the deletes.
                    updatePaths = ExtractDocumentMask(fields);
                    RemoveSentinels(fields, deletes);
                    updates = fields.ToDictionary(pair => new FieldPath(pair.Key), pair => pair.Value);
                }
                else
                {
                    // Merge specific:
                    // - Deletes must be in the mask
                    // - Only timestamps in the mask are converted to transforms
                    // - Apply the field mask to get the updates
                    GaxPreconditions.CheckArgument(deletes.All(sf => mask.Contains(sf.FieldPath)), nameof(documentData), "Delete cannot appear in an unmerged field");
                    nonDeletes = nonDeletes.Where(sf => mask.Any(fp => fp.IsPrefixOf(sf.FieldPath))).ToList();
                    RemoveSentinels(fields, deletes);
                    RemoveSentinels(fields, nonDeletes);
                    updates = ApplyFieldMask(fields, mask);
                    // Every field path in the mask must either refer to a now-removed sentinel, or a remaining value.
                    // Sentinels are permitted to be in the mask in a nested fashion rather than directly, e.g. a mask of "parent" with a sentinel of "parent.child.timestamp" is fine.
                    GaxPreconditions.CheckArgument(
                        mask.All(p => updates.ContainsKey(p) ||
                                 deletes.Any(sf => p.IsPrefixOf(sf.FieldPath)) ||
                                 nonDeletes.Any(sf => p.IsPrefixOf(sf.FieldPath))),
                        nameof(documentData), "All paths specified for merging must appear in the data.");
                    updatePaths = mask;
                }
            }
            else
            {
                // Overwrite:
                // - No deletes allowed
                // - All timestamps converted to transforms
                // - Each top-level entry becomes a FieldPath
                GaxPreconditions.CheckArgument(deletes.Count == 0, nameof(documentData), "Delete cannot appear in document data when overwriting");
                RemoveSentinels(fields, nonDeletes);
                updates     = fields.ToDictionary(pair => new FieldPath(pair.Key), pair => pair.Value);
                updatePaths = null;
            }

            AddUpdateWrite(documentReference, ExpandObject(updates), updatePaths, precondition: null, sentinelFields: nonDeletes);
            return(this);
        }