This chapter covers the following topics related to updating selected portions of the content or metadata of a document using the db.documents.patch
function.
A partial update is an update you apply to a portion of a document or metadata, rather than replacing an entire document or all of its metadata. For example, inserting a JSON property or XML element, or changing the value of a JSON property. You can only apply partial content updates to JSON and XML documents. You can apply partial metadata updates to any document type.
A patch is a partial update descriptor, expressed in JSON or XML. A patch tells MarkLogic Server what update to apply and where to apply it. Four operations are available in a patch: insert, replace, replace-insert, and delete. (A replace-insert operation functions as a replace if there is at least one match for the target content; if there are no matches, then the operation functions as an insert.)
Please note that statements of the following form {'$ml.xml': '<prop:properties ......}
are not allowed in metadata patching.
Use the partial update feature for the following operations:
You can apply multiple updates in a single patch, and you can update both content and metadata in the same patch.
Patch operations can target JSON properties, XML elements and attributes, and data values such as JSON array items and the data in an XML element or attribute. You identify the target of an operation using XPath or JSONPath expressions. When inserting new content or metadata, the insertion point is further defined by specifying the position; for details, see How Position Affects the Insertion Point in the REST Application Developer's Guide.
When applying a patch to document content, the patch format must match the document format: An XML patch for an XML document, a JSON patch for a JSON document. You cannot patch the content of other document types. You can patch metadata for all document types. A metadata-only patch can be in either XML or JSON. A patch that modifies both content and metadata must match the document content type.
The Node.js Client API provides the following interfaces for building and applying patches:
Apply a patch by calling DatabaseClient.documents.patch
with a URI and one or more patch operations. Build patch operations using marklogic.patchBuilder
.
The following example patches the JSON document /patch/example.json
by inserting a property named child3
in the last child position under the property named theTop
. For a complete example, see Example: Adding a JSON Property.
const pb = marklogic.patchBuilder; db.documents.patch('/patch/example.json', pb.insert('/object-node("theTop")', 'last-child', {child3: 'INSERTED'}) );
For additional examples of building patch operations, see Patch Examples.
If a patch contains multiple operations, they are applied independently to the target document. That is, within the same patch, one operation does not affect the context path or select path results or the content changes of another. Each operation in a patch is applied independently to every matched node. If any operation in a patch fails with an error, the entire patch fails.
Content transformations are not directly supported in a partial update. However, you can implement a custom replacement content generation function to achieve the same effect. For details, see Constructing Replacement Data on MarkLogic Server.
Before patching JSON documents, you should familiarize yourself with the restrictions outlined in Limitations of JSON Path Expressions in the REST Application Developer's Guide.
The following example inserts a new property into a JSON document using the DatabaseClient.documents.patch
function and a patch builder. The example program does the following:
/patch/example.json
.theTop
named child3
in the last child position. The parent node is identified using an XPath expression.The db.documents.patch
function accepts a document URI and one or more operations. In this case, we pass only one operation: A property insertion constructed by calling the patch builder insert
method.
pb.insert('/object-node("theTop")', // path to parent node 'last-child', // insertion position {child3: 'INSERTED'}) // content to insert
The promise pattern is used to chain together the write, patch, and read operations. For details, see Promise Result Handling Pattern.
The example script is shown below. The initial write operation is included in the example only to encapsulate the example document and ensure consistent behavior across runs. Usually, the document targeted by a patch would have been loaded into the database separately.
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); // (1) Insert the base document into the database db.documents.write({ uri: '/patch/example.json', contentType: 'application/json', content: { theTop: { child1: { grandchild: 'gc-value' }, child2: 'c2-value' } } }).result().then(function(response) { // (2) Patch the document const pb = marklogic.patchBuilder; return db.documents.patch(response.documents[0].uri, pb.insert('/object-node("theTop")', 'last-child', {child3: 'INSERTED'}) ).result(); }).then(function(response) { // (3) Read the patched document return db.documents.read(response.uri).result(); }).then(function(response) { console.log(response[0].content); });
The example program results in the following document transformation:
On success, DatabaseClient.documents.patch
returns the URI of the patched document. For example, the result of the db.documents.patch call above is:
{ uri: '/patch/example.json' }
For more examples, see Patch Examples.
You can include one or more of the following operations in a call to DatabaseClient.documents.patch
: insert, replace, replace-insert, remove. These operations can occur multiple times in a given patch
call. Use the marklogic.patchBuilder
interface to construct each operation. For example:
db.documents.patch(uri, pb.insert(path, position, newContent) )
The patch builder includes special purposes interfaces for constructing patch operations that target metadata: patchBuilder.collections
, patchBuilder.permissions
, patchBuilder.properties
, patchBuilder.quality
, and patchBuilder.metadataValues
. These interfaces enable you to patch collections, permissions, quality, document properties, and values metadata without knowing the internal representation of metadata. For an example of how to use these interfaces, see Example: Patching Metadata.
In addition, you can include patch configuration directives, such as patchBuilder.library
and patchbuilder.pathLanguage
. You can only include one library
directive, and it is only needed if you use custom functions to generate replacement content; for details, see Constructing Replacement Data on MarkLogic Server.
The table below summarizes the patch builder methods for constructing operations on content:
Builder Method | Descriptions |
---|---|
insert |
Insert a new property or array item. |
replace |
Replace an existing property or array item. |
replaceInsert |
Replace a JSON property or array item; if there are no existing matching properties, perform an insertion instead. |
remove |
Remove a JSON property or array item. |
library |
Identify a server-side XQuery library module that contains custom replacement content generation functions that can be used in the apply operation that is part of a replace or replaceInsert operation. |
apply |
Specify a server-side replacement content generation function. The patch operation must include a library operation, and the named function must be in that module. |
pathLanguage |
Configure a patch to parse select and context expressions as either XPath (default) or JSONPath expressions. |
The following table summarizes the patch builder interfaces for constructing operations on metadata.
Builder Method | Descriptions |
---|---|
collections |
Construct patch operations for modifying collections metadata. |
permissions |
Construct patch operations for modifying permissions metadata. |
properties |
Construct patch operations for modifying document properties metadata. |
quality |
Construct patch operations for modifying quality metadata. |
metadataValues |
Construct patch operations for modifying values metadata. |
The following table summarizes the patch builder methods for constructing cofiguration directives:
Builder Method | Descriptions |
---|---|
library |
Identify a server-side XQuery library module that contains custom replacement content generation functions that can be used in the apply operation that is part of a replace or replaceInsert operation. |
apply |
Specify a server-side replacement content generation function. The patch operation must include a library operation, and the named function must be in that module. |
pathLanguage |
Configure a patch to parse select and context expressions as either XPath (default) or JSONPath expressions. |
Use patchbuild.insert
to create an operation that inserts a new JSON property or array item. Build an insert operation with a call of the following form:
marklogic.patchBuilder.insert( context, position, content, cardinality)
The following table summarizes the parameters of the patchBuilder.insert
function.
Parameter | Req'd | Description |
---|---|---|
context |
Y | An XPath or JSONPath expression that selects an existing JSON property or array element on which to operate. The expression can select multiple items. If no matches are found for the The path expression is restricted to the subset of XPath for which cts:valid-document-patch-path (XQuery) or cts.validDocumentPatchPath (JavaScript) returns true. For details, see Path Expressions Usable in Patch Operations in the REST Application Developer's Guide. |
position |
Y | Where to insert the content, relative to the JSON property or value selected by context . The pos-selector must be one of "before" , "after" , or "last-child" . For details, see How Position Affects the Insertion Point. |
content |
Y | The new content to be inserted, expressed as a JSON object, array, or atomic value. |
cardinality |
N | The required occurrence of matches to |
Use patchBuilder.replace
to create an operation that replaces an existing JSON property value or array item. If no matching JSON property or array item exists, the operation is silently ignored. Build a replace
operation with a call of the following form:
marklogic.patchBuilder.replace( select, content, cardinality, apply)
You can use apply
to specify a content generation builtin or custom function for generating dynamic content. For details, see Constructing Replacement Data on MarkLogic Server.
The following table summarizes the parameters of the patchBuilder.replace
function.
Parameter | Req'd | Description |
---|---|---|
select |
Y | An XPath or JSONPath expression that selects the JSON property or array element to replace. If no matches are found for the The path expression is restricted to the subset of XPath for which cts:valid-document-patch-path (XQuery) or cts.validDocumentPatchPath (JavaScript) returns true. For details, see Path Expressions Usable in Patch Operations in the REST Application Developer's Guide. The selected item(s) cannot be the target of any other operation in the patch. The ancestor of the selected item may not be modified by a |
content |
N | The replacement value. If you omit this parameter, you must specify a content generation function in the If |
cardinality |
N | The required occurrence of matches to |
apply |
N | The local name of a replacement content generation function. If you do not specify a function, the operation must include a If you name a custom function, the For details, see Constructing Replacement Data on MarkLogic Server. |
Use patchBuilder.replaceInsert
to create an operation that replaces a property value or array item if it exists, or insert a property or array item if does not. Build a replace-insert
operation with a call of the following form:
replaceInsert( select, context, position, content, cardinality, apply)
You can omit content
if you use apply
to specify a content generation builtin or custom function for generating dynamic content. For details, see Constructing Replacement Data on MarkLogic Server.
The following table summarizes the parts of a replace-insert
operation.
Parameter | Req'd | Description |
---|---|---|
|
Y | An XPath or JSONPath expression that selects the JSON property or array item to replace. If no matches are found for the The path expression is restricted to the subset of XPath for which cts:valid-document-patch-path (XQuery) or cts.validDocumentPatchPath (JavaScript) returns true.. For details, see Path Expressions Usable in Patch Operations in the REST Application Developer's Guide. The selected item(s) cannot be the target of any other operation in the patch. The ancestor of the selected item may not be modified by a |
content |
N | The content with which to replace the selected value. If there is no If |
|
Y | An XPath or JSONPath expression that selects an existing property or array element on which to operate. The expression can select multiple items. If no matches are found for the either the The path expression is restricted to the subset of XPath for which cts:valid-document-patch-path (XQuery) or cts.validDocumentPatchPath (JavaScript) returns true. For details, see Path Expressions Usable in Patch Operations in the REST Application Developer's Guide. The ancestor of the selected node may not be modified by a |
position |
N | If |
cardinality |
N | The required occurrence of matches to |
apply |
N | The local name of a replacement content generation function. If you do not specify a function, the operation must include a If you name a custom function, the For details, see Constructing Replacement Data on MarkLogic Server. |
Use patchBuilder.remove
to create an operation that removes a JSON property or array element. A call to the patchBuilder.remove
function has the following form:
marklogic.patchBuilder.remove( select, cardinality)
The following table summarizes the parameters of patchBuilder.remove
.
Component | Req'd | Description |
---|---|---|
select |
Y | An XPath or JSONPath expression that selects the JSON property or array element to remove. If no matches are found for the The path expression is restricted to the subset of XPath for which cts:valid-document-patch-path (XQuery) or cts.validDocumentPatchPath (JavaScript) returns true. For details, see Path Expressions Usable in Patch Operations in the REST Application Developer's Guide.. The selected item(s) cannot be the target of any other operation in the patch. The ancestor of the selected item may not be modified by a |
cardinality |
N | The required occurrence of matches to |
Use patchBuilder.apply
to create a configuration directive that specifies a server-side function with which to generate replacement conent for a replace
or replaceInsert
operation. The named function must be in a server-side module identified by a library
directive included in the same call to db.documents.patch
.
A call to the patchBuilder.apply
function has the following form:
marklogic.patchBuilder.apply(functionName)
For details, see Constructing Replacement Data on MarkLogic Server.
Use patchBuilder.library
to create a configuration directive that identifies a server-side library module containing one or more functions for generating replacement content. The module must conform to the conventions described in Writing a Custom Replacement Constructor. To make use of the functions in your library, include the generated library directive in your call to db.documents.patch
, and include the output from patchBuilder.apply
when calling the replace
or replaceInsert
builder functions.
A call to the patchBuilder.library
function has the following form:
marklogic.patchBuilder.library(moduleName)
For details, see Constructing Replacement Data on MarkLogic Server.
By default, all path expressions in patches are expressed using XPath. JSONPath is also supported for compatibility with previous releases, but it is deprecated. You do not need to use a pathLanguage
directive unless you use JSONPath.
Use patchBuilder.pathLanguage
to create a configuration directive that specifies the path language in which you express context and select expressions in your patch operations. A call to the patchBuilder.pathLanguage
function must have one of the following forms:
marklogic.patchBuilder.pathLanguage('xpath') marklogic.patchBuilder.pathLanguage('jsonpath')
Include the resulting directive in your call to db.documents.patch
.
You must use the same path language for all operations in a DatabaseClient.documents.patch
call.
Use the patchBuilderCollections
methods to construct a patch operation for collections metadata. Use patchBuiler.collections
to access these methods. You can add or remove a collection, using the following methods:
marklogic.patchBuilder.collections.add(collName) marklogic.patchBuilder.collections.remove(collName)
For an example, see Example: Patching Metadata.
Use patchBuilderPermissions
methods to construct a patch operation for permissions metadata. Use patchBuilder.permissions
to access these methods. You can add or remove a permission or replace the capabilities of a role using this interface.
marklogic.patchBuilder.permissions.add(role, capabilities) marklogic.patchBuilder.permissions.remove(role) marklogic.patchBuilder.permissions.replace(role, capabilities)
Where role is a string that contains the role name, and capabilities is either a single string or an array of strings with the following possible values: read, insert, update, execute.
Note that replace
replaces all the capabilities associated with a role. You cannot use it to selectively replace a single capability. Rather, you must operate on a role and the associated capabilities as a single unit.
For an example, see Example: Patching Metadata.
Use patchBuilderProperties
methods to construct a patch operation for document properties metadata. Use patchBuilder.properties
to access these methods. You can add or remove a property, or replace the value of a property.
marklogic.patchBuilder.properties.add(name, value) marklogic.patchBuilder.properties.remove(name) marklogic.patchBuilder.properties.replace(name, value)
For an example, see Example: Patching Metadata.
Use patchBuilderQuality
methods to construct a patch operation that sets the value of quality metadata. Use patchBuilder.quality
to access these methods.
marklogic.patchBuilder.quality.set(value)
For an example, see Example: Patching Metadata.
Use patchBuilderMetadataValues
methods to construct a patch operation on values metadata. Use patchBuilder.metdataValues
to access these methods. You can add or remove a key-value pair, or replace the value of an existing key.
marklogic.patchBuilder.metadataValues.add(name, value) marklogic.patchBuilder.metadataValues.remove(name) marklogic.patchBuilder.metadataValues.replace(name, value)
For an example, see Example: Patching Metadata.
When you insert, replace, or delete content or metadata, the patch definition must include enough context to tell MarkLogic Server what JSON or XML components to operate on. For example, which json property or XML element to delete, where to insert a new property or element, or which JSON property, XML element, or value to replace.
When you create a patch using a builder, you specify the context through the contextPath and selectPath parameters of builder methods such as DocumentPatchBuilder.insertFragment()
or DocumentPatchBuilder.replaceValue()
. When you create a patch from raw XML or JSON, you specify the operation context through the context
and select
XML attributes or JSON property.
Use XPath expressions to define the operation context. For security and performance reasons, your XPath expressions are restricted to the subset of XPath for which cts:valid-document-patch-path (XQuery) or cts.validDocumentPatchPath (JavaScript) returns true. For details, see Patch Feature of the Client APIs in the XQuery and XSLT Reference Guide.
Insertion operations have an additional position parameter/property that defines where to insert new content relative to the context, such as before or after the selected node. For more details, seeHow Position Affects the Insertion Point.
The insert
and replace-insert
patch operations include a position parameter that defines the point of insertion when coupled with a context expression. This section describes the details of how position affects the insertion point.
This topic focuses on patching JSON documents. For details on XML, see Specifying Position in XML in the REST Application Developer's Guide.
The position parameter to a PatchBuilder operation (or the position
property of a raw patch) specifies where to insert new content relative to the item selected by the context XPath expression. Position can be one of the following values:
before
: Insert before the item selected by context
.after
: Insert after the item selected by context
.last-child
: Insert into the value of the target property or array, in the position of last child. The value selected by context
must be an object or array node.The same usage applies whether inserting on behalf of an insert
operation or a replace-insert
operation.
The following table shows example combinations of context
and position
and the resulting insertion point for new content. The insertion point is indicated by ***.
For more information about XPath over JSON, see Traversing JSON Documents Using XPath in the Application Developer's Guide.
This section contains examples of constructing and applying patches to metadata and JSON documents. The following topics are covered:
To patch XML documents, you must construct a raw patch. For details, see Patching XML Documents and Creating a Patch Without a Builder.
The examples are self-contained, except for the database connection information. Each example assumes your connection information is encapsulated in a module named my-connection.js
that is co-located with the example script.
This module is expected contain contents similar to the following:
module.exports = { connInfo: { host: 'localhost', port: 8000, user: your-ml-username, password: your-ml-user-password } };
For details, see Using the Examples in This Guide.
This example demonstrates the insert operation by making the following changes to the base JSON document:
INSERTED1
).INSERTED2
).INSERTED3
).INSERTED4
).INSERTED5
).INSERTED6
).The example first inserts the base document into the database, then builds and applies a patch. Finally, the modified document is retrieved from the database and displayed on the console. The example uses a Promise pattern to sequence these operations; for details, see Promise Result Handling Pattern.
To run the example, copy the following code into a file, then supply it to the node
command. For example: node insert.js
.
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); // (1) Insert the base document into the database db.documents.write({ uri: '/patch/insert.json', contentType: 'application/json', content: { theTop: { child1: { grandchild: 'gcv' }, child2: [ 'c2v1', 'c2v2' ], child3: [ {c3k1: 'c3v1'}, {c3k2: 'c3v2'} ] } } }).result().then(function(response) { // (2) Patch the document const pb = marklogic.patchBuilder; return db.documents.patch(response.documents[0].uri, // insert a sibling of theTop pb.insert('/theTop', 'before', {INSERTED1: [ 'i1v1', 'i1v2']}), // insert a property in child1's value pb.insert('/theTop/child1/grandchild', 'before', {INSERTED2: 'i2v'}), // insert an array item in child2 by value pb.insert('/theTop/child2[. = "c2v1"]', 'after', 'INSERTED3'), // insert an array item in child2 by position pb.insert('/theTop/child2[2]', 'after', 'INSERTED4'), // insert an object in child3 pb.insert('/theTop/array-node("child3")', 'last-child', {INSERTED5 : 'i5v'}), // insert a new child of theTop pb.insert('/theTop', 'last-child', {INSERTED6: 'i6v'}) ).result(); }).then(function(response) { // (3) Emit the resulting document return db.documents.read(response.uri).result(); }).then(function(documents) { console.log(JSON.stringify(documents[0].content)); });
The following table shows how applying the patch changes the target document.
Note that when you insert something using last-child
for the position, the insertion context XPath expression must refer to the containing object or array. When you construct an XPath expression such as /a/b
, the expression references a value (or sequence of values), rather than the container. To reference the container, use the node()
and array-node()
operators. This is why the operation that creates INSERTED5 under child3
uses array-node("child3")
, as shown below:
pb.insert('/theTop/array-node("child3")', 'last-child', {INSERTED5 : 'i5v'})
If you change the insertion context expression to /theTop/child3
, you're referencing the sequence of values in the array, not the array object to which you want to add a child.
To insert a property if and only if it does not already exist, use a predicate in the context path expression to test for existence. For example, the following patch will insert a property named TARGET only if the object does not already contain such a property:
pb.insert('/theTop[fn:empty(TARGET)]', 'last-child', {TARGET: 'INSERTED'})
This patch must use the last-child
position because the context selects the node that will contain the new property.
For more information about XPath over JSON, see Traversing JSON Documents Using XPath in the Application Developer's Guide.
This example demonstrates the replace operation by making the following changes to a JSON document:
child1
), object value (child2
), or array value (child3
).child4
).child5
).child6
).child6
).The example first inserts the base document into the database, then builds and applies a patch. Finally, the modified document is retrieved from the database and displayed on the console. The example uses a Promise pattern to sequence these operations; for details, see Promise Result Handling Pattern.
To run the example, copy the following code into a file, then supply it to the node
command. For example: node replace.js
.
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); // (1) Insert the base document into the database db.documents.write({ uri: '/patch/replace.json', contentType: 'application/json', content: { theTop: { child1: 'c1v', child2: { gc: 'gcv' }, child3: [ 'c3v1', 'c3v2' ], child4: [ 'c4v1', 'c4v2' ], child5: [ 'c5v1', 'c5v2' ], child6: [ {gc1: 'gc1v'}, {gc2:'gc2v'}], child7: [ 'av1', ['nav1', 'nav2'], 'av2'], child8: [ 'av1', ['nav1', 'nav2'], 'av2'] } } }).result().then(function(response) { // (2) Patch the document const pb = marklogic.patchBuilder; return db.documents.patch(response.documents[0].uri, // replace the simple value of a property pb.replace('/theTop/child1', 'REPLACED1'), // replace a property value that is an object pb.replace('/theTop/child2', {REPLACE2: 'gc2'}), // replace the value of a property that is an array pb.replace('/theTop/array-node("child3")', ['REPLACED3a', 'REPLACED3b']), // replace an array item by position pb.replace('/theTop/child4[1]', 'REPLACED4a'), // replace an array item by value pb.replace('/theTop/child4[.="c4v2"]', 'REPLACED4b'), // replace the value of all items in an array with the same value pb.replace('/theTop/child5', 'REPLACED5'), // replace the value of a property in an array item pb.replace('/theTop/child6/gc1', 'REPLACED6a'), // replace an object-valued array item by property name pb.replace('/theTop/child6[gc2]', {REPLACED6b: '6bv'}), // replace the value of an array item that is a nested array pb.replace('/theTop/array-node("child7")/node()[2]', [ 'REPLACED7a', 'REPLACED7b' ]), // replace the value of item in a nested array pb.replace('/theTop/child8[2]', 'REPLACED8') ).result(); }).then(function(response) { // (3) Emit the resulting document return db.documents.read(response.uri).result(); }).then(function(documents) { console.log(JSON.stringify(documents[0].content, null, 2)); });
The following table shows how applying the patch changes the target document.
You should understand the difference between selecting a container and selecting its contents. For example, consider the two replacement operations applied to child5
. The XPath expression /theTop/child6/gc1
addresses the value of property with name gc1
. Therefore, the replacement operation results in the following change:
pb.replace('/theTop/child6/gc1', 'REPLACED6a') {"gc1" : "gc1v"} ==> {"gc1" : "REPLACED6a"}
By contrast the XPath expression /theTop/child6[gc2]
selects object nodes that contain a property named gc2
. Therefore, the replacement operation replaces the entire object with a new value, resulting in the following change:
pb.replace('/theTop/child6[gc2]', {REPLACED6b: '6bv'}) {"gc2" : "gc2v"} ==> {"REPLACED6b": "6bv"}
Similarly, consider the replacement operation on child3
. The XPath expression /theTop/array-node("child3")
selects the array node named child3
, so the operation replaces the entire array with a new value. For example:
pb.replace('/theTop/array-node("child3")', ['REPLACED3a', 'REPLACED3b']) "child3": ["c3v1", "c3v2"] ==> "child3": ["REPLACED3a", "REPLACED3b"]
By contrast, an XPath expression such as /theTop/child5
selects the values in the array ('c5v1'
, 'c5v2'
), so a replacement operation with this select expression replaces each of the array values with the same content. For example:
pb.replace('/theTop/child5', 'REPLACED5') "child5": ["c5v1", "c5v2"] ==> "child5": ["REPLACED5", "REPLACED5"]
For more information on XPath over JSON, see Traversing JSON Documents Using XPath in the Application Developer's Guide.
This example demonstrates the replace-insert
patch operation. The example performs the following update operations:
child1
).child2
). child3
).The example first inserts the base document into the database, then builds and applies a patch. Finally, the modified document is retrieved from the database and displayed on the console. The example uses a Promise pattern to sequence these operations; for details, see Promise Result Handling Pattern.
To run the example, copy the following code into a file, then supply it to the node
command. For example: node replace-insert.js
.
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); // (1) Insert the base document into the database db.documents.write({ uri: '/patch/replace-insert.json', contentType: 'application/json', content: { theTop: { child1: [ 'c1v1', 'c1v2' ], child2: [ 'c2v1', 'c2v2' ], child3: [ { c3a: 'c3v1' }, { c3b: 'c3v2' } ] } } }).result().then(function(response) { // (2) Patch the document const pb = marklogic.patchBuilder; return db.documents.patch(response.documents[0].uri, // Replace the value of an array item by position, or // insert a new one in the target position. pb.replaceInsert('/theTop/child1[1]', '/theTop/array-node("child1")', 'last-child', 'REPLACED1'), pb.replaceInsert('/theTop/child1[3]', '/theTop/child1[2]', 'after', 'INSERTED1'), // Replace an array item that has a specific value, or // insert a new item with that value at the end of the array pb.replaceInsert('/theTop/child2[. = "c2v1"]', '/theTop/node("child2")', 'last-child', 'REPLACED2'), pb.replaceInsert('/theTop/child2[. = "INSERTED2"]', '/theTop/array-node("child2")', 'last-child', 'INSERTED2'), // Replace the value of an object that is an array item, or // insert an equivalent object at the end of the array pb.replaceInsert('/theTop/child3[c3a]', '/theTop/node("child3")', 'last-child', { REPLACED3: 'c3rv'}), pb.replaceInsert('/theTop/child3[INSERTED3]', '/theTop/node("child3")', 'last-child', { INSERTED3: 'c3iv'}) ).result(); }).then(function(response) { // (3) Emit the resulting document return db.documents.read(response.uri).result(); }).then(function(documents) { console.log(JSON.stringify(documents[0].content, null, 2)); }, function(error) { console.log(error); throw error; } );
The following table shows how applying the patch changes the target document.
Recall that the select
path identifies the content to replace. When working with an array item, an absolute path is usually required. For example, consider the following patch operation:
pb.replaceInsert('/theTop/child1[1]', '/theTop/array-node("child1")', 'last-child', 'REPLACED1')
The goal is to replace the value of the first item in the array value of /theTop/child1 if it exists. If the array is empty, insert the new value. That is, one of these two transformations takes place:
{"theTop": {"child1": ["c1v1", "c1v2"], ... } ==> {"theTop": {"child1": ["REPLACED1", "c1v2"], ... } {"theTop": {"child1": [], ... } ==> {"theTop": {"child1": ["REPLACED1"], ... }
The select
expression, /theTop/child1[1]
, must target an array item value, while the context expression must target the containing array node by referencing /theTop/array-node("child1")
. You cannot make the select expression relative to the context expression in this case.
Note that while you can target an entire array item value with replace-insert
, you cannot target just the value of a property. For example, consider the following array in JSON:
"child3": [ { "c3a": "c3v1" }, { "c3b": "c3v2" } ]
You can use replace-insert
on the entire object-valued item { "c3a": "c3v1" }, as is done in the example. However, you cannot construct an operation that targets just the value of the property in the object ("c3v1"). The replacement of the property value is fundamentally different from inserting a new object in the array. A property (as opposed to the containing object) can only be replaced by deleting it and then inserting a new value.
You cannot use a replaceInsert
operation to conditionally insert or replace a property because the insertion content and the replacement content requirements differ. However, you can use separate insert and replace operations within the same patch to achieve the same effect.
For example, the following patch inserts a new property named TARGET if it does not already exists, and replaces its value if it does already exist:
db.documents.patch('/patch/replace-insert.json', pb.insert('/theTop[fn:empty(TARGET)]', 'last-child', { TARGET: 'INSERTED' }), pb.replace('/theTop/TARGET', 'REPLACED') )
The following table illustrates the effect of applying the patch:
For more details on the JSON document model and traversing JSON documents with XPath, see Working With JSON in the Application Developer's Guide.
This example demonstrates how to use the patch remove (delete
) operation to make the following changes to a JSON document:
To run the example, copy the following code into a file, then supply it to the node
command. For example: node remove.js
.
const marklogic = require('marklogic'); const my = require('../my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); // (1) Insert the base document into the database db.documents.write({ uri: '/patch/remove.json', contentType: 'application/json', content: { props: { anyType: [1, 2], objOrLiteral: 'anything', arrayVal: [3, 4] }, arrayItems: { byPos: ['DELETE', 'PRESERVE'], byVal: ['DELETE', 'PRESERVE'], byPropName: [ {DELETE: 5}, {PRESERVE: 6} ], all: ['DELETE1', 'DELETE2'] } } }).result().then(function(response) { // (2) Patch the document const pb = marklogic.patchBuilder; return db.documents.patch(response.documents[0].uri, // Remove a property with any value type pb.remove('/props/node("anyType")'), // Remove a property with atomic or object value type pb.remove('/props/objOrLiteral'), // Remove a property with array value type pb.remove('/props/array-node("arrayVal")'), // Remove all items in an array pb.remove('/arrayItems/all'), // Remove an array item by position pb.remove('/arrayItems/byPos[1]'), // Remove an array item by value pb.remove('/arrayItems/byVal[. = "DELETE"]'), // Remove an object-valued array item by property name pb.remove('/arrayItems/byPropName["DELETE"]') ).result(); }).then(function(response) { // (3) Read the patched document return db.documents.read(response.uri).result(); }).then(function(documents) { console.log(JSON.stringify(documents[0].content, null, 2)); }, function(error) { console.log(error); throw error; });
The following table shows how applying the patch changes the target document.
Note that when removing properties, you must either use a named node step to identify the target property or be aware of the value type. Consider these 3 operations from the example program:
pb.remove('/props/node("anyType")'), pb.remove('/props/objOrLiteral'), pb.remove('/props/array-node("arrayVal")')
The first operation uses a type agnostic XPath expression, /props/node("anyType"). This expression selects any nodes named anyType
, regardless of the type of value. The second operation uses the XPath expression /props/objOrLiteral
, which selects the array item values, rather than the containing array node. That is, this operation applied to the original document will delete the contents of the array, not the arrayVal
property:
pb.remove('/props/arrayVal') ==> "arrayVal": [ ]
The third form, /props/array-node("arrayVal"), deletes the arrayVal
property, but it will only work on properties with array type. Therefore, if you need to delete a property by name without regard to its type, use an XPath expression of the form /path/to/parent/node("propName")
.
For more details on the JSON document model and traversing JSON documents with XPath, see Working With JSON in the Application Developer's Guide.
This example demonstrates patching document metadata. The examples performs the following patch operations:
This example uses the metadata patching helper interfaces on patchBuilder
: patchBuilderCollections
, patchBuilderPermissions
, patchBuilderProperties
, patchBuilderQuality
, and patchBuilder.metadataValues
. You can also patch metadata directly, but using the helper interfaces frees you from understanding the layout details of how MarkLogic stores metadata internally.
To run the example, copy the following code into a file, then supply it to the node
command. For example: node metadata.js
. Note that the parameter passed to db.docments.patch
must include a categories
property that indicates the patch should be applied to metadata rather than content.
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); // (1) Ensure the example document doesn't already exist db.documents.remove('/patch/metadata-example.json') .result().then( function() { // (2) Insert the base document into the database db.documents.write({ uri: '/patch/metadata-example.json', categories: ['content', 'metadata'], contentType: 'application/json', content: { key: 'value' }, collections: [ 'initial1', 'initial2' ], permissions: [ { 'role-name': 'app-user', capabilities: ['read'] } ], properties: { myProp1: 'some-value', myProp2: 'some-other-value' }, metadataValues: { key1: 'value1', key2: 2 } }).result().then(function(response) { // (3) Patch the document const pb = marklogic.patchBuilder; return db.documents.patch({ uri: response.documents[0].uri, categories: [ 'metadata' ], operations: [ // Add a collection pb.collections.add('INSERTED'), // Remove a collection pb.collections.remove('initial1'), // Add a capability to a role pb.permissions.replace( {'role-name': 'app-user', capabilities: ['read','update']}), // Add a property pb.properties.add('myProp3', 'INSERTED'), // Change the quality pb.quality.set(2), // Add new values metadata pb.metadataValues.add('key3', 'INSERTED'), // Remove a values metadata key-value pair pb.metadataValues.remove('key2'), // Replace the value of a values metadata key pb.metadataValues.replace('key1', 'REPLACED') ] }).result(); }).then(function(response) { // (4) Emit the resulting metadata return db.documents.read({ uris: [ response.uri ], categories: [ 'metadata'] }).result(); }).then(function(documents) { console.log('collections: ' + JSON.stringify(documents[0].collections, null, 2)); console.log('permissions: ' + JSON.stringify(documents[0].permissions, null, 2)); console.log('properties: ' + JSON.stringify(documents[0].properties, null, 2)); console.log('quality: ' + JSON.stringify(documents[0].quality, null, 2)); console.log('metadataValues: ' + JSON.stringify(documents[0].metadataValues, null, 2)); }, function(error) { console.log(error); throw error; }); });
The output from the example script should be similar to the following:
collections: [ "initial2", "INSERTED" ] permissions: [ { "role-name": "app-user", "capabilities": [ "read", "update" ] }, { "role-name": "rest-writer", "capabilities": [ "update" ] }, { "role-name": "rest-reader", "capabilities": [ "read" ] } ] properties: { "myProp1": "some-value", "myProp2": "some-other-value", "myProp3": "INSERTED" } quality: 2 metadataValues: { "key1": "REPLACED", "key3": "INSERTED" }
Note that the patch operation that adds the update capability to the app-user
role replaces the entire permission, rather than attempting to insert update as a new value in the capabilities
array. You must operate on each permission as a unit. You cannot modify selected components, such as role-name
or capabilities
.
// Add a capability to a role pb.permissions.replace( {'role-name': 'app-user', capabilities: ['read','update']})
Also, notice that the rest-writer
and rest-reader
roles are assigned to the document, even though they are never explicitly specified. All documents created using the Node.js, Java, or REST Client APIs have these roles by default. For details, see Security Requirements.
The examples in this chapter use marklogic.PatchBuilder
to construct a patch using JSON. You can also construct a raw patch without a builder and pass it to db.documents.patch
as either a JavaScript object or a string.
You must use a raw patch when patching content for XML documents. For details, see Patching XML Documents.
The syntax for raw XML and JSON patches is covered in detail in Partially Updating Document Content or Metadata in the REST Application Developer's Guide.
The following call to db.documents.patch
applies a raw JSON patch that insert an array element:
db.documents.patch(response.documents[0].uri, { patch: [ { insert: [ { context: '/theTop/child[2]', position: 'after', content: 'three' } ] } ] } );
In a raw patch, each type of update (insert, replace, replace-insert, remove) is an array of objects, with each object describing one operation of that type. The output from a call to a patch builder operation represents one such operation.
For example, the insert
operation in the raw patch above is equivalent to the following patch builder call:
pb.insert('/theTop/child[2]', 'after', 'three')
The patch builder functions correspond to raw patch operations as follows:
You must use a raw XML patch when patching content for XML documents. The patch builder only constructs JSON patch operations, and a JSON patch can only be applied to a JSON document.
You can pass a raw XML patch as a string to db.documents.patch
. The syntax for raw XML patches is covered in detail in XML Patch Reference in the REST Application Developer's Guide.
The following example applies a raw XML patch that inserts a new element as a child of another. The patch is passed as a string in the second parameter of db.documents.patch
.
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); // (1) Insert the base document into the database db.documents.write({ uri: '/patch/raw-patch2.xml', contentType: 'application/xml', content: '<parent><child>data</child></parent>' }).result().then(function(response) { // (2) Patch the document return db.documents.patch( response.documents[0].uri, '<rapi:patch xmlns:rapi="http://marklogic.com/rest-api">' + ' <rapi:insert context="/parent" position="last-child">' + ' <new-child>INSERTED</new-child>' + ' </rapi:insert>' + '</rapi:patch>' ).result(); }).then(function(response) { // (3) Emit the resulting document return db.documents.read(response.uri).result(); }).then(function(documents) { console.log(documents[0].content); }, function(error) { console.log(error); throw error; });
The following table shows the document transformation applied by the patch:
Before Update | After Update |
---|---|
<parent> <child>data</data> </parent> |
<parent> <child>data</data> <new-child>INSERTED</new-child> </parent> |
For another Node.js example, see Example: Custom Replacement Constructors. For more details, see Partially Updating Document Content or Metadata in the REST Application Developer's Guide.
You can use builtin or custom replacement constructor functions to generate the content for a patch operation dynamically on MarkLogic Server. The builtin functions support simple arithmetic and string manipulation. For example, you can use a builtin function to increment the current value of numeric data or concatenate strings. For more complex operations, create and install a custom function.
The following topics are covered:
A replacement constructor function is a server-side function that generates content for a patch replace or replace-insert operation.
You can use replacement constructor functions when creating a patch operation using PatchBuilder.replace
and PatchBuilder.replaceInsert
functions, or the replace
and replace-insert
operations of a raw patch. The replacement constructor function call specification takes the place of replacement content supplied by your application. The replacement constructor function call is evaluated on MarkLogic Server and usually generates new content relative to the current value.
For example, you could use the builtin PatchBuilder.multiplyBy
operation to increase the current value of a property by 10%. The following replace operations says For every value selected by the XPath expression /inventory/price
, multiply the current value by 1.1. Notice that the multiplyBy
result is passed to PatchBuilder.replace
instead of new content.
pb.replace('/inventory/price', pb.multiplyBy(1.1))
The builtin replacement constuctors are available as methods of PatchBuilder
. For details, see Using a Builtin Replacement Constructor.
You can also use custom replacement constructors by calling PatchBuilder.library
and PatchBuilder.apply
. Use PatchBuilder.library
to identify a server side XQuery library module that contains your replacement constructor functions, then use PatchBuilder.apply
to create a patch operation that invokes a function in that module.
For example, the following code snippet creates a patch replace operation that doubles the price of every value selected by the XPath expression /inventory/price
. The custom dbl
function is implemented by the XQuery library module installed in the modules database with URI /ext/marklogic/patch/apply/my-lib.xqy
. The dbl
function does not expect any argument values, so there is no additional content supplied to PatchBuilder.apply
.
pb.library('my-lib'), pb.apply('dbl')
For details, see Writing a Custom Replacement Constructor.
For details on using replacement generator functions in a raw patch, see Constructing Replacement Data on the Server in the REST Application Developer's Guide.
The Node.js Client API includes several builtin server-side functions you can use to dynamically generate the content for a replace
or replaceInsert
patch operation. For example, you can use a builtin function to increment the current value of a data item.
The builtin arithmetic functions are equivalent to the XQuery +
, -
, *
, and div
operators, and accept values castable to the same datatypes. That is, numeric, date, dateTime, duration, and Gregorian (xs:gMonth
, xs:gYearMonth
, etc.) values. The operand type combinations are as supported by XQuery; for details, see http://www.w3.org/TR/xquery/#mapping. All other functions expect values castable to string.
The PatchBuilder
interface includes methods corresponding to each builtin function. If you use a raw patch rather than the patch builder, see Constructing Replacement Data on the Server in the REST Application Developer's Guide.
The table below lists the available builtin replacement constructor functions. In the table, $current represents the current value of the target of the replace operation; $arg and $argN represent argument values passed in by the patch. For details, see the Node.js API Reference. The Apply Operation Name column lists the name of the equivalent operation for use with patchBuilder.apply
.
When using a patch builder to construct a call to a builtin or custom replacement constructor, simply pass the expected arguments to the patchBuilder
method.
For example, patchBuilder.concatBetween
concatenates each selected value between two strings supplied as input. Therefore, the concatBetween
method takes the two input string as arguments. The following example concatenates the strings fore and aft on either side of the current value of a selected data item.
pb.replace('/some/path/expr', pb.concatBetween('fore', 'aft'))
In a raw patch, you supply the input argument values in the content
property of the operation. For details, see Using a Replacement Constructor Function in the REST Application Developer's Guide.
Use patchBuilder.apply
and patchBuilder.datatype
to explicitly specify the datatype of an input argument value. You can choose among the types supported by the XML schema; for details, see http://www.w3.org/TR/xmlschema-2/#built-in-datatypes. Omit the namespace prefix on the type name.
For example, the following call explicitly specifies xs:long
as the datatype for the input value to the raw patch operation equivalent of calling patchBuilder.multiplyBy
. For a list of the builtin function names usable with apply, see the table in Using a Builtin Replacement Constructor.
pb.replace('/some/path/expr', p.apply('ml.add', p.datatype('long'), '9223372036854775807'))
Before you can use a custom replacement constructor, the XQuery library module containing your constructor implementation must be installed in the modules database of your REST API instance. For details, see Installing or Updating a Custom Replace Library.
To bring a library of custom replacement constructor functions into scope for a patch operation, include the result of calling PatchBuilder.library
in your patch. The library
operation tells the API how to locate your implementation on MarkLogic Server.
For example, the following library
call indicates that any custom replacement constructor functions used by the patch are in the XQuery module with modules database URI /ext/marklogic/patch/apply/my-replace-lib.xqy
.
pb.patch('/some/doc.json', pb.library('my-replace-lib.xqy'), ...)
You can only use one such library per patch, but you use multiple functions from the same library.
Use PatchBuilder.apply
to construct a call to a function in your library. For example, if my-replae-lib.xqy
contains a function called dbl
, you can use by including the result of the following call in your patch:
pb.patch('/some/doc.json', pb.library('my-replace-lib.xqy'), pb.apply('dbl') /* additional patch operations */)
If the function expects input arguments, include them in the apply
call:
pb.apply('doSomething', 'arg1Val', 'arg2Val')
You are responsible for passing in values of the type(s) expected by the named function. No type checking is performed for you.
This section covers requirements for implementing a custom replacement constructor. For an example implementation, see Example: Custom Replacement Constructors.
You can create your own functions to generate content for the replace
and replace-insert
operations using XQuery. A custom replacement generator function has the following XQuery interface:
declare function module-ns:func-name( $current as node()?, $args as item()* ) as node()*
The target node of the replace (or replace-insert) operation is provided in $current
. If the function is invoked as an insert operation on behalf of a replace-insert, then $current
is empty.
The argument list supplied by the operation is passed through $args
. You are responsible for validating the argument values. If the arguments supplied by the patch operation is a JSON array or a sequence of XML <rapi:value/>
elements, then $args is the result of applying the fn:data function to each value. If an explicit datatype is specified by the patch operation, the cast is applied before invoking your function.
Your function should report errors using fn:error and RESTAPI-SRVEXERR
. For details, see Error Reporting in Extensions and Transformations.
To use a patch builder to construct references to your module, you must adhere to the following namespace and installation URI convention:
http://marklogic.com/patch/apply/
yourModuleName./ext/marklogic/patch/apply/
yourModuleName. This happens automatically if you install your module using db.config.patch.replace.write
.For example, if your library module includes the following module namespace declaration:
module namespace my-lib = "http://marklogic.com/patch/apply/my-lib";
And is installed in the modules database with the following URI:
/ext/marklogic/patch/apply/my-lib.xqy
Then the following patch successfully applies the function dbl
in my-lib.xqy
:
pb.patch('/some/doc.json', pb.library('my-lib.xqy'), pb.replace('/some/path/expr', pb.apply('dbl')), ...)
This shorthand convention does not apply to raw patches. A raw patch must explicitly specify the complete namespace and module path in the replace-library
directive, even if you follow the convention naming convention.
To install or update custom replacement constructor functions, place your function(s) into an XQuery library module and install the module and any dependent libraries in the modules database associated with your REST API instance.
Use config.patch.replace.write
to install your module(s). This function ensures your module is installed using the modules database URI convention expected by PatchBuilder
. You must ensure your module uses the required namespace convention; for details, see Writing a Custom Replacement Constructor.
For example, the following script installs a module into the modules database with the URI /ext/marklogic/patch/apply/my-lib.xqy
. The implementation is read from a file with pathname ./my-lib.xqy
. The module is executable by users with the app-user
role.
const fs = require('fs'); const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); db.config.patch.replace.write('my-lib.xqy', [{'role-name': 'app-user', capabilities: ['execute']}], fs.createReadStream('./my-lib.xqy') ).result(function(response) { console.log('Installed module ' + response.path); }, function(error) { console.log(JSON.stringify(error, null, 2)); });
Note that only the module name portion of the URI ('my-lib.xqy
') is passed in. The remainder of the expected URI is constructed for you.
If you do not specify any permissions when writing the implementation to the modules database, the module is only executable by users with the rest-admin
role.
For an end-to-end example, see Example: Custom Replacement Constructors.
If your library module requires dependent libraries, you can install them using the extlibs
interface. The extlibs
interface allows you to manage modules database assets at both the directory and file level. For details, see Managing Assets in the Modules Database.
Calling db.config.patch.replace.write
is equivalent to calling db.config.extlibs.write
and setting the path parameter to a value that conforms to the PatchBuilder
convention. For example, the following call performs an installation equivalent to the above use of db.config.patch.replace.write
:
db.config.extlibs.write({ path: '/marklogic/patch/apply/my-lib.xqy', permissions: [ {'role-name': 'app-user', capabilities: ['execute']} ], contentType: 'application/xquery', source: fs.createReadStream('./my-lib.xqy') })
To remove a module that contains custom replacement constructor functions, use db.config.patch.replace.remove
. For example, the following call removes the module my-lib.xqy
that was previously installed using db.config.patch.replace.write
:
db.config.patch.replace.remove('my-lib.xqy');
This example walks you through installing and using an XQuery library module that contains custom replacement constructor functions.
The example installs an XQuery library module containing 2 custom replacement constructors, named dbl
and min
. The dbl
function creates a new node whose value is double that of the original input; it accepts no additional arguments. The min
function creates a new node whose value in the minimum of the current value and the values passed in as additional arguments.
For simplicity, this example skips most of the input data validation that a production implementation should include. For example, the min
function accepts JSON number nodes and XML elements as input, but it does not allow for boolean, text, or date input. Nor does min
perform any validation on the additional input args.
After installing the replacement content generators, a patch is applied to double the value of oranges (from 10 to 20) and select the lowest price for pears.
Use the following procedure to set up the files used by the example:
my-lib.xqy
. This file contains the implementation of the custom replacement constructors.xquery version "1.0-ml"; module namespace my-lib = "http://marklogic.com/patch/apply/my-lib"; (: Double the value of a node :) declare function my-lib:dbl( $current as node()?, $args as item()* ) as node()* { if ($current/data() castable as xs:decimal) then let $new-value := xs:decimal($current) * 2 return typeswitch($current) case number-node() (: JSON :) return number-node {$new-value} case element() (: XML :) return element {fn:node-name($current)} {$new-value} default return fn:error((), "RESTAPI-SRVEXERR", ("400", "Bad Request", fn:concat("Not an element or number node: ", xdmp:path($current)) )) else fn:error((), "RESTAPI-SRVEXERR", ("400", "Bad Request", fn:concat("Non-decimal data: ", $current) )) }; (: Find the minimum value in a sequence of value composed of :) (: the current node and a set of input values. :) declare function my-lib:min( $current as node()?, $args as item()* ) as node()* { if ($current/data() castable as xs:decimal) then let $new-value := fn:min(($current, $args)) return typeswitch($current) case element() (: XML :) return element {fn:node-name($current)} {$new-value} case number-node() (: JSON :) return number-node {$new-value} default return fn:error((), "RESTAPI-SRVEXERR", ("400", "Bad Request", fn:concat("Not an element or number node: ", xdmp:path($current)) )) else fn:error((), "RESTAPI-SRVEXERR", ("400", "Bad Request", fn:concat("Non-decimal data: ", $current))) };
install-udf.js
. This script installs the above XQuery module. For demonstration purposes, the module is installed such that the role app-user
has permission to execute the contained functions.const fs = require('fs'); const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); db.config.patch.replace.write('my-lib.xqy', [ {'role-name': 'app-user', capabilities: ['execute']} ], fs.createReadStream('./my-lib.xqy') ).result(function(response) { console.log('Installed module ' + response.path); }, function(error) { console.log(JSON.stringify(error, null, 2)); });
udf.js
. This script inserts a base document into the database and applies a patch that uses the dbl
and min
replacement content constructors.const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); // (1) Insert the base document into the database db.documents.write({ uri: '/patch/udfs.json', contentType: 'application/json', content: { inventory: [ {name: 'orange', price: 10}, {name: 'apple', price: 15}, {name: 'pear', price: 20} ] } }).result().then(function(response) { // (2) Patch the document const pb = marklogic.patchBuilder; return db.documents.patch(response.documents[0].uri, pb.library('my-lib.xqy'), pb.replace('/inventory[name eq "orange"]/price', pb.apply('dbl')), pb.replace('/inventory[name eq "pear"]/price', pb.apply('min', 18, 21)) ).result(); }).then(function(response) { // (3) Emit the resulting document return db.documents.read(response.uri).result(); }).then(function(documents) { console.log(JSON.stringify(documents[0].content, null, 2)); }, function(error) { console.log(error); throw error; });
Use the following procedure to run the example. This procedure installs the replacement content constructor module, inserts a base document in the database, patches the document, and displays the update document contents.
node install-udf.js
dbl
and min
functions.node udf.js
You should see output similar to the following:
{ "inventory": [ { "name": "orange", "price": 20 }, { "name": "apple", "price": 15 }, { "name": "pear", "price": 18 } ] }
The price of oranges is doubled, from 10 to 20 by the dbl
function, due to the following patch operation:
pb.replace('/inventory[name eq "orange"]/price', pb.apply('dbl'))
The value of pears is lowered from 20 to 18 by the min
function, due to the following patch operation:
pb.replace('/inventory[name eq "pear"]/price', pb.apply('min', 18, 21))
The XPath expression used in each patch operation selects a number node (price
) in the target document and the replacement content constructor functions construct a new number node:
typeswitch($current) case element() (: XML :) return element {fn:node-name($current)} {$new-value} case number-node() (: JSON :) return number-node {$new-value}
You cannot simply return the new value. You must return a complete replacement node. To learn more about the JSON document model and JSON node constructors, see Working With JSON in the Application Developer's Guide.
The typeswitch on the node type of $current
also enables the example replacement constructors to work with XML input. Run the following script to apply an equivalent to an XML document, using the previously installed dbl
and min
replacement content constructors. As described in Patching XML Documents, you must use a raw patch rather than a builder when working with XML documents.
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); // (1) Insert the base document into the database db.documents.write({ uri: '/patch/udf.xml', contentType: 'application/xml', content: '<inventory>' + '<item>' + '<name>orange</name>' + '<price>10</price>' + '</item>' + '<item>' + '<name>apple</name>' + '<price>15</price>' + '</item>' + '<item>' + '<name>pear</name>' + '<price>20</price>' + '</item>' + '</inventory>' }).result().then(function(response) { // (2) Patch the document return db.documents.patch( response.documents[0].uri, '<rapi:patch xmlns:rapi="http://marklogic.com/rest-api">' + '<rapi:replace-library ' + 'at="/ext/marklogic/patch/apply/my-lib.xqy" ' + 'ns="http://marklogic.com/patch/apply/my-lib" />' + '<rapi:replace ' + 'select="/inventory/item[name eq \'orange\']/price" ' + 'apply="dbl" />' + '<rapi:replace ' + 'select="/inventory/item[name eq \'pear\']/price" ' + 'apply="min">' + '<rapi:value>18</rapi:value>' + '<rapi:value>21</rapi:value>' + '</rapi:replace>' + '</rapi:patch>' ).result(); }).then(function(response) { // (3) Emit the resulting document return db.documents.read(response.uri).result(); }).then(function(documents) { console.log(documents[0].content); }, function(error) { console.log(error); throw error; });
Notice that in a raw patch, you must explicitly specify the module path and module namespace in the replace-library
directive:
<rapi:replace-library at="/ext/marklogic/patch/apply/my-lib.xqy" ns="http://marklogic.com/patch/apply/my-lib" />'
When you use PatchBuilder
to construct a JSON patch, the call to PatchBuilder.library
fills these details in for you, assuming you follow the installation path conventions described in Installing or Updating a Custom Replace Library.
The db.config.patch.replace interface offers additional methods for managing library modules containing replacement content constructor functions, including the following:
db.config.patch.replace.read
- Retrieve the implementation of a replace library. This returns a module installed using db.config.patch.replace.write
.db.config.patch.replace.list
- Retrieve a list of all installed replace library modules.For details, see Node.js Client API Reference.