Read document from DocumentDB by ID using Node.js fails - javascript

I'm trying to read a single document from DocumentDB by using the document ID. The collection has four fields, author being the partition key.
{
"id": string,
"author": string,
"title": string,
"year": int
}
I have two functions for reading the records stored into DocumentDB. queryCollectionByBookId reads a single document by document id and queryCollectionGetBooks returns all the documents in the collection.
var dbutils = {
queryCollectionByBookId: function(client, documentUrl) {
return new Promise((resolve, reject) => {
client.readDocument(documentUrl, function(err, doc) {
if (err) {
reject(err);
} else {
resolve(doc);
}
});
});
},
queryCollectionGetBooks: function(client, collectionUrl) {
return new Promise((resolve, reject) => {
client.readDocuments(collectionUrl).toArray((err, results) => {
if (err)
reject(err);
else {
resolve(results);
}
});
});
}
};
queryCollectionGetBooks function works fine, but queryCollectionByBookId returns the error message below.
{"Errors":["The partition key supplied in x-ms-partitionkey header has fewer components than defined in the the collection."]}
Has anyone else seen this error message and found out how to resolve it?

It's an oversight in the documentation for the node.js client, but you have to add an options.partitionKey property to the optional second parameter in your readDocument(link, options, callback) call.
It's also an oversight in the docs but I think partitionKey needs to be an array with a single element (e.g. {options: {partitionKey: ["myKey"]}} [UPDATE: I'm told that it will now work with a single partition key value but I haven't confirmed it myself.]
Alternatively, you can set options.enableCrossPartitionQuery property to true but that is less efficient.

Related

Creating new mongoose sub-doc and appending to existing parent doc

I'm building a website with a database using NodeJS, MongoDB, Express, Mongoose etc.
I have two schema set up: Events and a sub-doc schema Categories (among others).
The function pulls in array which contains the data needed to create several categories (this bit works) as well as the Event ID appended to the end.
The first few bits below just grab that ID, then remove it from the array (probably a better way to do this, but again, it works).
As mentioned above, the Categories then create correctly (and even do validation), which is amazing, BUT...
They don't get appended to the Event doc. The doc updates the "categories" field to an applicable number of "null" values, but I cannot for the life of me get it to actually take the IDs of the newly created categories.
I nabbed (and adjusted) the below code from somewhere, so this is where I'm at...
exports.addCategories = catchAsync(async (req, res, next) => {
const categories = req.body;
const length = categories.length;
const eventID = categories[length - 1].eventId;
categories.pop();
Event.findOne({ _id: eventID }, (err, event) => {
if (err) return res.status(400).send(err);
if (!event)
return res.status(400).send(new Error("Could not find that event"));
Category.create(categories, (err, category) => {
if (err) return res.status(400).send(err);
event.categories.push(category._id);
event.save((err) => {
if (err) return res.status(400).send(err);
res.status(200).json(category);
});
});
});
});
Currently the mongoose debug output is showing the following (which confirms that MOST of it is working, but the IDs just aren't being pulled correctly):
> Mongoose: events.updateOne({ _id: ObjectId("614bc221bc067e62e0790875")}, { '$push': { categories: { '$each': [ undefined ] } }, '$inc': { __v: 1 }}, { session: undefined })
Nevermind! I realised that "category" was still an array, rather than an element of the categories array as I'd assumed.
So I replaced that section with this, and now... it works!
Category.create(categories, (err, categories) => {
if (err) return res.status(400).send(err);
categories.forEach((category) => {
event.categories.push(category._id);
});
event.save((err) => {
if (err) return res.status(400).send(err);
});
});

Error: Argument "documentRef" is not a valid DocumentReference. Couldn't serialize object of type "CollectionReference". FireStore

In order to update a document I do the following:
1) I collect the first part of the data from a document in a collection called "Loc"
2) I use a part of data from 1) to get a document in a collection called "userInfo"
3) I use results from 1) and 2) to update the second document
I used Transaction because I need this 3 actions to run all at once according to the documentation, this is possible.
return new Promise((res, rej) => {
var LocRef = db.collection("Loc").doc(newLocale);
db.runTransaction(function (transaction) {
return transaction.get(LocRef).then(function (doc) {
if (!doc.exists) {
throw "Document does not exist!";
}
var userInfoDocRef = userInfoRef.doc(user_id)
transaction.get(userInfoDocRef).then(function (userDoc) {
if (!userDoc.exists) {
throw "Document does not exist!";
}
var userInfoUpdate = {
"preferred_locale": newLocale,
"preferred_pair": userDoc.data().preferred_crypto_currency + doc.data().CurrencyCode,
"preferred_fiat_currency": doc.data().CurrencyCode
}
transaction.update(userInfoRef, userInfoUpdate);
var ans = {"success": true}
return ans
}).catch((e)=>{
console.log(e)
});
}).then(function (ans) {
res(ans)
}).catch(function (err) {
// This will be an "population is too big" error.
console.error(err);
rej(err)
});
})
})
However, I get the following error:
Error: Argument "documentRef" is not a valid DocumentReference.
Couldn't serialize object of type "CollectionReference" Firestore doesn't support JavaScript objects with custom prototypes (i.e. objects that were created via the 'new' operator).

Trouble Returning Different Variables from Mongo using NodeJS

So I have a function that when you pass a username as an argument it queries a MongoDB database and returns the document containing that username. So in the function, I check to see if the document exists containing the username, and if it doesn't I return the document that has an empty string as the username. So kind of like, return default if doesn't exist. So I assume that if it doesn't find a matching document it returns an undefined object.
Ideally, I want a function that when called will either return a default document retrieved from a database when the username doesn't exist or return the corresponding document for the username passed as an argument. Maybe the problems are trying to read or return variables before they exist because of the asynchronous nature of the calls.
I really don't think major restructuring of the code is a good idea, because I'm trying to work with three asynchronous libraries and connect them all together. I have multiple asynchronous classes in recursive processing functions.
getContext(username = '') {
const MongoClient = require('mongodb').MongoClient;
MongoClient.connect('mongodb://localhost:27017/tc-db', function (err, db) {
if (err) {
console.log(err);
} else {
db.collection('chatters').findOne({ username: username }, function (err, results) {
if (err) {
throw err;
} else if (results === undefined) {
db.collection('chatters').findOne({ username: '' }, function (err, results) {
console.log('Notifier');
console.log('Get if Null: ' + JSON.stringify(results));
return JSON.stringify(results.context);
});
} else {
console.log('Notifier 2');
return JSON.stringify(results.context);
}
});
}
});
}
The actual error I'm getting alot when running the function, especially with a username that doesn't exist in the database is "Can't read property 'context' of null". Thank you guys so much for any help you can offer.

Dynamically added object attributes not available

I have an array of Stock objects and try to attach n Report objects to each of the Stock objects:
router.get('/stocks', function (req, res, next) {
Stock.find({}, function (err, stocks) {
if (err) {
next(err)
return
}
async.map(stocks, function (stock, callback) {
Report.find({ 'isin': stock.isin }).sort('-created').limit(10).exec(function (err, reports) {
if (err) {
next(err)
return
}
stock.reports = reports
return callback(null, stock)
})
}, function (err, stocks) {
if (err) {
next(err)
return
}
res.json(stocks)
})
})
})
What I get is the list of stock objects without the reports... What I want is instead the same stocks, but with the additional attribute reports set.
Most interesting is the fact, that console.log(stock) before and after the assignment stock.reports = reports is the same, but console.log(stock.reports) delivers the actual array of report objects...
I found the solution in this other Stackoverflow topic. The solution was the following:
And because mongoose ignores fields that does not exist in the schema...
Because the reports object was not in my stock model, mongoose ignored it... The solution was to add it to mongoose:
const StockSchema = new mongoose.Schema({
...
reports: {
type: mongoose.Schema.Types.Mixed
},
...
})
Blind shot: Sometimes the "dot. notation" fails if attribute doesn't exist. You could try:
stock['reports'] = reports
instead of
stock.reports = reports

node.js: Return from function not acting as expected

I'm very new to javascript/node.js and I'm having trouble with the following code. This is the handler for API an call. The 2nd code segment is just like the 1st, except there is an additional database lookup Merchant.findOne(...), and therefor the 'newTransaction.save()' function is nested one level deeper.
Both code segments return the 'output' variable value correctly. However, the second code segment does NOT also properly save the 'newTransaction' to the Mongo database.
I'm pretty sure the issue has to do with how/when the code returning from newTransaction.save(function (err, transaction){..} but I can't seem to get it straightened out.
I have been looking all over the internet trying to understand and fix this, with no success. Any help is appreciated...
Here is the older, simpler code that works as expected:
handler : function(request, reply) {
var output = {
"success": true,
"operations": [],
"epoch": Date.now()
};
Terminal.findById(request.payload.deviceNumber, function (err, terminal) {
if (err) {
return reply(Boom.internal('Error looking up terminal.', err));
}
if (terminal) {
ticket.quote("bitstamp", "USD", 1, function (err, exchangeRate) {
if (err) {
console.error(err);
return reply(Boom.internal('Error obtaining ticket quote.', err));
}
var newTransaction = new Transaction({
terminal: request.payload.deviceNumber,
merchant: terminal.merchant,
ccExchangeRate: exchangeRate.buy,
fiatAmtDue: request.payload.transactionValue,
ccAmtDue: ccAmtDueTruncated
});
newTransaction.save(function (err, transaction){
if (err) {
return reply(Boom.internal('Error creating new transaction.', err));
}
output.operations.push(
{
"control": "KeyPairGenControl",
"rand": cc.pseudoRandomBytes(32).toString('hex'),
"follow": {
"url": "/pos/v1/AddressAndEncKey",
"post": {
"transactionId": transaction.transactionId
}
}
}
);
return reply(output);
});
});
} else {
return reply(Boom.internal('Error looking up terminal.', err));
}
});
}
Here is the new code that does NOT save the newTransaction data into the Mongo DB.
handler : function(request, reply) {
var output = {
"success": true,
"operations": [],
"epoch": Date.now()
};
Terminal.findById(request.payload.deviceNumber, function (err, terminal) {
if (err) {
return reply(Boom.internal('Error looking up terminal.', err));
}
if (terminal) {
Merchant.findOne({merchantId: terminal.merchant}, function(err, merchant) {
if (err) {
console.log('Cannot find merchant');
return reply(output);
}
var processor = merchant.backendPaymentProcessor.name;
var localCurrency = merchant.localFiatCurrency;
//###################
ticket.quote(processor, localCurrency, 1, function (err, exchangeRate) {
if (err) {
console.error(err);
return reply(Boom.internal('Error obtaining ticket quote.', err));
}
var newTransaction = new Transaction({
terminal: request.payload.deviceNumber,
merchant: terminal.merchant,
ccExchangeRate: exchangeRate.buy,
fiatAmtDue: request.payload.transactionValue,
ccAmtDue: ccAmtDueTruncated
});
newTransaction.save(function (err, transaction){
if (err) {
return reply(Boom.internal('Error creating new transaction.', err));
}
output.operations.push(
{
"control": "KeyPairGenControl",
"rand": cc.pseudoRandomBytes(32).toString('hex'),
"follow": {
"url": "/pos/v1/AddressAndEncKey",
"post": {
"transactionId": transaction.transactionId
}
}
}
);
return reply(output);
});
//return reply(output);
});
//###################
});
} else {
return reply(Boom.internal('Error looking up terminal.', err));
}
});
}
I did a diff of your 2 version:
Check 1
ticket.quote
Callback are identical for both version
processor, localCurrency are different
Is exchangeRate pass into callback correct?
Check 2
newTransaction.save
newTransaction and callback for .save are setup identical
Check(console.log()) the values used in setting up new Transaction({...})
Check transaction object received by callback
Check/debug the code of Transaction.save().
I don't think the issue is with the code you posted. Both version reached return reply(output); inside newTransaction.save's callback. Very likely issue is inside Transaction class or Transaction.save() logic.
One scenario I can think of is when a transaction failed:
Transaction object is available (even for failed transaction)
Transaction Class / Transaction.save() does not write to db because transaction failed
Transaction.save() pass transaction object to callback, but NOT setting err, even when it should.
Mongoose having a feature to specify the collection name under the schema, or as the third argument when declaring the model. Otherwise it will use the pluralized version given by the name you map to the model.
Mongoose official doc having following statement:
Mongoose by default produces a collection name by passing the model name to the utils.toCollectionName method. This method pluralizes the name. Set this option if you need a different name for your collection.
schema-mapped:
new Schema({ <key>: <value>},
{ collection : '<collection name>' }); // collection name
model-mapped:
mongoose.model('<Model name>',
new Schema({ <key>: <value>}),
'<collection name>'); // collection name
You may also find same here

Categories

Resources