Let's say we have a container with following items
{
location:CA,
bool: false
}
{
location:CA,
bool: false
}
{
location:CA,
bool: false
}
How do we write a stored procedure to query all items and update all item's bool field from false to true? I have not find any reference yet and I know CosmosDB stored procedure only support querying not updating or deleting.
This stored procedure code:
// SAMPLE STORED PROCEDURE
function sample() {
var collection = getContext().getCollection();
// Query all documents in one logic partition
var isAccepted = collection.queryDocuments(
collection.getSelfLink(),
'SELECT * FROM root r',
function (err, feed, options) {
if (err) throw err;
if (!feed || !feed.length) {
var response = getContext().getResponse();
response.setBody('no docs found');
}
else {
feed.forEach(element => {
element.bool = true;
collection.replaceDocument(element._self, element,function (err){
if (err) throw err;
})
})
var response = getContext().getResponse();
response.setBody("replace success");
}
});
if (!isAccepted) throw new Error('The query was not accepted by the server.');
}
Execute stored procedure:
BTW, your partition key should be /location.
Related
A simple stored procedure using readDocument function in CosmosDB/DocumentDB, but it does not work.
function testRead() {
var collection = getContext().getCollection();
var docId = collection.getSelfLink() + 'docs/myDocId';
// Query documents and take 1st item.
var isAccepted = collection.readDocument(docId, {}, function (err, doc, options) {
if (err) throw err;
response.setBody(JSON.stringify(doc));
});
if (!isAccepted) throw new Error('The query was not accepted by the server.');
}
it always get error code 400.
{"code":400,"body":"{\"code\":\"BadRequest\",\"message\":\"Message:
{\\"Errors\\":[\\"Encountered exception while executing Javascript.
Exception = Error: Error creating request message\\r\\nStack
trace: Error: Error creating request message\\n at readDocument
(testRead.js:512:17)\\n at testRead (testRead.js:8:5)\\n at
__docDbMain (testRead.js:18:5)\\n at Global code (testRead.js:1:2)\\"]}\r\nActivityId:
2fb0f7ef-c192-4b56-b8bb-9681c9f8fa6e, Request URI:
/apps/DocDbApp/services/DocDbServer22/partitions/a4cb4962-38c8-11e6-8106-8cdcd42c33be/replicas/1p/,
RequestStats: , SDK:
Microsoft.Azure.Documents.Common/1.22.0.0\"}","activityId":"2fb0f7ef-c192-4b56-b8bb-9681c9f8fa6e","substatus":400}
Anyone can help me ?
Can you try this: var docId = collection.getAltLink() + 'docs/myDocId';
-- self link is not for "name routing".
According to Michael's suggestion, my sample works now, here is the code
function testRead() {
var collection = getContext().getCollection();
var response = getContext().getResponse();
var docId = collection.getAltLink() + '/docs/myDocId';
// Query documents and take 1st item.
var isAccepted = collection.readDocument(docId, {}, function (err, doc, options) {
if (err) throw err;
response.setBody(JSON.stringify(doc));
});
if (!isAccepted) throw new Error('The query was not accepted by the server.');
}
You could modify your code like :
function testRead() {
var collection = getContext().getCollection();
var docId = collection.getAltLink() + 'docs/myDocId';
console.log(collection.getSelfLink() + 'docs/myDocId');
var isAccepted = collection.readDocument(docId, {}, function (err, doc, options) {
if (err) throw err;
response.setBody(JSON.stringify(doc));
});
if (!isAccepted) throw new Error('The query was not accepted by the server.');
}
Or you could use follow sample code to query the document, it also contains all of the fields.
function testRead() {
var collection = getContext().getCollection();
var query = "select * from c where c.id = '1'";
var isAccepted = collection.queryDocuments(collection.getSelfLink(), query,function (err, doc, options) {
if (err) throw err;
var response = getContext().getResponse();
response.setBody(JSON.stringify(doc));
});
if (!isAccepted) throw new Error('The query was not accepted by the server.');
}
I've been following along the javascript stored proc examples shown here
The code below is an attempt at writing a modified version of the update stored proc sample. Here's what I'm trying to do:
Instead of operating on a single document, I'd like to perform the
update on the set of documents returned by a provided query.
(Optional) Return a count of updated documents in the response body.
Here's the code:
function updateSproc(query, update) {
var collection = getContext().getCollection();
var collectionLink = collection.getSelfLink();
var response = getContext().getResponse();
var responseBody = {
updated: 0,
continuation: false
};
// Validate input.
if (!query) throw new Error("The query is undefined or null.");
if (!update) throw new Error("The update is undefined or null.");
tryQueryAndUpdate();
// Recursively queries for a document by id w/ support for continuation tokens.
// Calls tryUpdate(document) as soon as the query returns a document.
function tryQueryAndUpdate(continuation) {
var requestOptions = {continuation: continuation};
var isAccepted = collection.queryDocuments(collectionLink, query, requestOptions, function (err, documents, responseOptions) {
if (err) throw err;
if (documents.length > 0) {
tryUpdate(documents);
}
else if (responseOptions.continuation) {
// Else if the query came back empty, but with a continuation token; repeat the query w/ the token.
tryQueryAndUpdate(responseOptions.continuation);
}
else {
// Else if there are no more documents and no continuation token - we are finished updating documents.
responseBody.continuation = false;
response.setBody(responseBody);
}
});
// If we hit execution bounds - return continuation:true
if (!isAccepted) {
response.setBody(responseBody);
}
}
// Updates the supplied document according to the update object passed in to the sproc.
function tryUpdate(documents) {
if (documents.length > 0) {
var requestOptions = {etag: documents[0]._etag};
// Rename!
rename(documents[0], update);
// Update the document.
var isAccepted = collection.replaceDocument(
documents[0]._self,
documents[0],
requestOptions,
function (err, updatedDocument, responseOptions) {
if (err) throw err;
responseBody.updated++;
documents.shift();
// Try updating the next document in the array.
tryUpdate(documents);
}
);
if (!isAccepted) {
response.setBody(responseBody);
}
}
else {
tryQueryAndUpdate();
}
}
// The $rename operator renames a field.
function rename(document, update) {
var fields, i, existingFieldName, newFieldName;
if (update.$rename) {
fields = Object.keys(update.$rename);
for (i = 0; i < fields.length; i++) {
existingFieldName = fields[i];
newFieldName = update.$rename[fields[i]];
if (existingFieldName == newFieldName) {
throw new Error("Bad $rename parameter: The new field name must differ from the existing field name.")
} else if (document[existingFieldName]) {
// If the field exists, set/overwrite the new field name and unset the existing field name.
document[newFieldName] = document[existingFieldName];
delete document[existingFieldName];
} else {
// Otherwise this is a noop.
}
}
}
}
}
I'm running this sproc via the azure web portal, and these are my input parameters:
SELECT * FROM root r
{$rename: {A: "B"}}
My documents look something like this:
{ id: someId, A: "ChangeThisField" }
After the field rename, I would like them to look like this:
{ id: someId, B: "ChangeThisField" }
I'm trying to debug two issues with this code:
The updated count is wildly inaccurate. I suspect I'm doing something really stupid with the continuation token - part of the problem is that I'm not really sure about what to do with it.
The rename itself is not occurring. console.log() debugging shows that I'm never getting into the if (update.$rename) block in the rename function.
I modified your stored procedure code as below and it works for me.I didn't use object or array as my $rename parameter, I used oldKey and newKey instead. If you do concern the construct of parameters, you could change the rename method back which does not affect other logic. Please refer to my code:
function updateSproc(query, oldKey, newKey) {
var collection = getContext().getCollection();
var collectionLink = collection.getSelfLink();
var response = getContext().getResponse();
var responseBody = {
updated: 0,
continuation: ""
};
// Validate input.
if (!query) throw new Error("The query is undefined or null.");
if (!oldKey) throw new Error("The oldKey is undefined or null.");
if (!newKey) throw new Error("The newKey is undefined or null.");
tryQueryAndUpdate();
function tryQueryAndUpdate(continuation) {
var requestOptions = {
continuation: continuation,
pageSize: 1
};
var isAccepted = collection.queryDocuments(collectionLink, query, requestOptions, function (err, documents, responseOptions) {
if (err) throw err;
if (documents.length > 0) {
tryUpdate(documents);
if(responseOptions.continuation){
tryQueryAndUpdate(responseOptions.continuation);
}else{
response.setBody(responseBody);
}
}
});
if (!isAccepted) {
response.setBody(responseBody);
}
}
function tryUpdate(documents) {
if (documents.length > 0) {
var requestOptions = {etag: documents[0]._etag};
// Rename!
rename(documents[0]);
// Update the document.
var isAccepted = collection.replaceDocument(
documents[0]._self,
documents[0],
requestOptions,
function (err, updatedDocument, responseOptions) {
if (err) throw err;
responseBody.updated++;
documents.shift();
// Try updating the next document in the array.
tryUpdate(documents);
}
);
if (!isAccepted) {
response.setBody(responseBody);
}
}
}
// The $rename operator renames a field.
function rename(document) {
if (oldKey&&newKey) {
if (oldKey == newKey) {
throw new Error("Bad $rename parameter: The new field name must differ from the existing field name.")
} else if (document[oldKey]) {
document[newKey] = document[oldKey];
delete document[oldKey];
}
}
}
}
I only have 3 test documents, so I set the pagesize to 1 to test the usage of continuation.
Test documents:
Output:
Hope it helps you.Any concern,please let me know.
Azure DocumentDB does not support UPSERT. Is there a reasonable work around to achieve the same functionality?
Is using a stored procedure which checks if the document exists to determine whether and insert or update should be performed an effective strategy?
What if I need to perform thousands of these in bulk?
Vote for the feature here:
http://feedback.azure.com/forums/263030-documentdb/suggestions/7075256-provide-for-upsert
Update - Here is my attempt at a bulk upsert stored procedure.
function bulkImport(docs) {
var collection = getContext().getCollection();
var collectionLink = collection.getSelfLink();
var count = 0;
if (!docs) throw new Error('Docs parameter is null');
var docsLength = docs.length;
if (docsLength == 0) {
getContext().getResponse().setBody(0);
}
tryUpsert(docs[count], callback);
function tryUpsert(doc, callback) {
var query = { query: ""select * from root r where r.id = #id"", parameters: [ {name: ""#id"", value: doc.id}]};
var isAccepted = collection.queryDocuments(collectionLink, query, function(err, resources, options) {
if (err) throw err;
if(resources.length > 0) {
// Perform a replace
var isAccepted = collection.replaceDocument(resources[0]._self, doc, callback);
if (!isAccepted) getContext().getResponse().setBody(count);
}
else {
// Perform a create
var isAccepted = collection.createDocument(collectionLink, doc, callback);
if (!isAccepted) getContext().getResponse().setBody(count);
}
});
if (!isAccepted) getContext().getResponse().setBody(count);
}
function callback(err, doc, options) {
if (err) throw err;
// One more document has been inserted, increment the count.
count++;
if (count >= docsLength) {
// If we have created all documents, we are done. Just set the response.
getContext().getResponse().setBody(count);
} else {
// Create next document.
tryUpsert(docs[count], callback);
}
}
}
Update (2015-10-06): Atomic upsert is now supported by Azure DocumentDB.
Yes, a store procedure would work great for upsert.
There are even code samples available on DocumentDB's Github:
Upsert (Optimized for Insert): https://github.com/aliuy/azure-node-samples/blob/master/documentdb-server-side-js/stored-procedures/upsert.js
Upsert (Optimized for Replace): https://github.com/aliuy/azure-node-samples/blob/master/documentdb-server-side-js/stored-procedures/upsertOptimizedForReplace.js
Bulk Import / Upsert:
https://github.com/Azure/azure-documentdb-hadoop/blob/master/src/BulkImportScript.js
I have some problem with transactions in ArangoDB+nodejs. I need to do something like this:
transaction
{
insertedId=insertItemOneInDB();
insertItemTwoInDB();
}
but when the second insert failed, the first one didn't rollback!
please help me with an example!
here is my code:
var transaction = function (collections,params,callback)
{
try
{
db.transaction.submit("user_merchant_tbl",params,
function ()
{
console.log("_collections:",collections);
console.log("params:");
console.log(params);
console.log("---");
console.log("+==========+");
//
var insertedDataId;
var relationsArrayIds=[];
db.document.create(collections,params.data).then(function(_insertedId)
{
insertedDataId=_insertedId;
}
,function(err)
{
console.log("ERROR: Arango--insert-->err: %j", err);
//throw "Error: "+err;
return false;
});
/////
var relations=params.relations;
for(var i=0;i<relations.length;i++)
{
db.document.create(relations[i].edge,relations[i].data).then(
function(_id)
{
relationsArrayIds.push(_id);
next(true);
}
,function(err)
{
console.log("ERROR: Arango--insert.edge-->err:23232 %j", err);
console.log("after return");
next(false);
return false
});
}
console.log("transaction before true");
function next(result)
{
if(result==true)
{
console.log("transaction is ok:",result);
callback(insertedDataId,result);
}
else
{
console.log("transaction is not OK:",result);
callback(insertedDataId,false);
}
}
}
);
}
catch(e)
{
console.log("catch->error in -->Arango.transaction: ",e);
}
}
first of all there seems to be a misunderstanding in how to write the action that is supposed to be executed. This action is executed directly on the Database Server , hence you cant use any functionality provided by the Arango Javascript api.
If you want to design your action it has to run in the arango shell or on the server console (bin/arangod data --console)
I took a look into your code and assume you want to store relations between users and merchants. As Arango comes with a nice graph module you could follow the following approach :
// First we define a graph, containing of 2 document collections ("users" and "merchants") and 2 edge collections (one per relation type, in this example "contactRequested" and "boughtSomethingFrom".
// Note that in this definition the relation "boughtSomethingFrom" is only allowed from a user to a merchant. Of course this is just one way to design it, you have to do it the way it suits you the best.
var edgeDefinitions = [{
collection: "contactRequested",
from: ["users", "merchants"],
to: ["users", "merchants"]
}, {
collection: "boughtSomethingFrom",
from: ["users"],
to: ["merchants"]
}];
// Now we create a graph called "user_merchant_graph" and in the callback function execute a transaction
db.graph.create("user_merchant_graph", edgeDefinitions, function(err, ret, message) {
// Lets define the action for the transaction, again this will be executed directly on the server ......
var action = function (params) {
// We have to require the database module ....
var db = require("internal").db;
var relationsArrayIds = [];
// now we store the user provided to the function
var insertedUserId = db["users"].insert(params.data)._id;
var relations = params.relations;
// Now we loop over through the relations object, store each merchant and it's relations to the user
Object.keys(relations).forEach(function (relation) {
// store merchant
var insertedMerchantId = db["merchants"].insert({merchantName : relation})._id;
// store relation as edge from "insertedUserId" to "insertedMerchantId".
var edgeId = db[relations[relation].relation].insert(insertedUserId, insertedMerchantId, relations[relation].additionalData)._id;
relationsArrayIds.push(edgeId);
});
};
// End of action
var options = {};
options.params = {
data: {
userName : "someUserName",
userSurname : "someUserSurname"
},
relations : {
merchantA : {relation : "contactRequested", additionalData : {data :"someData"}},
merchantB : {relation : "boughtSomethingFrom", additionalData : {data :"someData"}},
merchantC : {relation : "contactRequested", additionalData : {data :"someData"}}
}
};
// Now we call the transaction module ... a note to the collections parameter, it has to be an object containing the keys "write" and "read" which have a list of all collections as value into which the action is writing /reading from
// This collections object is NOT available within your action, the only thing passed as argument to your action is "options.params" !!
db.transaction.submit({write : ["users", "merchants", "contactRequested", "boughtSomethingFrom"]}, action, options, function(err, ret, message) {
//some callback
});
});
With regards to transactions they are working, you can give this code a shot and if you f.e. mess up the storing of the edges (change it to "var edgeId = db[relations[relation].relation].insert(relations[relation].additionalData)._id;")
you will see that your user and merchant have not been stored
I hope this helps
I would like to read a csv file and upload each row to a couchdb using a grunt task. At this point I am not yet doing any database validation such as checking if the record already exists but will have to do that at some point also.
Currently this is what I am doing and the problem is only the first 65 rows, of the first sub task named people is being uploaded to couchdb.
I know this has something to do with asynchronous execution but just can't work out how to do this
Gruntils.js
csv2couch: {
people: {
db: 'http://localhost:5984/db',
collectionName: 'person',
src:['./data/schema3/people.csv']
},
organisms: {
db: '<%= qmconfig.COUCHDBURL %>',
collectionName: 'organism',
src:['./data/schema3/organisms.csv']
}
}
csv2couch.js
'use strict';
var nanolib = require('nano'),
csv = require('csv'),
urls = require('url'),
fs = require('fs');
module.exports = function(grunt) {
grunt.registerMultiTask('csv2couch', 'Parse csv file and upload data to couchdb.', function() {
var done, parts, dbname, _this, collectionName;
_this = this;
done = this.async();
parts = urls.parse(this.data.db);
dbname = parts.pathname.replace(/^\//, '');
collectionName = this.data.collectionName;
// Merge task-specific and/or target-specific options with these defaults.
var options = this.options({});
// couchdb connection
try {
var nano = nanolib(parts.protocol + '//' + parts.host);
} catch (e) {
grunt.warn(e);
done(e, null);
}
// database connection
var db = nano.use(dbname);
// process each source csv file
this.filesSrc.forEach(function(f) {
console.log('source file:', f);
csv()
.from.path(f, {
columns:true,
delimeter:',',
quote:'"'
})
.on('record', function(row,index){
console.log('#'+index, row);
save(row, collectionName);
})
.on('end', function(count){
console.log('Number of lines: '+count);
done();
})
.on('error', function(error){
console.log(error.message);
done(error);
});
});
function save (data, collectionName) {
// document ID is concatenation of collectionName and ID
var docID = collectionName[0]+'_'+data.ID;
// add some additional data
data.type = collectionName;
// insert data into couchdb
db.insert(data, docID, function(err, body, header) {
if (err) {
console.log('[db.insert] ', err.message);
return;
}
});
}
});
};
You're right, the async code is incorrect. The CSV file is being read to the end before all your records are saved. You need to call done only when your last record has been saved.
Your save method needs to take a callback
var rowsRead = 0, // the number of rows read from the csv file
rowsWritten = 0; // the number of rows written to CouchdDb
caller:
.on('record', function(row,index){
rowsRead++;
save(row, collectionName, function(err){
if(err){
return done(err);
}
rowsWritten++;
if(rowsRead===rowsWritten){ // check if we've written all records to CouchDb
done();
}
});
})
save method:
function save (data, collectionName, callback) {
// document ID is concatenation of collectionName and ID
var docID = collectionName[0]+'_'+data.ID;
// add some additional data
data.type = collectionName;
// insert data into couchdb
db.insert(data, docID, callback);
}