Mongodb inserts not completing successfully (using node.js) - mongodb

I've got a node.js script that loads an XML file. It loops through each element in the Mongo array and says that they're all getting inserted correctly, but when the script has completed a check of db.collection.count(); tells me that far fewer records have been inserted into the database than the number expected.
How can I make mongo and node.js play nicely with inserts?
GrabRss = function() {
var http = require('http');
var sys = require('sys');
var xml2js = require('xml2js');
var fs = require('fs');
var Db = require('../lib/mongodb').Db,
Conn = require('../lib/mongodb').Connection,
Server = require('../lib/mongodb').Server,
// BSON = require('../lib/mongodb').BSONPure;
BSON = require('../lib/mongodb').BSONNative;
var data;
var checked = 0;
var len = 0;
GotResponse = function(res) {
var ResponseBody = "";
res.on('data', DoChunk);
res.on('end', EndResponse);
function DoChunk(chunk){
ResponseBody += chunk;
}
function EndResponse() {
//console.log(ResponseBody);
var parser = new xml2js.Parser();
parser.addListener('end', GotRSSObject);
parser.parseString(ResponseBody);
}
}
GotError = function(e) {
console.log("Got error: " + e.message);
}
GotRSSObject = function(r){
items = r.item;
//console.log(sys.inspect(r));
var db = new Db('myrssdb', new Server('localhost', 27017, {}), {native_parser:false});
db.open(function(err, db){
db.collection('items', function(err, col) {
len = movies.length;
for (i in items) {
SaveItem(items[i], col);
}
});
});
}
SaveItem = function(m, c) {
/* REPLACE FROM HERE IN ANSWER */
c.find({'id': m.id}, function(err, cursor){
cursor.nextObject(function(err, doc) {
if (doc == null) {
c.insert(m, function(err, docs) {
docs.forEach(function(doc) {
console.log('Saved: '+doc.id+' '+doc.keywords);
});
});
} else {
console.log('Skipped: '+m.id);
}
if (++checked >= len) {
process.exit(0);
}
});
});
/* REPLACE TO HERE IN ANSWER */
}
//http.get(options, GotResponse).on('error', GotError);
var x2js = new xml2js.Parser();
fs.readFile('/home/ubuntu/myrss.rss', function(err, data) {
x2js.parseString(data);
});
x2js.addListener('end', GotRSSObject);
}
GrabRss();
As requested, the code is above. The file is read locally (though is used to be an HTTP request, but it's a 25 meg file now, lots of RSS records)
I just ran the file with some ~10k records in it and a count of the items in the mongoDB after the script has run is about 800 items.
As per the answer I replaced the insert code:
with:
c.update({'id': m.id}, {$set: m}, {upsert: true, safe: true}, function(err){
if (err) console.warn(err.message);
else console.log(m.keywords);
if (++checked >= len) {
console.log(len);
//process.exit(0);
process.exit(0);
}
});

By default, MongoDB writes do not check for an error.
You need to set safe:true in the options to your insert, as explained in the documentation for node-mongodb-native:
var collection = new mongodb.Collection(client, 'test_collection');
collection.insert({hello: 'world'}, {safe:true},
function(err, objects) {
if (err) console.warn(err.message);
if (err && err.message.indexOf('E11000 ') !== -1) {
// this _id was already inserted in the database
}
});
Otherwise your callback will not be invoked for errors and your client won't know about them.
You probably also want to look at upserts and updates, as it is incredibly inefficient to do find & insert if null in a loop.
Instead, upsert will update if the matching document exists, otherwise it will insert a new one. An explanation on how to do this in Node is in the documentaiton for the driver.

Related

Order of save() and find() in NodeJS with MongoDB

I'm trying to create a new record in my MongoDB ("thisPlayer") and save it to my database, then find all records in my database (including the new one) and render them.
I am having trouble understanding why my save() function actually occurs after my find() function. When this code executes, the find() function does not include my new thisPlayer record. However, after the find() runs, the save occurs -- the record is saved to the database AFTER the find() ran.
Thanks in advance!
const playerNumber = async function countPlayers() {
return new Promise((resolve, reject) => {
Player.count(function(err, numOfDocs) {
err ? reject(err) : resolve(numOfDocs);
console.log('I have '+numOfDocs+' documents in my collection');
});
});
}
async function playerProfile() {
var count = await playerNumber();
console.log("count already in db: "+ count);
if (count===0) {
teamCaptain=1;
} else {teamCaptain=0};
count++;
const thisPlayer = new Player({
playerNum: count,
playerName: Name,
});
thisPlayer.save();
Player.find({}, function(err, playaz){
var playerOne;
if (playaz.length > 0) {
playerOne = playaz[0].playerName;
} else {
playerOne = "";
}
res.renderPjax("leavetakings",
{player1: "1: " + playerOne}
);
});
}
playerProfile();
You need to use await
for example.
await Player.find({})

How to check if value already exists in the data received from api before inserting it into db

I am having hard times trying to write data received from a api to db.
I successfully got data and then have to write it to db. The point is to check whether the quote is already exists in my collection.
The problem I am dealing with is that every value gets inserted in my collection, not regarding if it exists or not.
const { MongoClient } = require('mongodb')
const mongoUrl = 'mongodb://localhost/kanye_quotes'
async function connectToDb() {
const client = new MongoClient(mongoUrl, { useNewUrlParser: true })
await client.connect()
db = client.db()
}
async function addQuote(data) {
await connectToDb()
try {
const collection = db.collection('quotes')
let quotes = [];
quotes = await collection.find({}).toArray()
if (quotes = []) { // I added this piece of code because if not check for [], no values will be inserted
collection.insertOne(data, (err, result) => {
if (err) {
return
}
console.log(result.insertedId);
return
})
}
quotes.forEach(quote => {
if (quote.quote !== data.quote) { // I compare received data with data in collection, it actually works fine(the comparison works as it supposed to)
collection.insertOne(data, (err, result) => {
if (err) {
return
}
console.log(result.insertedId);
})
} else console.log('repeated value found'); // repeated value gets inserted. Why?
})
}
catch (err) {
console.log(err)
}
}
Hi it's probably better to set unique: true indexing on your schema. That way you won't have duplicated values.

Waiting for meteor cursor in method

I have a large aggrogate query that required me to pass "allowDiskUse: true" as an option. This would not work with the aggegate as described here:
https://github.com/meteorhacks/meteor-aggregate/issues/11
My meteor method is defined here. When I call the method I need to wait for ondata to complete before anything is returned to the client, but nothing I try allows me to get that data in a safe way up to the front end.
Meteor.methods({
'getSummary': function (dept,startDate,endDate,filterType) {
f = myQuery(startdate,enddate,dayFinalGroup);
f.on("data", Meteor.bindEnvironment(function(row) {
//load an array or something here to return
}));
f.once("end", Meteor.bindEnvironment(function() {
// tidy up, in my case end the stream
}));
//here I'd return the array loaded
},
});
This is my front end.
Meteor.call(
'getSummary',0,Session.get('start_date'),Session.get('end_date'),1,
function(error, result){
if(error){
console.log(error);
} else {
Session.set('sumTotals',result);
}
}
);
Finally Got it. I utilized wrapSync
'getSummary': function (dept,startDate,endDate,filterType) {
console.log(dept);
console.log(startDate);
console.log(endDate);
console.log(filterType);
var startdate = new Date(startDate);
var enddate = new Date(endDate);
var arr = [];
f = myQuery(startdate,enddate,dayFinalGroup);
var fetchCursor = Meteor.wrapAsync(function fetchCursor (cursor, cb) {
cursor.each(function (err, doc) {
if (err) return cb(err);
if (!doc) return cb(null, { done: true }); // no more documents
arr.push(doc);
});
});
var myData = fetchCursor(f);
return arr;

mongodb update not working to replace a document (but $set does)

From homework 2_2 of the M101JS course at university.mongodb.com.
Write a program that finds the document with the highest recorded temperature for each state, and adds a "month_high" field for that document, setting its value to true.
The following code should work to update the correct doc in the collection adding "month_high". And the problem is here:
//db.collection('data').update(query, setMonthHigh, function(err, updated) {//OK
db.collection('data').update(query, doc, function(err, updated) {// NOT OK, dunno why
The 2nd update line does not save anything. But if I use the commented update above, it works.
Does anyone have an idea why the 2nd update line isn't working (the one that replaces the whole doc)?
var MongoClient = require('mongodb').MongoClient;
MongoClient.connect('mongodb://localhost:27017/weather', function(err, db) {
if(err) throw err;
var options = { 'sort' : [ ['State', 1], ['Temperature', -1] ]};
var lastDoc = null;
var setMonthHigh = {'$set':{'month_high':true}};
var cur = db.collection('data').find({}, {},options);
//var cur = db.collection('data').find({'month_high':true}, {},options);
cur.each(function(err, doc) {
if(err) throw err;
if(doc == null) {
console.log("Document done");
//return db.close();
db.close();
process.exit(0);
}
var query = {'_id' : doc._id};
//db.collection('data').update({'month_high':true}, {$unset:{'month_high':1}}, function(err, updated) {});
//console.dir(doc);
if ( ( lastDoc != null && lastDoc.State != doc.State ) ||
lastDoc == null )
{
doc['month_high'] = 'true';
//db.collection('data').update(query, setMonthHigh, function(err, updated) {//OK
db.collection('data').update(query, doc, function(err, updated) {
// NOT OK, dunno why
if(err) throw err;
console.dir("Update "+updated);
});
console.dir(doc);
}
lastDoc = doc;
});
});
Also, I'm told that using process.exit(0) is a bad hack, but it was the only way I could exit the code without throwing exceptions on return db.close();. If anyone knows a way to exit the code gracefully, that would be great too.
First of all, you have made a mistake in part of your code responsible for updating a database.
db.collection('data').update is an asynchronous function. It means that you cannot execute db.close() without knowing if all updates have been completed. For more details please read: How can I use a cursor.forEach() in MongoDB using Node.js?
Both updates works. However there is one difference between two of them:
db.collection('data').update(query, setMonthHigh, function(err, updated)
updates records with a new field: 'month_high': true <- boolean value
Query to find these records is: db.data.find({'month_high': true})
db.collection('data').update(query, doc, function(err, updated)
updates records with a new field: 'month_high': 'true' <- string value
Query to find these records is: db.data.find({'month_high': 'true'})
Your updated code:
Before run it, first install async library (npm install async)
var async = require('async');
var MongoClient = require('mongodb').MongoClient;
MongoClient.connect('mongodb://localhost:27017/weather', function(err, db) {
if(err) throw err;
var q = async.queue(function (doc, callback) {
db.collection('data').update({_id: doc._id}, doc, callback);
}, Infinity);
var options = { 'sort' : [ ['State', 1], ['Temperature', -1] ]};
var lastDoc = null;
var cur = db.collection('data').find({}, {},options);
cur.each(function(err, doc) {
if(err) throw err;
if(doc === null) return;
if ( ( lastDoc !== null && lastDoc.State !== doc.State ) ||
lastDoc === null )
{
doc['month_high'] = true;
q.push(doc, function(err, updated) {
if(err) throw err;
console.log("Update "+updated);
});
}
lastDoc = doc;
});
q.drain = function() {
if (cur.isClosed()) {
console.log('Document done');
db.close();
}
}
});

Creating new Meteor collections on the fly

Is it possible to create new Meteor collections on-the-fly? I'd like to create foo_bar or bar_bar depending on some pathname which should be a global variable I suppose (so I can access it throughout my whole application).
Something like:
var prefix = window.location.pathname.replace(/^\/([^\/]*).*$/, '$1');
var Bar = new Meteor.Collection(prefix+'_bar');
The thing here is that I should get my prefix variable from URL, so if i declare it outside of if (Meteor.isClient) I get an error: ReferenceError: window is not defined. Is it possible to do something like that at all?
Edit : Using the first iteration of Akshats answer my project js : http://pastie.org/6411287
I'm not entirely certain this will work:
You need it in two pieces, the first to load collections you've set up before (on both the client and server)
var collections = {};
var mysettings = new Meteor.Collection('settings') //use your settings
//Startup
Collectionlist = mysettings.find({type:'collection'});
Collectionlist.forEach(function(doc) {
collections[doc.name] = new Meteor.Collection(doc.name);
})'
And you need a bit to add the collections on the server:
Meteor.methods({
'create_server_col' : function(collectionname) {
mysettings.insert({type:'collection', name: collectionname});
newcollections[collectionname] = new Collection(collectionname);
return true;
}
});
And you need to create them on the client:
//Create the collection:
Meteor.call('create_server_col', 'My New Collection Name', function(err,result) {
if(result) {
alert("Collection made");
}
else
{
console.log(err);
}
}
Again, this is all untested so I'm just giving it a shot hopefully it works.
EDIT
Perhaps the below should work, I've added a couple of checks to see if the collection exists first. Please could you run meteor reset before you use it to sort bugs from the code above:
var collections = {};
var mysettings = new Meteor.Collection('settings')
if (Meteor.isClient) {
Meteor.startup(function() {
Collectionlist = mysettings.find({type:'collection'});
Collectionlist.forEach(function(doc) {
eval("var "+doc.name+" = new Meteor.Collection("+doc.name+"));
});
});
Template.hello.greeting = function () {
return "Welcome to testColl.";
};
var collectionname=prompt("Enter a collection name to create:","collection name")
create_collection(collectionname);
function create_collection(name) {
Meteor.call('create_server_col', 'tempcoll', function(err,result) {
if(!err) {
if(result) {
//make sure name is safe
eval("var "+name+" = new Meteor.Collection('"+name+"'));
alert("Collection made");
console.log(result);
console.log(collections);
} else {
alert("This collection already exists");
}
}
else
{
alert("Error see console");
console.log(err);
}
});
}
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
Collectionlist = mysettings.find({type:'collection'});
Collectionlist.forEach(function(doc) {
collections[doc.name] = new Meteor.Collection(doc.name);
});
});
Meteor.methods({
'create_server_col' : function(collectionname) {
if(!mysettings.findOne({type:'collection', name: collectionname})) {
mysettings.insert({type:'collection', name: collectionname});
collections[collectionname] = new Meteor.Collection(collectionname);
return true;
}
else
{
return false; //Collection already exists
}
}
});
}
Also make sure your names are javascript escaped.
Things got much easier:
var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;
db.createCollection("COLLECTION_NAME", (err, res) => {
console.log(res);
});
Run this in your server method.