Set-Based Patch Operations



Overview

Defining set-based patching:


  • In other databases, a simple SQL query that updates a set of documents can look like this:
    UPDATE Users SET IsActive = 0 WHERE LastLogin < '2020-01-01'

  • To achieve that in RavenDB, define the following two components within a PatchByQueryOperation:

    1. The query:
      An RQL query that defines the set of documents to update.
      Use the exact same syntax as you would when querying the database/indexes for usual data retrieval.

    2. The update:
      A JavaScript clause that defines the updates to perform on the documents resulting from the query.

  • When sending the PatchByQueryOperation to the server, the server will run the query and perform the requested update on the query results.

    // A "query & update" sample
    // Update the set of documents from the Orders collection that match the query criteria:
    // =====================================================================================
    
    // The RQL part:
    from Orders where Freight < 10
    
    // The UPDATE part:
    update  {
        this.Freight += 10;
    }

Important characteristics:


  • Transactional batches:
    The patching of documents matching a specified query is run in batches of size 1024.
    Each batch is handled in a separate write transaction.

  • Dynamic behavior:
    During the patching process, documents that are added/modified after the patching operation has started
    may also be patched if they match the query criteria.

  • Concurrency:
    RavenDB doesn't perform concurrency checks during the patching process so it can happen that a document
    has been modified or deleted while patching is in progress.

  • Patching stale indexes:
    By default, set-based patch operations will only succeed if the index is Not stale.
    For indexes that are frequently updated, you can explicitly allow patching on stale results if needed.
    An example can be seen in the Allow updating stale results example.

  • Manage lengthy patch operations:
    The set-based patch operation (PatchByQueryOperation) runs in the server background may take a long time to complete.
    Executing the operation via the Send method return an object that can be awaited for completion or aborted (killed). Learn more about this and see dedicated examples in Manage length operations.

Examples

Update by collection query:


// Update all documents in a collection
// ====================================

// Define the Patch by Query Operation, pass the "query & update" string:
const patchByQueryOp = new PatchByQueryOperation(
    `from Orders as o
     update
     {
         // Increase the Freight in ALL documents in the Orders collection:
         o.Freight += 10;
     }`);

// Execute the operation by passing it to operations.send:
const operation = await documentStore.operations.send(patchByQueryOp);

Update by collection query - access metadata:


// Update the collection name for all documents in the collection
// ==============================================================

// Delete the document before recreating it with a different collection name:
const patchByQueryOp = new PatchByQueryOperation(
    `from Orders as c
     update
     {
         del(id(c));
         this["@metadata"]["@collection"] = "New_Orders";
         put(id(c), this);
     }`);

const operation = await documentStore.operations.send(patchByQueryOp);

Update by dynamic query:


// Update all documents matching a dynamic query
// =============================================

// Update the Discount in all orders that match the dynamic query predicate:
const patchByQueryOp = new PatchByQueryOperation(`from Orders as o
                                                  where o.Employee = 'employees/4-A'
                                                  update
                                                  {
                                                      o.Lines.forEach(line=> line.Discount = 0.3);
                                                  }`);

const operation = await documentStore.operations.send(patchByQueryOp);

// Note: An AUTO-INDEX will be created when the dynamic query is executed on the server.

Update by static index query:


// Update all documents matching a static index query
// ==================================================

// Modify the Supplier to 'suppliers/13-A' for all products that have 'suppliers/12-A': 
const patchByQueryOp = new PatchByQueryOperation(`from index 'Products/BySupplier' as p
                                                  where p.Supplier = 'suppliers/12-A'
                                                  update
                                                  {
                                                      p.Supplier = 'suppliers/13-A'
                                                  }`);

const operation = await documentStore.operations.send(patchByQueryOp);
class Products_BySupplier extends AbstractJavaScriptIndexCreationTask {
    constructor() {
        super();

        // Define the index-fields 
        this.map("Products", p => ({
            Supplier : e.Supplier
        }));
    }
}

Update all documents:


// Update all documents matching an @all_docs query
// ================================================

// Patch the 'Updated' field to ALL documents (query is using the @all_docs keyword):
const patchByQueryOp = new PatchByQueryOperation(`from @all_docs
                                                  update
                                                  {
                                                      this.Updated = true;
                                                  }`);

const operation = await documentStore.operations.send(patchByQueryOp);

Update by document ID:


// Update all documents matching a query by ID
// ===========================================

// Patch the 'Updated' field to all documents that have the specified IDs:
const patchByQueryOp = new PatchByQueryOperation(`from @all_docs as d
                                                  where id() in ('orders/1-A', 'companies/1-A')
                                                  update
                                                  {
                                                      d.Updated = true;
                                                  }`);

const operation = await documentStore.operations.send(patchByQueryOp);

Update by document ID using parameters:


// Update all documents matching a query by ID using query parmeters
// =================================================================

// Define an IndexQuery object:
const indexQuery = new IndexQuery();

// Define the "query & update" string
// Patch the 'Updated' field to all documents that have the specified IDs
// Parameter ($ids) contains the listed IDs:
indexQuery.query = `from @all_docs as d 
                    where id() in ($ids)
                    update {
                        d.Updated = true
                    }`;

// Define the parameters for the script:
indexQuery.queryParameters = {
    ids: ["orders/830-A", "companies/91-A"]
};

// Pass the indexQuery to the operation definition
const patchByQueryOp = new PatchByQueryOperation(indexQuery);

// Execute the operation
const operation = await documentStore.operations.send(patchByQueryOp);

Allow updating stale results:


  • Set allowStale to true to allow patching of stale results.

  • The RQL in this example is using an auto-index.
    Use allowStale in exactly the same way when querying a static-index.

// Update documents matching a dynamic query even if auot-index is stale
// =====================================================================

// Define an IndexQuery object:
const indexQuery = new IndexQuery();

// Define the "query & update" string
// Modify company to 'companies/13-A' for all orders that have 'companies/12-A':
indexQuery.query = `from Orders as o
                    where o.Company = 'companies/12-A'
                    update
                    {
                        o.Company = 'companies/13-A'
                    }`;

// Define query options:
const queryOptions = {
    // The query uses an auto-index (index is created if it doesn't exist yet).
    // Allow patching on all matching documents even if the auto-index is still stale.
    allowStale: true
};

// Pass indexQuery & queryOptions to the operation definition
const patchByQueryOp = new PatchByQueryOperation(indexQuery, queryOptions);

// Execute the operation
const operation = await documentStore.operations.send(patchByQueryOp);

Syntax


Send syntax

await send(operation);
Parameter Type Description
operation PatchByQueryOperation The operation object describing the query and the patch to perform.
Return value
Promise<OperationCompletionAwaiter> A promise that resolves to an object that allows waiting for the operation to complete.

PatchByQueryOperation syntax

// Available overload:
// ===================
patchByQueryOp = new PatchByQueryOperation(queryToUpdate);
patchByQueryOp = new PatchByQueryOperation(queryToUpdate, options);
Parameter Type Description
queryToUpdate string The query & patch definition.
queryToUpdate IndexQuery Object that allows adding parameters to the query & patch.
options object Options for the PatchByQueryOperation.

class IndexQuery {
    query;           // string
    queryParameters; // Record<string, object>
}        

// Options for 'PatchByQueryOperation'
{
    // Limit the amount of base operation per second allowed.
    maxOpsPerSecond; // number

    // Indicate whether operations are allowed on stale indexes.
    allowStale;      // boolean

    // If AllowStale is set to false and index is stale, 
    // then this is the maximum timeout to wait for index to become non-stale. 
    // If timeout is exceeded then exception is thrown.
    staleTimeout;    // number

    // Set whether operation details about each document should be returned by server.
    retrieveDetails; // boolean
}