protected function doUpdate(array $query, array $newObj, array $options = [])
    {
        $file = isset($newObj['$set']['file']) ? $newObj['$set']['file'] : null;
        unset($newObj['$set']['file']);

        if ($file === null) {
            $file = isset($newObj['file']) ? $newObj['file'] : null;
            unset($newObj['file']);
        }

        /* Before we inspect $newObj, remove an empty $set operator we may have
         * left behind due to extracting the file field above.
         */
        if (empty($newObj['$set'])) {
            unset($newObj['$set']);
        }

        /* Determine if $newObj includes atomic modifiers, which will tell us if
         * we can get by with a storeFile() in some situations or if a two-step
         * storeFile() and update() is necessary.
         */
        $newObjHasModifiers = false;

        foreach (array_keys($newObj) as $key) {
            if ('$' === $key[0]) {
                $newObjHasModifiers = true;
            }
        }

        // Is there a file in need of persisting?
        if (isset($file) && $file->isDirty()) {
            /* It is impossible to overwrite a file's chunks in GridFS so we
             * must remove it and re-persist a new file with the same data.
             *
             * First, use findAndRemove() to remove the file metadata and chunks
             * prior to storing the file again below. Exclude metadata fields
             * from the result, since those will be reset later.
             */
            $document = $this->findAndRemove($query, ['fields' => [
                'filename' => 0,
                'length' => 0,
                'chunkSize' => 0,
                'uploadDate' => 0,
                'md5' => 0,
                'file' => 0,
            ]]);

            /* If findAndRemove() returned nothing (no match or removal), create
             * a new document with the query's "_id" if available.
             */
            if (! isset($document)) {
                /* If $newObj had modifiers, we'll need to do an update later,
                 * so default to an empty array for now. Otherwise, we can do
                 * without that update and store $newObj now.
                 */
                $document = $newObjHasModifiers ? [] : $newObj;

                /* If the document has no "_id" but there was one in the query
                 * or $newObj, we can use that instead of having storeFile()
                 * generate one.
                 */
                if (! isset($document['_id']) && isset($query['_id'])) {
                    $document['_id'] = $query['_id'];
                }

                if (! isset($document['_id']) && isset($newObj['_id'])) {
                    $document['_id'] = $newObj['_id'];
                }
            }

            // Document will definitely have an "_id" after storing the file.
            $this->storeFile($file, $document);

            if (! $newObjHasModifiers) {
                /* TODO: MongoCollection::update() would return a boolean if
                 * $newObj was not empty, or an array describing the update
                 * operation. Improvise, since we only stored the file and that
                 * returns the "_id" field.
                 */
                return true;
            }
        }

        // Now send the original update bringing the file up to date
        $options = isset($options['safe']) ? $this->convertWriteConcern($options) : $options;
        return $this->mongoCollection->update($query, $newObj, $options);
    }