I have the need to assign an sequence number for each document inserted in a collection.
I found the following suggestion which hints that such feature is implemented by the function "DocumentId(collection)":
https://feedback.azure.com/forums/263030-azure-cosmos-db/suggestions/6408183-i-want-atomic-counter
When inserting documents directly from .NET code as:
var _cosmosClient = new CosmosClient(new CosmosConfiguration(endpoint, accountKey));
var _container = _cosmosClient.Databases[DatabaseId].Containers[ContainerId];
for (int i = 0; i < 15; i++) {
await _container.Items.CreateItemAsync("event", new { aggregateId = "event", id = Guid.NewGuid().ToString() });
}
And when i query the collection as below, function "DocmentId()" seems to return an incremented integer / sequence just as I need:
SELECT f.id, DocumentId(f) FROM Events f
[
{
"id": "2215dc32-89c1-464c-a559-0269be3068f4",
"$1": 9
},
{
"id": "ca6fac1c-59e1-42e2-858c-9646d6800a7e",
"$1": 10
},
{
"id": "54891f66-26de-461a-8ee7-2b23eb2d7c18",
"$1": 11
}
]
If I instead insert multiple document via a stored procedure, the DocumentId() function behaves differently.
All documents inserted by the stored procedure now has the same value and the value is just a big integer.
{
"id": "046f1a2b-7e6c-488b-af4c-bb06762dae9d",
"$1": 576460752303423500
},
{
"id": "d4f5a8d3-3e5a-456c-8ec9-3967a4250a08",
"$1": 576460752303423500
},
{
"id": "HEAD",
"$1": 576460752303423500
}
Do you know why the DocumentId function behaves differently when a document has been inserted from a stored procedure?
Do you know how I can achieve to have a auto incremented number assigned to each inserted document?
Regards Niclas
Stored Procedure:
function appendEvents(header, docs, etag) {
var collection = getContext().getCollection();
var collectionLink = collection.getSelfLink();
// The count of imported docs, also used as current doc index.
var count = 0;
// Validate input.
if (!docs) throw new Error("The array is undefined or null.");
var docsLength = docs.length;
if (docsLength === 0) {
getContext().getResponse().setBody(0);
}
var options = { disableAutomaticIdGeneration: true };
// Call the CRUD API to create a document.
tryCreateEvent(docs[count], callback);
// Insert or update the header:
upsertStreamHeader(
header,
null,
function (err, doc, options) {
if (err) throw err;
}
);
function tryCreateEvent(doc, callback) {
// Assign the offset value relative from the header.offset:
//doc.offset = header.offset + count + 1;
var isAccepted = collection.createDocument(
collectionLink,
doc,
options,
callback
);
if (!isAccepted) getContext().getResponse().setBody(count);
}
// This is called when collection.createDocument is done and the document has been persisted.
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.
tryCreateEvent(docs[count], callback);
}
}
function upsertStreamHeader(doc, continuation, callback) {
var query = { query: "SELECT * FROM root r WHERE r.id = #id", parameters: [{ name: "#id", value: doc.id }] };
var requestOptions = { continuation: continuation };
var isAccepted = collection.queryDocuments(collectionLink, query, requestOptions, function (err, retrievedDocs, responseOptions) {
if (err)
{
throw err;
}
if (retrievedDocs.length > 0)
{
tryReplaceStreamHeader(retrievedDocs[0], doc, callback);
}
else
{
tryCreateStreamHeader(doc, callback);
}
});
if (!isAccepted) {
throw new Error("Unable to query documents");
}
}
function tryCreateStreamHeader(doc, callback) {
var isAccepted = collection.createDocument(collectionLink, doc, callback);
if (!isAccepted) {
throw new Error("Unable to schedule create document");
}
}
function tryReplaceStreamHeader(docToReplace, docContent, callback) {
var isAccepted = collection.replaceDocument(
docToReplace._self,
docContent,
{ etag: etag },
callback);
if (!isAccepted) {
throw new Error("Unable to schedule replace document");
}
}
}
Related
I'm working on a problem where I need to query the db for an instance of a Voter, and use that instance to update an Election, returning to the original function whether that update was successful or not. My code currently looks like this:
function addCandidatesToElection(req, res) {
let electionName = req.body.electionName;
let candidates = req.body.candidates;
let addedCandidatesSucessfully = true;
for(let i=0; i<candidates.length; i++) {
addedCandidatesSucessfully = _addCandidateToElection(electionName, candidates[i]);
console.log("added candidates sucessfully:" + addedCandidatesSucessfully);
}
if(addedCandidatesSucessfully) {
res.send("createElection success");
} else {
res.send("createElection fail");
}
}
which calls this function:
function _addCandidateToElection(electionName, candidateName) {
async.parallel(
{
voter: function(callback) {
Voter.findOne({ 'name' : candidateName }, function(err,voter) {
callback(err, voter);
});
}
},
function(e, r) {
if(r.voter === null){
return 'Voter not found';
} else {
Election.findOneAndUpdate(
{'name': electionName },
{$push: { candidates: r.voter }},
{new: true},
function(err, election) {
if(err){ return err; }
return (election) ? true : false;
});
}
}
);
}
I've already tried printing out the Voter instance(r.voter) to check if it exists (it does), and also printing out the election object returned by the mongoose call, which also works. However, I'm getting a null value in the
addedCandidatesSucessfully = _addCandidateToElection(electionName, candidates[i]);
line, regardless of the result of the call. I think it has to do with the mongoose call returning a local value which is never returned to the function that called _addCandidateToElection, but I don't know how I should return that. I've tried putting control flags such as
let foundAndUpdatedElection = false;
on the first line of _addCandidateToElection and updating it inside the Mongoose query's callback, but apparently it doesn't change.
How should I return the result of the query to the addCandidatesToElection function?
You should probably 'promisify' your code to help you better deal with the asynchronous nature of js. Try the following instead of your example:
function findVoter(candidateName) {
return new Promise(function(resolve, reject) {
Voter.findOne({ 'name' : candidateName }, function(err,voter) {
if(error) {
reject(error);
} else {
resolve(voter);
}
});
});
}
function addCandidateToElection(electionName, candidateName) {
return findVoter(candidateName).then(function(voter) {
return new Promise(function(resolve, reject) {
Election.findOneAndUpdate(
{'name': electionName },
{$push: { candidates: voter }},
{new: true},
function(err, election) {
if (err) {
reject(err);
} else {
resolve(!!election);
}
});
});
}
function addCandidatesToElection(req, res) {
let electionName = req.body.electionName;
let candidates = req.body.candidates;
let addedCandidatesSucessfully = true;
let candidatePromiseArray = [];
for(let i=0; i<candidates.length; i++) {
candidatePromiseArray.push(addCandidateToElection(electionName, candidates[i]));
}
Promise.all(candidatePromiseArray)
.then(function(results) {
console.log(results);
res.send('create election success');
})
.catch(function(error) {
console.error(error);
res.send('failed');
});
}
You will also no longer need to use the async library because promises are now native in ES6
This is my code want to access callback variable newID outside calling-function. I want to insert bulk data into mongodb using batch with auto incremented id instead of default object
for (var i = 0; i < sizeOfResult; ++i) {
var newKey = {}; //Main json array
newKey = {
date: result[i]['date'],
issue: result[i]['issue'],
status: result[i]['status']
};
getNextSequenceValue("inventoryid",db, function(err, newID) {
newKey["_id"] = newID; <!-- try to add/assign callback variable(newID) into newKey -->
});
console.log("newKey: %j", newKey); <!-- but unable to get access callback variable(newID) here below-->
batch.insert(newKey);
}
// This is my called function
function getNextSequenceValue(name,db,callback) {
var ret = db.collection('counters_inv').findAndModify({ _id: name },null,{ $inc: { sequence_value: 1 } }, {new: true},
function(err,doc ) {
if(err){
return callback(err) // callback on error
}
callback(null, doc.value.sequence_value); // callback on success
});
}
Look at this code, you just need to put the variable outside and it works:
let i = 1
function functionwithcallback(callback) {
console.log(i)
i++
callback(i)
}
for (let j = 1; j <= 10; j++) {
functionwithcallback(() => {
if (j == 10)
console.log('done')
})
}
I'm not sure what the overall objective is, but the reason your newKey variable is not set correctly is because where it is being used has executed before the variable is set. In your example, your for loop is going to completely finish running kicking off a bunch of getNextSequenceValue() method calls that will eventually come back and run the callback code. It does not wait on the getNextSequenceValue function to finish before continuing the loop.
Solution: moving the console.log() and batch.insert() into the callback.
Here's an example that would execute in the correct order.
var keys = [];
for (var i = 0; i < sizeOfResult; ++i) {
var newKey = {
date: result[i]['date'],
issue: result[i]['issue'],
status: result[i]['status']
};
getNextSequenceValue("inventoryid", db, function(err, newID) {
newKey["_id"] = newID;
keys.push(newKey);
if (keys.length === sizeOfResult) {
console.log("keys: %j", keys);
batch.insertAll(keys);
}
});
}
function getNextSequenceValue(name, db, callback) {
db.collection('counters_inv').findAndModify({ _id: name }, null, { $inc: { sequence_value: 1 } }, {new: true},
function(err,doc) {
if(err){
return callback(err);
}
callback(null, doc.value.sequence_value);
});
}
exports.show = = function(req, res) {
var userdata = [{
"productcode": "9563456789",
"cost": "1000"
}, {
"productcode": "8756348947",
"cost": "5600"
}]
var parameterObject = [];
Parameter.find().exec(function(err, Parameters) {
if (err) {
return handleError(res, err);
}
// i want to push Parameters[0].value to parameterObject
parameterObject.push({
pointvalue: Parameters[0].value
});
});
for (var i = 0; i < userdata.length; i++) {
Product.find({
'productcode': userdata[i].productcode
}).exec(function(err, Products) {
if (err) {
return handleError(res, err);
}
var point = 0;
if (!Products) {
point = 0;
} else if (Products[0].product.point > 0) {
point = Products[0].product.point;
}
if (point > 0) {
// here i am not getting userdata[i].cost
//parameterObject.pointvalue value also not getting
totalprice = userdata[i].cost / parameterObject.pointvalue * point;
}
});
}
};
Here i have written function for calculating totalprice. i have mentioned userdata(this is my req.body).
Expectation :
i need to store Parameters objects in some variable to access where ever i want.
i want to pass userdata object in Product.find() function
how can i calculate this
totalprice= userdata[i].cost/parameterObject.pointvalue) * point);
exports.show = = function(req, res) {
var userdata = [{
"productcode": "9563456789",
"cost": "1000"
}, {
"productcode": "8756348947",
"cost": "5600"
}]
var parameterObject = [];
Parameter.find().exec(function(err, Parameters) {
if (err) {
return handleError(res, err);
}
// i want to push Parameters[0].value to parameterObject
parameterObject.push({
pointvalue: Parameters[0].value
});
return FindProducts(parameterObject, function(data) {
console.log(data);
});
});
function FindProducts(parameterObject, callback) {
for (var i = 0; i < userdata.length; i++) {
var totalprice = 0;
findProduct(i, parameterObject, function(i, price) {
totalprice += price;
if (i <= userdata.length) {
return callback({
"userid": "myuserid",
"total": totalprice
});
}
});
}
}
function findProduct(i, parameterObject, callback) {
Product.find({
'productcode': userdata[i].productcode
}).exec(function(err, Products) {
if (err) {
return handleError(res, err);
}
var point = 0;
if (!Products) {
point = 0;
} else if (Products[0].product.point > 0) {
point = Products[0].product.point;
}
if (point > 0) {
// here you can now get the value of userdata[i].cost
// here you can now get the value of parameterObject
totalprice = userdata[i].cost / parameterObject[0].pointvalue * point;
return callback(i, totalprice);
}
});
}
};
You can use promises when you want to use the result of two functions and later use it for further computation.
In your case, you can execute the two asynchronous functions in parallel. It can look like this.
Promise.all([
asyncFunc1(),
asyncFunc2(),
])
.then(function(result){
// result is an array and has the response of the functions which is
// result[0] and result[1]
···
// you can manipulate the result of the functions here
})
.catch(function(err){
// Receives rejection/error among the Promises
···
});
Here asyncFunc1() will be your first find function
asyncFunc2() will be your second find function.
The result[0] and result[1] will be the results of the functions respectively.
Later you can use the result to do further computations.
Hope this helps.
I am planning to update the document, if similar values already exists in other records of the document.
Say, if root document has few records in which few fields(track,time) are similar, then I want to update the isOriginal field for such record as false.
function updateArticlesDetailsX() {
var collection = getContext().getCollection();
var collectionLink = collection.getSelfLink();
var response = getContext().getResponse();
var docCount = 0;
var counter = 0;
tryQueryAndUpdate();
function tryQueryAndUpdate(continuation) {
var query = {
query: "select * from root r ORDER BY r.created.epoch DESC"
};
var requestOptions = {
continuation: continuation
};
var isAccepted =
collection
.queryDocuments(collectionLink,
query,
requestOptions,
function queryCallback(err, documents, responseOptions) {
//response.setBody(responseOptions);
if (err) throw err;
if (documents.length > 0) {
document.upd
// If at least one document is found, update it.
docCount = documents.length;
//response.setBody("Found " + docCount + " documents");
for (var i=0; i<docCount; i++){
tryUpdate(documents[i]);
}
//response.setBody("Updated " + docCount + " documents");
}
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 {
// throw new Error("Document not found.");
// }
});
if (!isAccepted) {
//throw new Error("The stored procedure timed out");
}
}
function tryUpdate(document) {
//Optimistic concurrency control via HTTP ETag.
var requestOptions = { etag: document._etag };
//Update statement goes here:
document.update({"track":{document.track},"Time":{document.Time} }, {"$set":{"isOriginal":"false!"}});
document.created = {
date: "2016-06-22 19:18:14",
epoch: 1466623094582
}
//response.setBody(document);
var isAccepted = collection
.replaceDocument(document._self,
document,
requestOptions,
function replaceCallback(err, updatedDocument, responseOptions) {
if (err) throw err;
counter++;
response.setBody("Updated " + counter + " documents");
// response.setBody(updatedDocument);
});
// If we hit execution bounds - throw an exception.
if (!isAccepted) {
//throw new Error("The stored procedure timed out");
}
}
}
I want to have exactly auto-increment field like relational or objective databases, so i need an integer _id field with automatically set field value, value should be one more last record _id value like this:
data:
{_id:1,name"foo"}
{_id:2,name"bar"}
remove last record:
{_id:1,name"foo"}
add new record:
{_id:1,name"foo"}
{_id:3,name"newbar"}
I added a function to my datastore and calculate maximum of _id and plus 1 max(_id)+1 and set as field value, but there is problem here:
When we use auto-increment field in relational databases, it works like i said and after you remove last record it reserved a deleted record number and new inserted records continue increment but in my way its says the _id of removed record for new record.
My code is:
var Datastore = require('nedb'),
localDb = new Datastore({
filename: __dirname + '/dbFilePath.db',
autoload: true
});
localDb.getMax = function(fieldName, onFind){
db.find({}).sort({_id:-1}).limit(1).exec(function (err, docs) {onFind && onFind(err, docs['_id']);});
return localDb;
}
localDb.insertAutoId = function(data, onAdd){
var newIndex = 0;
localDb.getMax(function (err, maxValue) {
newIndex = maxValue+1;
if(!data["_id"])
data["_id"] = newIndex;
localDb.insert(data, function (err, newDoc) {
onAdd && onAdd(err, newDoc);
});
});
return localDb;
}
An improved answer for nedb would be:
db.getAutoincrementId = function (cb) {
this.update(
{ _id: '__autoid__' },
{ $inc: { seq: 1 } },
{ upsert: true, returnUpdatedDocs: true },
function (err, affected, autoid) {
cb && cb(err, autoid.seq);
}
);
return this;
};
Which is equivalent to the mongodb way:
db.getAutoincrementId = function (cb) {
this.findAndModify({
query: { _id: '__autoid__' },
update: { $inc: { seq: 1 } },
new: true
}
function (err, autoid) {
cb && cb(err, autoid.seq);
}
);
return this;
};
You can store the last value of the index in the database. Something like this:
var Datastore = require('nedb');
var db = new Datastore({
filename: __dirname + '/dbFilePath.db',
autoload: true
});
// Initialize the initial index value
// (if it already exists in the database, it is not overwritten)
db.insert({_id: '__autoid__', value: -1});
db.getAutoId = function(onFind) {
db.findOne( { _id: '__autoid__' }, function(err, doc) {
if (err) {
onFind && onFind(err)
} else {
// Update and returns the index value
db.update({ _id: '__autoid__'}, { $set: {value: ++doc.value} }, {},
function(err, count) {
onFind && onFind(err, doc.value);
});
}
});
return db;
}
I do not know if it will be useful for you anymore I use a database to store the next ids, inspired in the mysql system. Who always reserves the next id.
So I created a function that verifies if there is an id to the db, if it does not, it add with the value "1", and when it updates it looks for and if it exists and it performs the sequence.
This gave me full control over my ids.
The schema would be:
{
name: nameDb,
nextId: itemID
}
If you want you can create functions for updating documents, versioning, etc.
example:
db.autoincrement = new Datastore({filename: 'data/autoincrement.db', autoload: true});
function getUniqueId(nameDb, cb) {
db.autoincrement.findOne({name: nameDb}, function (err, doc) {
if (err) {
throw err;
} else {
if (doc) {
const itemID = doc.nextId + 1;
db.autoincrement.update({name: nameDb}, {
name: nameDb,
nextId: itemID
}, {}, function (err, numReplaced) {
db.autoincrement.persistence.compactDatafile();
if (err) {
throw err;
} else {
// console.log(numReplaced);
}
cb(doc.nextId);
});
} else {
const data = {
name: nameDb,
nextId: 2
};
db.autoincrement.insert(data, function (err, newDoc) {
if (err) {
throw err;
} else {
// console.log(newDoc);
}
cb(1);
});
}
}
});
}
insert new document example:
function insert(req, cb) {
getUniqueId("testdb", function (uniqueId) {
data.itemId = uniqueId;
db.testdb.insert(data, function (err, newDoc) {
if (err) {
cb({error: '1', message: 'error#2'});
throw err;
}
cb({error: '0', message: 'Item add'});
});
});
}