This chapter discusses the following topics related to using the Node.js Client API to create, read, update and delete documents and metadata:
The Node.js Client API exposes functions for creating, reading, updating and deleting documents and document metadata.
Most document manipulation functions are provided through the DatabaseClient.documents
interface. For example, the following code snippet reads a document by creating a database client object and invoking its documents.read()
method:
const ml = require('marklogic'); const db = ml.createDatabaseClient({'user':'me','password':'mypwd'}); db.documents.read('/doc/example.json'). ...;
The DatabaseClient
interface includes read and write operations for binding JavaScript objects to Database documents, such as DatabaseClient.read
and DatabaseClient.createCollection
. Generally, these operations provide a simpler but less powerful capability than the equivalent method of DatabaseClient.documents
. For example, you cannot specify a transaction id or read document metadata using DatabaseClient.read
.
Several of the DatabaseClient.documents
interfaces accept or return document descriptors that encapsulate data such as the URI and document content. For details, see Input Document Descriptors and Document Descriptor.
When loading data into the database, the DatabaseClient.documents.write
method provides the most control and richest feature set. However, if you do not need that level control, one of the other interfaces may be simpler to use. For example, if you just want to save JavaScript domain objects in the database, DatabaseClient.createCollection
enables you to do so without creating document descriptors or constructing document URIs.
By default, each Node.js Client API call that interacts with the database represents a complete transactional operation. For example, if you use a single call to DatabaseClient.Documents.write
to update multiple documents, then all the updates are applied as part of the same transaction, and the transaction is committed when the operation completes on the server. You can use multi-statement transactions to have multiple client-side operations span a single transaction. For details, see Managing Transactions.
The following table lists some common tasks related to writing to the databaes, along with the method best suited for the completing the task. For a complete list of interfaces, see the Node.js API Reference.
If you want to | Then use |
---|---|
Save a collection of JavaScript objects in the database as JSON documents. |
For details, see Managing Collections of Objects and Documents. |
Update a collection of JavaScript objects created using DatabaseClient.createCollection . |
For details, see Managing Collections of Objects and Documents. |
Insert or update a collection of documents by URI. |
For details, see Managing Collections of Objects and Documents. |
Insert or update document metadata, with or without accompanying content. |
For details, see Inserting or Updating Metadata for One Document. |
Insert or update documents and/or metadata in the context of a multi-statement transaction. |
For details, see Loading Documents into the Database. |
Apply a content transformation while loading documents. |
For details, see Loading Documents into the Database and Transforming Content During Ingestion. |
Update a portion of a document or its metadata, rather than replacing the entire document. |
For details, see Patching Document Content or Metadata. |
The following table lists some common tasks related to reading data from the database, along with the functions best suited for each task. For a complete list of interfaces, see the Node.js API Reference.
If you want to | Then use |
---|---|
Read the contents of one or more documents by URI. |
|
Restore a collection of JavaScript objects previously saved in the in database using DatabaseClient.createCollection . |
For details, see Querying Documents and Metadata and Managing Collections of Objects and Documents. |
Read one or more documents and/or metadata by URI. |
For details, see Reading Documents from the Database. |
Read the contents of one or more documents and/or metadata by URI and apply a read transformation. |
For details, see Reading Documents from the Database and Transforming Content During Retrieval. |
Read one or more documents and/or metadata by URI in the context of a multi-statement transaction. |
For details, see Reading Documents from the Database. |
Read documents and/or metadata that match a query. |
For details, see Querying Documents and Metadata. |
Query and analyze values in lexicons and range indexes. For details, see Querying Lexicons and Range Indexes. |
|
Read a semantic graph from the database. For details, see Node.js API Reference. |
|
Use the DatabaseClient.documents.write
or DatabaseClient.documents.createWriteStream
methods to insert document content and metadata into the database. The stream interface is primarily intended for writing large documents such as binaries.
Use DatabaseClient.documents.write
to insert or update whole documents and/or metadata. To update only a portion of a document or its metadata, use DatabaseClient.documents.patch
; for details, see Patching Document Content or Metadata.
The primary input to the write
function is one or more document descriptors. Each descriptor encapsulates a document URI with the content and/or metadata to be written. For details, see Input Document Descriptors.
For example, the following call writes a single document with the URI /doc/example.json
:
const db = marklogic.createDatabaseClient(...); db.documents.write( { uri: '/doc/example.json', contentType: 'application/json', content: { some: 'data' } } );
Write multiple documents by passing in multiple desriptors. For example, the following call writes 2 documents:
db.documents.write( { uri: '/doc/example1.json', contentType: 'application/json', content: { data: 'one' } }, { uri: '/doc/example2.json', contentType: 'application/json', content: { data: 'two' } }, );
Descriptors can be passed as individual parameters, in an array, or in an encapsulating call object. For details, see Calling Convention.
You can take action based on the success or failure of a write operation by calling the result()
function on the return value. For details, see Supported Result Handling Techniques.
For example, the following code snippet prints an error message to the console if the write fails:
db.documents.write( { uri: '/doc/example.json', contentType: 'application/json', content: { some: 'data' } } ).result(null, function(error) { console.log( })
Each document to be written is described by a document descriptor. The document descriptor must include a URI and either content, metadata, or both content and metadata. For details, see Document Descriptor.
For example, the following is a document descriptor for a document with URI /doc/example.json
. The document contents are expressed as a JavaScript object containing a single property.
{ uri: '/doc/example.json', content: {'key': 'value'} }
The content
property in a document descriptor can be an object, a string, a Buffer
, or a ReadableStream
.
Metadata is expressed as properties of a document descriptor when it applies to a specific document. Metadata is expressed as properties of a call object when it applies to multiple documents; for details, see Inserting or Updating Metadata for One Document.
For example, the following document descriptor includes collection and document quality metadata for the document with URI /doc/example.json
:
{ uri: '/doc/example.json', content: {'key': 'value'}, collections: [ 'collection1', 'collection2' ], quality: 2 }
You must pass at least one document descriptor to DatabaseClient.documents.write
. You can also include additional properties such as a transform name or a transaction id. The parameters passed to documents.write
can take one of the following forms:
db.documents.write(desc1, desc2,...)
. db.documents.write([desc1, desc2, ...])
.db.documents.write({documents: [desc1, desc2, ...], txid: ..., ...})
.The following calls are equivalent:
// passing document descriptors as parameters db.documents.write( {uri: '/doc/example1.json', content: {...}}, {uri: '/doc/example2.json', content: {...}} ); // passing document descriptors in an array db.documents.write([ {uri: '/doc/example1.json', content: {...}}, {uri: '/doc/example1.json', content: {...}} ]); // passing document descriptors in a call object db.documents.write({ documents: [ {uri: '/doc/example1.json', content: {...}}, {uri: '/doc/example2.json', content: {...}} ], additional optional properties });
The additional optional properties can include a transform specification, transaction id, or temporal collection name; for details, see the Node.js API Reference. You can always specify such properties as properties of a call object.
For example, the following call includes a transaction id (txid
) as an additional property of the call object:
// passing a transaction id as a call object property db.documents.write({ documents: [ {uri: '/doc/example1.json', content: {...}}, {uri: '/doc/example2.json', content: {...}} ], txid: '1234567890' });
For convenience, if and only if there is a single document descriptor, the additional optional properties can be passed as properties of the document descriptors, as an alternative to using a call object. For example, the following call includes a transaction id inside the single document descriptor:
// passing a transaction id as a document descriptor property db.documents.write( { uri: '/doc/example1.json', content: {...}, txid: '1234567890' } );
This example inserts a single document into the database using DatabaseClient.documents.write
.
The document to load is identified by a document descriptor. The following document descriptor describes a JSON document with the URI /doc/example.json
. The document content is expressed as a JavaScript object here, but it can also be a string, Buffer
, or ReadableStream
.
{ uri: '/doc/example.json', contentType: 'application/json', content: { some: 'data' } })
The code below creates a database client and calls DatabaseClient.documents.write
to load the document. The example checks for a write failure by calling the result
function and passing in an error handler. In this example, no action is taken if the write succeeds, so null
is passed as the first parameter to result()
.
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); db.documents.write( { uri: '/doc/example.json', contentType: 'application/json', content: { some: 'data' } }) .result(null, function(error) { console.log(JSON.stringify(error)); });
For additional examples, see examples/before-load.js
and examples/write-remove.js
in the node-client-api
source directory.
To include metadata, add metadata properties to the document descriptor. For example, to add the document to a collection, you can add a collections
property to the descriptor:
db.documents.write( { uri: '/doc/example.json', contentType: 'application/json', content: { some: 'data' }, collections: [ 'collection1', 'collection2' ] })
You can include optional additional parameters such as a transaction id or a write transform by using a call object. For details, see Calling Convention.
This example builds on Example: Loading A Single Document to insert multiple documents into the database using DatabaseClient.documents.write
.
To insert or update multiple documents in a single request to MarkLogic Server, pass multiple document descriptors to DatabaseClient.documents.write
.
The following code inserts 2 documents into the database with URIs /doc/example1.json
and /doc/example2.json
:
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); db.documents.write( { uri: '/doc/example1.json', contentType: 'application/json', content: { data: 'one' } }, { uri: '/doc/example2.json', contentType: 'application/json', content: { data: 'two' } } ).result(null, function(error) { console.log(JSON.stringify(error)); });
A multi-document write returns an object that contains a descriptor for each document written. The descriptor includes the URI, the MIME type the contents were interpreted as, and whether the write updated content, metadata, or both.
For example, the return value of the above call is as follows:
{ documents: [ { uri: '/doc/example1.json', mime-type: 'application/json', category: ['metadata','content'] },{ uri: '/doc/example2.json', mime-type: 'application/json', category: ['metadata','content'] } ]}
Note that the category
property indicates both content and metadata were updated even though no metadata was explicitly specified. This is because system default metadata values were implicitly assigned to the documents.
To include metadata for a document when you load multiple documents, include document-specific metadata in the descriptor for that document. To specify metadata that applies to multiple documents include a metadata descriptor in the parameter list or documents property.
For example, to add the two documents to the collection examples, add a metadata descriptor before the document descriptors, as shown below. The order of the descriptors matters as the set of descriptors is processed in the order it appears. A metadata descriptor only affects document descriptors that appear after it in the parameter list or documents
array.
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); db.documents.write({ documents: [ { contentType: 'application/json', collections: [ 'examples' ] }, { uri: '/doc/example1.json', contentType: 'application/json', content: { data: 'one' } }, { uri: '/doc/example2.json', contentType: 'application/json', content: { data: 'two' } } ] }).result(null, function(error) { console.log(JSON.stringify(error)); });
To insert or update metadata for a specific document, include one or more metadata properties in the document descriptor passed to DatabaseClient.documents.write
. To insert or update the same metadata for multiple documents, you can include a metadata descriptor in a multi-document write; for details, see Working with Metadata.
When setting permissions, at least one update permission must be included.
Metadata is replaced on update, not merged. For example, if your document descriptor includes a collections
property, then calling DatabaseClient.documents.write
replaces all existing collection associations for the document.
The following example inserts a document with URI /doc/example.json
and adds it to the collections examples and metadata-examples. If the document already exists and is part of other collections, it is removed from those collections.
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); db.documents.write( { uri: '/doc/example.json', collections: ['examples', 'metadata-examples'], contentType: 'application/json', content: { some: 'data' } }) .result(null, function(error) { console.log(JSON.stringify(error)); });
To insert or update just metadata for a document, omit the content
property. For example, the following code sets the quality to 2 and the collections to some-collection, without changing the document contents:
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); db.documents.write( { uri: '/doc/example.json', collections: ['some-collection'], quality: 2, }) .result(null, function(error) { console.log(JSON.stringify(error)); });
You can have document URIs automatically generated during insertion by replacing the uri
property in your document descriptor with an extension
property, as described below.
You can only use this feature to create new documents. To update an existing document, you must know its URI.
To use this feature, construct a document descriptor with the following characteristics:
uri
property.extension
property that specifies the generated URI extension, such as xml or json. Do not include a dot (.) prefix. That is, specify json, not .json.directory
property that specifies a database directory prefix for the generated URI. The directory prefix must end in a forward slash (/).The following example inserts a document into the database with a URI of the form /my/directory/
auto-generated.json
.
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); db.documents.write( { extension: 'json', directory: '/my/directory/', content: { some: 'data' }, contentType: 'application/json' } ).result( function(response) { console.log('Loaded ' + response.documents[0].uri); }, function(error) { console.log(JSON.stringify(error)); } );
Running the above script results in output similar to the following upon success:
Loaded /my/directory/16764526972136717799.json
You can transform content during ingestion by applying a custom write transform. A transform is server-side XQuery, JavaScript, or XSLT that you install in the modules database associated with your REST API instance. You can install transfoms using the config.transforms
functions. This topic describes how to apply a transform during ingestion. For more details and examples, see Working with Content Transformations.
To apply a transform when creating or updating documents, call documents.write
with a call object that includes the transform
property. The transform
property encapsulates the transform name and any parameters expected by the transform. The transform
property has the following form:
transform: [transformName, {param1: value, param2: value, ...}]
For example, the following code snippet applies a transform installed under the name my-transform
and passes in values for 2 parameters:
db.documents.write({ documents: [ {uri: '/doc/example1.json', content: {...}}, {uri: '/doc/example2.json', content: {...}} ], transform: [ 'my-transform', { my-first-param: 'value', my-second-param: 42 } ] });
As a convenience, you can embed the transform
property in the document descriptor when inserting or updating just one document. For example:
db.documents.write({ uri: '/doc/example1.json', content: {...}}, transform: [ 'my-transform', { my-first-param: 'value', my-second-param: 42 } ] });
Use DatabaseClient.documents.read
to read one or more documents and/or metadata from the database. This section covers the following topics:
To retrieve the contents of a document from the database using its URI, use DatabaseClient.documents.read
or DatabaseClient.read
. You can also retrieve the contents and metadata of documents that match a query; for details, see Querying Documents and Metadata.
DatabaseClient.read
and DatabaseClient.Documents.read
both enable you to read documents by URI, but they differ in power and complexity. If you need to read metadata, use a multi-statement transaction, apply a read transformation, or access information such as the content-type, use DatabaseClient.Documents.read
. DatabaseClient.read
accepts only a list of URIs as input and returns just the contents of the requested documents.
The two functions return different output. DatabaseClient.Documents.read
returns an array of document descriptors, instead of just the content. DatabaseClient.read
returns an array containing only the content of each document, in the same order as the input URI list. You can process the output of both functions using a callback, Promise, or Stream; for details, see Supported Result Handling Techniques.
For example, the following code uses DatabaseClient.Documents.read
to read the document with URI /doc/example1.json
and processes the output using the Promise returned by the result
method.
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); db.documents.read('/doc/example1.json') .result( function(documents) { documents.forEach(function(document) { console.log(JSON.stringify(document)); }); }, function(error) { console.log(JSON.stringify(error, null, 2)); });
The complete descriptor returned by result
looks like the following. The returned array contains a document descriptor item for each document returned. In this case, there is only a single document.
{ "partType":"attachment", "uri":"/doc/example1.json", "category":"content", "format":"json", "contentType":"application/json", "contentLength":"14", "content":{"data":"one"} }
If you read the same document using DatabaseClient.read
, you get the output shown below. Notice that it is just the content, not a descriptor.
db.read('/doc/example1.json').result(...); ==> {"data":"one"}
You can read multiple documents by passing in multiple URIs. The following example reads two documents and uses the Stream pattern to process the results. The data handler receives a document descriptor as input.
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); db.documents.read('/doc/example1.json', '/doc/example2.json') .stream().on('data', function(document) { console.log('Read ' + document.uri); }). on('end', function() { console.log('finished'); }). on('error', function(error) { console.log(JSON.stringify(error)); done(); });
You can request metadata by passing a call object parameter to Documents.read
and including a categories
property that specifies which document parts to return. For details, see Retrieving Metadata About a Document.
When calling Documents.read
, you can use a read transform to apply server-side transformations to the content before the response is constructed. For details, see Transforming Content During Retrieval.
To perform reads across multiple operations with a consistent view of the database state, pass a Timestamp object to Documents.read. For more details, see Performing Point-in-Time Operations.
To retrieve metadata when reading documents by URI, pass a call object to DatabaseClient.documents.read
that includes a categories
property. You can retrieve all metadata (category value 'metadata'
) or a subset of metadata (category values 'collections'
, 'permissions'
, 'properties'
, 'metadataValues'
, and 'quality
'). To retrieve both content and metadata, include the category value 'content'
.
The metadataValues
metadata category represents simple key-value metadata, sometimes called metadata fields. For more details, see Metadata Fields in the Administrator's Guide.
For example, the following code retrieves all metadata about the document /doc/example.json
, but not the contents.
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); db.documents.read({ uris: ['/doc/example.json'], categories: ['metadata'] }).result( function(documents) { for (const i in documents) console.log('Read metadata for ' + documents[i].uri); }, function(error) { console.log(JSON.stringify(error)); } );
The result is a document descriptor that includes all metadata properties for the requested document, as shown below. For details on metadata categories and formats, see Working with Metadata.
[{ "partType":"attachment", "uri":"/doc/example.json", "category":"metadata", "format":"json", "contentType":"application/json", "contentLength":"168", "collections":[], "permissions":[ {"role-name":"rest-writer","capabilities":["update"]}, {"role-name":"rest-reader","capabilities":["read"]} ], "properties":{}, "quality":0 }]
The following example retrieves content and collections metadata about 2 JSON documents:
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); db.documents.read({ uris: ['/doc/example1.json', '/doc/example2.json'], categories: ['collections', 'content'] }).stream(). on('data', function(document) { console.log('Collections for ' + document.uri + ': ' + JSON.stringify(document.collections)); }). on('end', function() { console.log('finished'); }). on('error', function(error) { console.log(JSON.stringify(error)); });
The result is a document descriptor for each document that includes a collections
and a content
property:
{ "partType" : "attachment", "uri" : "/doc/example2.json", "category" : "content", "format" : "json", "contentType" : "application/json", "contentLength" : "14", "collections" : ["collection1", "collection2"], "content" : {"data":"two"} }
The example demonstrates reading content and metadata for a document.
The script below first writes an example document to the database that is in the examples collection and has document quality 2. Then, the document and metadata are read back in a single operation by including both 'content
' and 'metadata
' in the categories
property of the read document descriptor. You can also specify specific metadata properties, such as 'collections' or 'permissions'.
To run the example, copy the script below to a file and run it using the node command.
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); // (1) Seed the database with an example document that has custom metadata db.documents.write({ uri: '/read/example.json', contentType: 'application/json', collections: ['examples'], metadataValues: {key1: 'val1', key2: 2}, quality: 2, content: { some: 'data' } }).result().then(function(response) { // (2) Read back the content and metadata return db.documents.read({ uris: [response.documents[0].uri], categories: ['content', 'metadata'] }).result(); }).then(function(documents) { // Emit the read results console.log('CONTENT: ' + JSON.stringify(documents[0].content)); console.log('COLLECTIONS: ' + JSON.stringify(documents[0].collections)); 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)); });
The script produces output similar to the following:
CONTENT: {"some": "data"} COLLECTIONS: ["examples"] PERMISSIONS: [ { "role-name": "rest-writer", "capabilities": [ "update" ] }, { "role-name": "rest-reader", "capabilities": [ "read" ] } ] PROPERTIES: {} QUALITY: 2 METADATAVALUES: { "key2": 2, "key1": "val1" }
You can apply custom server-side transforms to a document before returning the content to the requestor. A transform is a JavaScript module, XQuery module, or XSLT stylesheet that you install in your REST API instance using the DatabaseClient.config.transforms.write
function. For details, see Working with Content Transformations.
You can configure a default read transform that is automatically applied whenever a document is retrieved. You can also specify a per-request transform by including a transform
property in the call object passed to most read operations. If there is both a default transform and a per-request transform, the transforms are chained together, with the default transform running first. Thus, the output of the default transform is the input to the per-request transform, as shown in the following diagram:
To configure a default transform, set the document-transform-out
configuration parameter of the REST API instance. Instance-wide configuration parameters are set using the DatabaseClient.config.serverprops.write
function. For details, see Configuring Instance Properties.
To specify a per-request transform, use the call object form of a read operation such as DatabaseClient.documents.read
and include a transform
property. The value of the transform
property is an array where the first item is the transform name and the optional additional items specify the name and value of parameters expected by the transform. That is, the transform
property has the following form:
transform: [transformName, {param1: value, param2: value, ...}]
The following example applies a transform installed under the name example that expects a single parameter named reviewer. For a complete example, see Example: Read, Write, and Query Transforms.
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); db.documents.read({ uris: ['/doc/example.json'], transform: ['example', {reviewer: 'me'}] }).result( function(documents) { for (const i in documents) console.log('Document ' + documents[i].uri + ': '); console.log(JSON.stringify(documents[i].content)); } );
You can use the Node.js Client API to remove documents by URI, collection or directory.
To remove one or more documents by URI, use DatabaseClient.remove
or DatabaseClient.documents.remove
. Both functions enable you to remove documents by URI, but DatabaseClient.remove
offers a simpler, more limited, interface. You can also remove multiple documents by collection or directory; for details, see Removing Sets of Documents.
Removing a document also removes the associated metadata. Removing a binary document with extracted metadata stored in a separate XHTML document also deletes the properties document; for details, see Working with Binary Documents.
The following example removes the documents /doc/example1.json
and /docs/example2.json
.
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); db.documents.remove('/doc/example1.json','/doc/example2.json').result( function(response) { console.log(JSON.stringify(response)); } );
The response contains the document URIs and a success indicator. For example, the above program produces the following output:
{ "uris":["/doc/example1.json", "/doc/example2.json"], "removed":true }
When you remove documents with DatabaseClient.remove
, the response only includes the URIs. For example:
db.remove('/doc/example1.json') ==> ['/doc/example1.json'] db.remove('/doc/example1.json','/doc/example2.json') ==> ['/doc/example1.json','/doc/example2.json']
For additional examples, see the examples and tests in the node-client-api
sources available on GitHub at http://github.com/marklogic/node-client-api.
Attempting to remove a document that does not exist produces the same output as successfully removing a document that exists.
When removing multiple documents, you can specify the URIs as individual parameters (if the call has no other parameters), an array of URIs, or an object with a uris
property whose value is the URIs. When removing multiple documents, the whole batch fails if there is an error removing any one of the documents.
You can supply additional parameters to DatabaseClient.documents.remove
, such as a transaction id and temporal collection information. For details, see the Node.js API Reference.
You can use DatabaseClient.documents.removeAll
to remove all documents in a collection or all documents in a database directory.
To remove documents by collection, use the following form:
db.documents.removeAll({collection:..., other-properties...}
To remove documents by directory, use the following form:
db.documents.removeAll({directory:..., other-properties...}
The optional other-properties can include a transaction id. For details, see the Node.js API Reference.
Removing all documents in a collection or directory requires the rest-writer
role or equivalent privileges.
When removing documents by directory, the directory name must include a trailing slash (/).
The following example removes all documents in the collection /countries:
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); db.documents.removeAll({collection: '/countries'}).result( function(response) { console.log(JSON.stringify(response)); } ); ==> {"exists":false,"collection":"/countries"}
The following example removes all documents in the directory /doc/:
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); db.documents.removeAll({directory: '/doc/'}).result( function(response) { console.log(JSON.stringify(response)); } ); ==> {"exists":false,"directory":"/doc/"}
You can also include a txid
property in the call object passed to DatabaseClient.documents.removeAll
to specify a transaction in which to perform the deletion. For example:
db.documents.removeAll({directory: '/doc/', txid: '1234567890'})
For additional examples, see the examples and tests in the node-client-api
sources available on GitHub at http://github.com/marklogic/node-client-api.
To remove all documents in the database, call DatabaseClient.documents.removeAll
and include an all property with value true in the call object. For example:
db.documents.removeAll({all: true})
Removing all documents in the database requires the rest-admin
or equivalent privileges.
There is no confirmation or other safety net when you clear the database in this way. Creating a backup is recommended.
The following example removes all documents in the database:
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); db.documents.removeAll({all: true}).result( function(response) { console.log(JSON.stringify(response)); } ); ==> {"exists":false,"allDocuments":true}
You can also include a txid
property in the call object passed to DatabaseClient.documents.removeAll
to specify a transaction in which to perform the deletion. For example:
db.documents.removeAll({all: true, txid: '1234567890'})
You can easily manage a collection of JavaScript objects in the database using the following operations. The objects must be serializable as JSON.
DatabaseClient.createCollection
: Store a collection of JavaScript objects in the database as JSON documents with auto-generated URIs. The objects must be serializable as JSON.DatabaseClient.read
: Read one or more JavaScript objects from the database by URI. Unlike DatabaseClient.documents.read
, this method does not return document descriptors. Rather, it returns just the document content. Documents are returned in the same order as the input URIs.DatabaseClient.documents.query
: Restore objects by finding all documents in the collection, or search your collection.DatabaseClient.writeCollection
: Update objects or other documents by URI and collection name. Use this method rather than creatCollection
to update objects in the collection because createCollection
always creates a new document for each object.DatabaseClient.removeCollection
: Remove objects or other documents by collection name.Calling createCollection
is additive. That is, documents (objects) already in the collection remain in the collection if you call createCollection
multiple times on the same collection. However, note that createCollection
generates a new document for each object every time you call it, so calling it twice on the same object creates a new object rather than overwriting the previous one. To update objects/documents in the collection, use DatabaseClient.writeCollection
.
If you need more control over your documents, use DatabaseClient.documents.write
. For example, when you use createCollection
you cannot exercise any control over the document URIs, include metadata such as permissions or document properties, or specify a transaction id.
To learn more about searching documents with DatabaseClient.documents.query
, see Querying Documents and Metadata.
The example script below does the following:
kind='cat'
.To try the example, copy the following script to a file and run it with the node
command. The database connection information is encapsulated in my-connection.js
, as described in Using the Examples in This Guide.
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); const qb = marklogic.queryBuilder; // The collection of objects to persist const pets = [ { name: 'fluffy', kind: 'cat' }, { name: 'fido', kind: 'dog' }, { name: 'flipper', kind: 'fish' }, { name: 'flo', kind: 'rodent' } ]; const collName = 'pets'; // (1) Write the objects to the database db.createCollection(collName, pets).result() .then(function(uris) { console.log('Saved ' + uris.length + ' objects with URIs:'); console.log(uris); // (2) Read back all objects in the collection return db.documents.query( qb.where(qb.collection(collName)) ).result(); }, function(error) { console.log(JSON.stringify(error)); }).then( function(documents) { console.log('\nFound ' + documents.length + ' documents:'); documents.forEach( function(document) { console.log(document.content); }); // (3) Find the cats in the collection return db.documents.query( qb.where(qb.collection(collName), qb.value('kind', 'cat')) ).result(); }).then( function(documents) { console.log('\nFound the following cats:'); documents.forEach( function(document) { console.log(' ' + document.content.name); }); // (4) Remove the collection from the database db.removeCollection(collName); });
Running the script produces output similar to the following:
Saved 4 objects with URIs: [ '/717293155828968327.json', '/5648624202818659648.json', '/4552049485172923004.json', '/16796864305170577329.json' ] Found 4 documents: { name: 'fido', kind: 'dog' } { name: 'flipper', kind: 'fish' } { name: 'flo', kind: 'rodent' } { name: 'fluffy', kind: 'cat' } Found the following cats: fluffy
Use DatabaseClient.documents.probe
or DatabaseClient.probe
to test for the existence of a document in the database or retrieve a document identifier without fetching the document (when content versioning is enabled).
The following example probes for the existence of the document /doc/example.json
:
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); db.documents.probe('/doc/example.json').result( function(response) { if (response.exists) { console.log(response.uri + ' exists'); } else { console.log(response.uri + 'does not exist'); } } );
The return value is a document descriptor that includes a boolean-valued exists
property. If content versioning is enabled on the REST instance, then the response also includes a versionId
property. For example, with content versioning enabled, the above example produces the following output:
{ contentType: "application/json", versionId: "14115045710437450", format: "json", uri: "/doc/example.json", exists: true }
For more information about content versioning, see Conditional Updates Using Optimistic Locking.
An application using optimistic locking creates a document only when the document does not exist and updates or deletes a document only when the document has not changed since this application last changed it. However, optimistic locking does not actually involve placing a lock on document.
Optimistic locking is useful in environments where integrity is important, but contention is rare enough that it is useful to minimize server load by avoiding unnecessary multi-statement transactions.
This section covers the following topics:
Consider an application that reads a document, makes modifications, and then updates the document in the database with the changes. The traditional approach to ensuring document integrity is to perform the read, modification, and update in a multi-statement transaction. This holds a lock on the document from the point when the document is read until the update is committed. However, this pessimistic locking blocks access to the document and incurs more overhead on the App Server.
With optimistic locking, the application does not hold a lock on a document between read and update. Instead, the application saves the document state on read, and then checks for changes at the time of update. The update fails if the document has changed between read and update. This is a conditional update.
Optimistic locking is useful in environments where integrity is important, but contention is rare enough that it is useful to minimize server load by avoiding unnecessary multi-statement transactions.
The Node.js Client API uses content versioning to implement optimistic locking. When content versioning is enabled on your REST API instance, MarkLogic Server associates an opaque version id with each document when it is created or updated. The version id changes each time you update the document. The version id is returned when you read a document, and you can pass it back in an update or delete operation to test for changes prior to commit.
Enabling content versioning does not implement document versioning. MarkLogic Server does not keep multiple versions of a document or track what changes occur. The version id can only be used to detect that a change occurred.
Using optimistic locking in your application requires the following steps:
You enable optimistic locking by setting the update-policy
REST API instance property; for details. You send and receive version ids via the versionId
property in a document descriptor.
To enable optimistic locking, call DatabaseClient.config.serverprops.write
and set the update-policy
property to version-required
or version-optional
. For example, the following code sets update-policy to version-optional:
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); db.config.serverprops.write({'update-policy': 'version-optional'}) .result(function(response) { console.log(JSON.stringify(response)); });
Set the property to version-required
if you want every document update or delete operation to use optimistic locking. Set the property to version-optional
to allow selective use of optimistic locking.
The table below describes how each setting for this property affects document operations.
When optimistic locking is enabled, a version id is included in the response when you read documents. You can only obtain a version id if optimistic locking is enabled by setting update-policy
; for details, see Enable Optimistic Locking.
You can obtain a version id for use in conditional updates in the following ways:
DatabaseClient.documents.read
. The version id is available through the versionId
property of the returned document descriptor(s).DatabaseClient.documents.probe
. No documents are fetched, but the version id is available through the versionId
property of the returned document descriptor(s).You can mix and match these methods of obtaining a version id.
The following example returns the version for multiple documents:
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); db.documents.read('/doc/example1.json', '/doc/example2.json'). .stream().on('data', function(document) { console.log('Read ' + document.uri + ' with version ' + document.versionId); }).on('end', function() { console.log('finished'); });
To apply a conditional update, include a versionId
property in the document descriptor passed to an update or delete operation. The version id is ignored if optimistic locking is not enabled; for details, see Enable Optimistic Locking.
When a document descriptor passed to an update or delete operation includes a version id, MarkLogic Server server checks for a version id match before committing the update or delete. If the input version id does not match a document's current version id, the operation fails. In a multi-document update, the entire batch of updates is rejected if the conditional update of any document in the batch fails.
The following example performs a conditional update of two documents by including the versionId
property in each input document descriptor.
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); db.documents.write({ documents: [ { uri: '/doc/example1.json', contentType: 'application/json', content: { data: 1 }, versionId: 14115098125553360 }, { uri: '/doc/example2.json', contentType: 'application/json', content: { data: 2 }, versionId: 14115098125553350 } ] }).result( function(success) { console.log('Loaded the following documents:'); for (const i in success.documents) console.log(success.documents[i].uri); } );
Similarly, the following example removes the document /doc/example.json
only if the current version id of the document in the database matches the version id in the input document descriptor:
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); db.documents.remove({ uris: ['/doc/example.json'], versionId: 14115105931044000}). result( function(response) { console.log(JSON.stringify(response)); });
You cannot use conditional delete when removing multiple documents in a single operation.
If a conditional update or delete fails due to a version id mismatch, MarkLogic Server responds with an error similar to the following. (The object contents have been reformatted for readability.)
{message: "remove document: response with invalid 412 status (on /doc/example1.json)", statusCode:412, body:{ error: { status-code: "412", status: "Precondition Failed", message-code: "RESTAPI-CONTENTWRONGVERSION", message: "RESTAPI-CONTENTWRONGVERSION: (err:FOER0000) Content version mismatch: uri /doc/example.json doesn't match if-match: 14115105931044000" }}}
This section provides a brief overview of how to use the Node.js API to manipulate binary document data in MarkLogic Server. The following topics are covered:
This section provides a brief summary of binary document types. For details, see Working With Binary Documents in the Application Developer's Guide.
MarkLogic Server can store binary documents in three representations:
MarkLogic automatically determines whether a binary document is a small or large binary document when you insert or update the document, based on the document size and the database configuration.
Though external binary documents cannot be created using the Node.js Client API, you can retrieve them, just like any other document.
By using streaming techniques to access binary content, you can avoid loading potentially large documents into memory on MarkLogic Server and in your client application.
You can stream binary and other data into MarkLogic using by using a stream as the input source. For details, see Streaming Into the Database.
When you retrieve a large or external binary document from a database, MarkLogic Server automatically streams the content out of the database under the following conditions:
You can also use range requests to incrementally retrieve pieces of a binary document that meets the above constraints. For details, see Retrieving Binary Content with Range Requests.
You can avoid loading the entire document into memory in your client application by using a streaming result handler, such as the chunked stream pattern described in Stream Result Handling Pattern.
When you just use db.documents.read
to retrieve a binary document, the goal is to eventually retrieve the entire document, even if your application code processes it in chunks. By contrast, using range requests to retrieve parts of a binary document enables retryable, random access to parts of a binary document.
To use range requests, your retrieval operation must meet the conditions described in Streaming Binary Content. Specify the range of bytes to retrieve by including a range property in the call object passed to DatabaseClient.documents.read
.
The following example requests the first 500K of the binary document with URI /binary/song.m4a
:
db.documents.read({ uris: '/binary/song.m4a', range: [0,511999] })
The document descriptor returned by such a call is similar to the following. The contentLength
property indicates how many bytes were returned. This value will be smaller than the size of the requested range if you request a range that extends past the end of the source document.
result: [{ content: { type': 'Buffer', data': [ theData ] }, uri: '/binary/song.m4a', category: [ 'content' ], format: 'binary', contentLength: '10', contentType: 'audio/mp4' }]
Most document write operations enable you to work with temporal documents. Temporal-aware document inserts and updates are enabled through the following parameters (or document descriptor properties) on operations such as documents.create
, documents.write
, documents.patch
, and documents.remove
:
temporalCollection
: The URI of the temporal collection into which the new document should be inserted, or the name of the temporal collection that contains the document being updated.temporalDocument
: The logical URI of the document in the temporal collection; the temporal collection document URI. This is equivalent to the first parameter of the temporal:statement-set-document-version-uri XQuery function or of the temporal.statementSetDocumentVersionUri Server-Side JavaScript function.sourceDocument
: The temporal collection document URI of the document being operated on. Only applicable when updating existing documents. This parameter facilitates working with documents with user-maintained version URIs.systemTime
: The system start time for an update or insert.During an update operation, if you do not specify a sourceDocument
or temporalDocument
, then the uri
descriptor property indicates the source document. If you specify temporalDocument
, but do not specify sourceDocument
, then temporalDocument
identifies the source document.
The uri
property always refers to the output document URI. When the MarkLogic manages the version URIs, the document URI and temporal document collection URI have the same value. When the user manages version URIs, they can be different.
Use documents.protect
to protect a temporal document from operations such as update, delete, and wipe for a specified period of time. This method is equivalent to calling the temporal:document-protect XQuery function or the temporal.documentProtect Server-Side JavaScript function.
Use documents.advanceLsqt
to advance LSQT on a temporal collection. This method is equivalen to calling the temporal:advance-lsqt XQuery function or the temporal.advanceLsqt Server-Side JavaScript function.
For more details, see the Temporal Developer's Guide and the Node.js Client API JSDoc.
The Node.js Client API enables you to insert, update, retrieve, and query metadata. Metadata is the properties, collections, permissions, and quality of a document. Metadata can be manipulated as either JSON or XML.
This section covers the following topics:
When working with documents and their metadata, metadata is usually expressed as properties of a document descriptor. For example, the following descriptor includes collections metadata for the document /doc/example.json
, whether the descriptor is input output:
{ uri: '/doc/example.json', collections: ['collection1','collection2']
Some operations support a categories
property for indicating what parts of a document and its metadata you want to read or write. For example, you can read just the collections and quality associated with /doc/example.json
by calling DatabaseClient.documents.read
similar to the following:
db.documents.read({ uris: ['/doc/example.json'], categories: ['collections', 'quality'] })
The following categories are supported:
The metadataValues
category represents simple key-value metadata property, sometimes called metadata fields. This category can contain both system-managed metadata, such as certain properties of a temporal document, and user-defined metadata. The value of a property in metadataValues
is always stored as a string in MarkLogic. For more details, see Metadata Fields in the Administrator's Guide.
The metadata
category is shorthand for collections, permissions, properties, and quality. Some operations, such as DatabaseClient.documents.read
, also support the content
category as a convenience for retrieving or updating content and metadata together.
Metadata takes the following form in a document descriptor. You do not need to specify all categories of metadata when including it in a write or query operation. This structure applies to both input and output metadata.
{ "collections" : [ string ], "permissions" : [ { "role-name" : string, "capabilities" : [ string ] } ], "properties" : { property-name : property-value }, "quality" : integer, "metadataValues": { key: value, key: value, ... } }
The following example is an output document descriptor from reading the metadata for the document with URI /doc/example.json
. The document is in two collections and has two permissions, two document properties, and two metadataValues
key-value pairs:
{ "partType": "attachment", "uri": "/doc/example.json", "category": "metadata", "format": "json", "contentType": "application/json", "collections": [ "collection1", "collection2" ], "permissions": [ { "role-name": "rest-writer", "capabilities": [ "update" ] }, { "role-name": "rest-reader", "capabilities": [ "read" ] } ], "properties": { "prop1": "this is my prop", "prop2": "this is my other prop" }, "metadataValues": { "key1": "value1", "key2": 2 }, "quality": 0 }
The following example shows metadata as XML. All elements are in the namespace http://marklogic.com/rest-api
. You can have 0 or more <collection/>
, <permission/>
or property elements. There can be only one <quality/>
element. The element name and contents of each property element depends on the property.
<metadata xmlns="http://marklogic.com/rest-api"> <collections> <collection>collection-name</collection> </collections> <permissions> <permission> <role-name>name</role-name> <capability>capability</capability> </permission> </permissions> <properties> <property-element/> </properties> <quality>integer</quality> <metadata-values> <metadata-value key="key1">value1</metadata-value> <metadata-value key="key2">2</metadata-value> <metadata-values> </metadata>
Document properties are a kind of metadata. You can use document properties to add queryable user-defined data to a document without changing the document contents. For details, see Properties Documents and Directories in the Application Developer's Guide.
In most cases, your application should work with document properties in a consistent format. That is, if you insert or update properties as JSON, then you should retrieve and query them as JSON. If you insert or update properties as XML, you should retrieve them as XML. If you mix and match XML and JSON formats, you can encounter namespace inconsistencies.
Document properties are always stored in the database as XML. A document property inserted as JSON is converted internally into an XML element in the namespace http://marklogic.com/xdmp/json/basic
, with an XML local name that matches the JSON property name. For example, a document property expressed in JSON as { myProperty :
'value
' }
has the following XML representation:
<rapi:metadata uri="/doc/example.json" ...> <prop:properties xmlns:prop="http://marklogic.com/xdmp/property"> <myProperty type="string" xmlns="http://marklogic.com/xdmp/json/basic"> value </myProperty> </prop:properties> </rapi:metadata>
As long as you consistently use a JSON representation for document properties on input and output, this internal representation is transparent to you. However, if you query or read document properties using XML, you must be aware of the namespace and the internal representation. Similarly, you can use XML to insert document properties in no namespace or in your own namespace, but the namespace cannot be reflected in the JSON representation of the property. Therefore, it is best to be consistent in how you work with properties.
In order to support for passing properties as XML in a JSON string, users need to specify the namespace such as in
{'$ml.xml':
'<prop:properties xmlns:prop="http://marklogic.com/xdmp/property">
<myProps>Property 1</myProps></prop:properties>'
}
Failing to mention namespace in a property with XML format in a JSON string can trigger Status 500: XDMP-DOCNONSBIND
error.
The one exception is in code that works with properties on the server, such as resource service extensions and transformations. Such code always accesses document properties as XML.
If you configure an index based on a user-defined document property inserted using JSON, you should use the http://marklogic.com/xdmp/json/basic
namespace in your configuration.
Protected system properties such as last-updated
cannot be modified by your application. The JSON representation of such properties wraps them in an object with the key $ml.prop
. For example:
{ "properties": { "$ml.prop": { "last-updated": "2013-11-06T10:01:11-08:00" } } }
If you use the REST Client API to ingest a large number of documents at one time and you find the performance unacceptable, you might see a small performance improvement by disabling metadata merging. This topic explains the tradeoff and how to disable metadata merging.
The performance gain from disabling metadata merging is modest, so you are unlikely to see significant performance improvement from it unless you ingest a large number of documents. You might see a performance gain under one of the following circumstances:
You cannot disable metadata merging in conjunction with update policies version-optional
or version-required
.
Metadata merging is disabled by default for multi-document write requests, as long as the request includes content for a given document. For details, see Understanding When Metadata is Preserved or Replaced in the REST Application Developer's Guide.
To learn more about the impact of disabling metadata merging, see Understanding Metadata Merging in the REST Application Developer's Guide.
Metadata merging is controlled by the update-policy
instance configuration property. The default value is merge-metadata
.
To disable metadata merging, set update-policy
to overwrite-metadata
using the procedure described in Configuring Instance Properties. For example:
const marklogic = require('marklogic'); const my = require('./my-connection.js'); const db = marklogic.createDatabaseClient(my.connInfo); db.config.serverprops.write({'update-policy': 'overwrite-metadata'}) .result(function(response) { console.log(JSON.stringify(response)); });