I want to start a transaction inside a pool connection. I found this code example in another StackOverflow question (link):
pool.getConnection(function(err, connection) {
connection.beginTransaction(function(err) {
if (err) { //Transaction Error (Rollback and release connection)
connection.rollback(function() {
connection.release();
//Failure
});
} else {
connection.query('INSERT INTO X SET ?', [X], function(err, results) {
if (err) { //Query Error (Rollback and release connection)
connection.rollback(function() {
connection.release();
//Failure
});
} else {
connection.commit(function(err) {
if (err) {
connection.rollback(function() {
connection.release();
//Failure
});
} else {
connection.release();
//Success
}
});
}
});
}
});
});
However I'm unsure about some specific lines in this example.
My first issue is in line 4 with the first connection.rollback(...). If beginTransaction() throws an error, why would you put a rollback here? If it couldn't start a transaction then there's nothing to rollback to, is there? Or is this just a security measure to close any potentially open transactions that may exist even though an error was thrown?
My second issue is with the rollback inside the commit. If the commit fails for whatever reason, wouldn't the rollback fail here as well? In what scenario could the commit fail but the following rollback succeed? Is there any reason other than network failure for the commit to fail?
The first question, rollback will do nothing. S
The second question depends on the error and database configuration. To be on the safe side, roll back after the error.
Related
Im trying to query a MySQL database and see if a record exists in a table
if it does then render page without inserting to a table
if it does not then call MySQL with another query to write to a table and then render page
What I believe is happening is that the first connection.query runs and before it renders the page when the record exists it tries to insert to table and errors with the below, maybe due to trying to render at the same time but not sure? Any help on solving this will be appreciated.
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client at ServerResponse.setHeader (_http_outgoing.js:558:11)
exports.follow = async (req, res) => {
try {
pool.getConnection(function (error, connection) {
if (error) {
console.log(error);
return;
}
connection.query(checkExists, async (error, results) => {
if (error)
throw error;
return res.status(200).render('search', {
});
})
connection.query(insertIfDoesNotExist, async (error, results) => {
if (error) throw error;
if (loggedin) {
return res.status(200).render('search', {
});
}
})
}
})
} catch (error) {
console.log(error);
}
}
You're right, connection.query() is asynchronous, so you've end up with race condition. checkExists and insertIfDoesNotExist will be queried synchronously, but it will only run its callback when it gets a reply from the database (this is the async part).
So most probably, you end up calling both call back, and trying to res.render twice, which is not correct. Each HTTP request can only have one response.
So how to solve this? You should nest your callback or use await (if you use a promise version of SQL driver) to something like this
exports.follow = async (req, res) => {
try {
pool.getConnection(function (error, connection) {
if (error) {
console.log(error);
return;
}
connection.query(checkExists, async (error, results) => {
if (error) throw error;
if (!results) // condition to check if it exists here!
// Only insert this after you've confirmed that it does not exists
connection.query(insertIfDoesNotExist, async (error, results) => {
if (error) throw error;
if (loggedin) {
return res.status(200).render('search', {});
}
});
return res.status(200).render('search', {});
});
});
} catch (error) {
console.log(error);
}
};
I am building a simple login/register app. I am testing the login part. Whenever I input a wrong login, however, the backend (written in NodeJS and Express) crashes and stops.
Previously my code threw an error whenever the SQL returned error like so:
con.query("SELECT * FROM table_name WHERE username=" + usernameGiven, function(er, result) {
if (er) throw er;
return result;
}
This worked but had the issue as told above. So I removed the throw er part and replaced it with console.log(er) but still the nodeJS would stop and further login attempt will throw a CORS error.
So how can I stop it from doing and make it just log it and continue to the next request?
Edit:
I tried try and catch as shown below:
try {
con.query(sql_query, function(er, result) {
if (er) {
console.log(er);
}
response.json(result);
})
} catch (er) {
console.log(er);
}
However this still shows an error:
/home/x/CodeOver/LoginForm/api/node_modules/mysql/lib/protocol/Parser.js:437
throw err; // Rethrow non-MySQL errors
^
TypeError: Cannot read property 'password' of undefined
at Query.<anonymous> (/home/x/CodeOver/LoginForm/api/api.js:43:37)
at Query.<anonymous> (/home/x/CodeOver/LoginForm/api/node_modules/mysql/lib/Connection.js:526:10)
I'd suggest a little refactoring to make the code more robust to this type of failure.
The try .. catch blocks won't actually catch query errors in this situation, though I'll leave them there in case of any other error.
We should also use parameters here when querying. SQL Injection attacks are bad news and using parameters will also make the query less likely to result in a syntax error.
I'd also fail early and throw back a 400 error if our usernameGiven is not present.
Here is the updated code, I hope this helps you!
try {
if (!usernameGiven) {
response.status(400).send("Bad Request");
return;
}
let sql_query = "select * from table_name where username = ?";
con.query(sql_query, [usernameGiven], function(er, result) {
if (er) {
console.error("Error occurred:", er);
response.status(500).send("Internal Server Error")
} else {
response.json(result);
}
})
} catch (er) {
console.error("Error occurred:", er);
}
I am trying to setup a connection to my MySQL database and so far it is working, but when i run the nodejs application, and i don't read/update anything in my database the connection shuts down with:
Error: Connection lost The server closed the connection
So i searched on google and found this stackoverflow question. But i cannot get it to work, and i think it is because i am working with a class and not a function.
Below is the code i currently have:
class ConnectionFactory {
constructor() {
this.connectAndHandleDisconnect();
}
connectAndHandleDisconnect() {
connection = mysql.createConnection(db_config);
connection.connect(function(err) {
if(err) {
console.log('error when connecting to db:', err);
setTimeout(connectAndHandleDisconnect, 2000);
}
});
connection.on('error', function(err) {
console.log('db error', err);
if(err.code === 'PROTOCOL_CONNECTION_LOST') {
connectAndHandleDisconnect(); // Error pops up from this line
} else {
throw err;
}
});
}
}
And i am getting the following error message:
ReferenceError: connectAndHandleDisconnect is not defined
I have tried to replace the connectAndHandleDisconnect() to this.connectAndHandleDisconnect() and self.connectAndHandleDisconnect() but i have no clue how to find the fix to this error.
You need to access methods via this (or with a reference to the instance that has the method) like so:
class ConnectionFactory {
constructor() {
this.connectAndHandleDisconnect();
}
connectAndHandleDisconnect() {
connection = mysql.createConnection(db_config);
const self = this;
connection.connect(function(err) {
if(err) {
console.log('error when connecting to db:', err);
setTimeout(self.connectAndHandleDisconnect, 2000);
}
});
connection.on('error', function(err) {
console.log('db error', err);
if(err.code === 'PROTOCOL_CONNECTION_LOST') {
self.connectAndHandleDisconnect(); // Error pops up from this line
} else {
throw err;
}
});
}
}
In the below code, users.push used within ‘db.each’ wont work. However, if I move ‘users.push’ outside then it seems to work.
How can I push the new objects from db.each into the users array?
let db = new sqlite3.Database('./db/main.db', (err) => {
if (err) console.error(err.message);
console.log('Connected to the main database.');
});
var users = [];
db.serialize(() => {
db.each(`SELECT email, name FROM users`, (err, row) => {
if (err) console.error(err.message);
let user = {
email: row.email,
name: row.name
}
users.push(user);
});
});
console.log(JSON.stringify(users));
db.close();
I am using express and sqlite3 node packages.
It's because db.serializeand db.each are asynchronous functions (and return immediately, thus executing console.log before the db callbacks are executed).
Here should be a working example :
db.serialize(() => {
db.each(`SELECT email,
name
FROM users`, (err, row) => {
if (err) {
console.error(err.message);
}
let user = {
email : row.email,
name : row.name
}
users.push(user);
console.log(JSON.stringify(users));
db.close();
});
});
First error: asynchronicity not handled properly
As Antoine Chalifour pointed out, you call console.log(JSON.stringify(users)); before users gets modified in the asynchronous callback. Refer to his answer for fix and explanations.
Second error: errors not handled
You wrote if (err) { console.error(err.message); } then go on with the rest of the function. That is bad, because an error might happen and you'd just continue with your program. You should instead write something like:
if (err) {
console.error(err);
return;
}
or:
if (err) throw err;
Maybe I've been staring at my code too long or maybe I'm using too many callbacks elsewhere or something, but I can't figure out the logistics of this one. I know the value contained in rows is correct from using util.inspect(rows) on it inside that callback function. I just don't know how to bring it back up to the surface, so to speak. dbHandler() is called from other functions that pass the params and a result is expected in return.
Any insight would be tremendously helpful.
Using node.js with Express and mysql packages if that makes a difference.
function dbHandler(req, res, params) {
pool_user.getConnection(function (err, connection) {
if (err) {
connection.release();
res.json({
"code" : 100,
"status" : "Error in connection database"
});
return;
}
connection.query(params.query_string, function (err, rows) {
connection.release();
if(!err) {
res.json(rows); // <<--- I want to get ahold of the value of `rows`
//console.log(util.inspect(rows));
}
});
connection.on('error', function(err) {
res.json({
"code" : 100,
"status" : "Error in connection database"
});
return;
});
});
}
Trying to return the result of an async function is an anti-pattern and will never work.
Instead, restructure the calling code.
Not Like This - the anonymous callback does not return to the caller
new_data = api_request_data(auth, request,
function(error, data){
return data;
});
store(new_data);
update_screen(new_data);
Better
api_request_data(auth, request,
function(error, data){
store(data);
update_screen(data);
});
Best
api_request_data(auth, request,
function(error, data){
if (error){
console.log(error);
return;
}
store(data);
update_screen(data);
});
In your code, the data pipeline looks ok.
connection.query(params.query_string, function (err, rows) {
connection.release();
if(!err) {
res.json(rows); // <<--- I want to get ahold of the value of `rows`
//console.log(util.inspect(rows));
}
});
The code res.json(rows); in a node.js application will queue a JSON string containing the data in rows for writing to the current web connection referenced in res.
Your question suggests that the issue is that this line does not return data to the caller of dbHandler(), but no trained node.js developer would expect that to happen. The code looks correct for delivering the data in rows to an over-the-web client of the node.js application.