Functions out of order when getting results from SQL - javascript

When an existing SQL record exists I want to use it rather than adding another, but if it doesn't yet exist I want to add it. The issue I am having is that when my Node.js app's endpoint is called it's not executing in the correct order so the SQL lookup to find existing records is happening after I check it's length to see if I need to add a new record.
// it does this second
let existingGet = "SELECT * FROM Items WHERE name = '" + productName + "'";
let existingItem = async () => {
db.query(existingGet, function (err, rows, fields) {
return rows;
});
};
// it does this first
if (await existingItem().length > 0) {
// Existing found, use existing
itemId = existingItem.ID;
} else {
// Item not found, create new
var sql = "INSERT INTO Items (...) VALUES (...)";
await db.query(sql, async function (err, result) {
itemId = existingItem.affectedRows.ID;
});
}
The desired outcome is that it does the first section before the second section because the second section needs the results of the first.

Try removing the outer brackets so that the existingItem will receive the result from the query
// it does this second
let existingGet = "SELECT * FROM Items WHERE name = '" + productName + "'";
//removed outer brackets
let existingItem = async () =>
db.query(existingGet, function (err, rows, fields) {
return rows;
});
// it does this first
if (await existingItem().length > 0) {
// Existing found, use existing
itemId = existingItem.ID;
} else {
// Item not found, create new
var sql = "INSERT INTO Items (...) VALUES (...)";
await db.query(sql, async function (err, result) {
itemId = existingItem.affectedRows.ID;
});
}

Do it in a single db call using sql command
"INSERT INTO Items (...) VALUES (...)
WHERE NOT EXISTS (SELECT 1 FROM Items WHERE name = ...)"
And use sql command parameters instead of concatenation to avoid sql injection.

Related

Can not insert id number using postman

This is the code in nodejs:
//Insert an employee
app.post('/employees', (req, res) => {
let emp = req.body;
var sql = "SET #EmpID = ?; SET #Name = ?; SET #EmpCode = ?; SET #Salary = ?; \
CALL EmployeeAddOrEdit(#EmpID,#Name,#EmpCode,#Salary);";
mysqlConnection.query(sql,[emp.EmpID, emp.Name, emp.EmpCode, emp.Salary],(err, rows, fields) => {
if (!err)
rows.forEach(element => {
if (element.constructor == Array)
res.send('Inserted employee id : ' + element[0].EmpID);
});
else
console.log(err);
})
});
I don't know if you will be able to see the image.
In the postman it keeps giving me this message:
Inserted employee id : null
Not getting the id number. In mysql workbench created the db with all the data proper settings, had 4 names, the delete process with postman was successful, but can't simply insert the data
You cannot return more than one response. So you can't use res.send in a loop.
But you can create an array and add all the ids to the array and present them in the response.
//Insert an employee
app.post('/employees', (req, res) => {
let emp = req.body;
var sql = "SET #EmpID = ?; SET #Name = ?; SET #EmpCode = ?; SET #Salary = ?; \
CALL EmployeeAddOrEdit(#EmpID,#Name,#EmpCode,#Salary);";
const container = []; //Create an empty array
mysqlConnection.query(sql, [emp.EmpID, emp.Name, emp.EmpCode, emp.Salary], (err, rows, fields) => {
if (!err) {
rows.forEach(element => {
if (element.constructor == Array)
container.push(row.insertedId); //Push the ids to the array
});
res.send('Inserted employee ids : ' + container.join());
}
else
console.log(err);
})
});

In nodejs SQLite3 .each() method, how can I add mulitple arguments to work with multiple "WHERE ... = ?" queries?

I have a table that has a column named CRS_TITLE and I would like to search a list of course titles within that column. Right now my code only works with one argument at a time. I suppose I can write a simple loop, and during each iteration I can just call db.each() again with a new class name. I know this way is inefficient, so I would like to check all the arguments within one pass.
Things I have tried:
1) Adding another '?' in WHERE and other variations.
2) Using the new spread operator from javascript like [...classes] when passing in the arguments.
None of the above works due to syntax error.
Anyways, here is my code below.
let sqlite3 = require('sqlite3').verbose()
// open the database
let db = new sqlite3.Database('./classes.db', (err) => {
if (err) {
console.error(err.message);
}
console.log('Connected to the classes database.');
});
let sql = `SELECT CRS_CDE course_code,
Column3 start_time,
Column4 end_time
FROM spring_schedule
WHERE CRS_TITLE = ?`;
// I want to do multiple "WHERE = ?" here.
// with multiple arguments
let className1 = 'Spanish I'
// I want to change the line above to this instead.
// let classNames = ['Accounting I', 'English', 'Math'...etc]
db.each(sql, [className1]/*pass an array of items to look up instead of just 1 item.*/, (err, row) => {
if (err) {
throw err;
}
console.log(`${row.course_code} ${row.start_time} - ${row.end_time}`);
// Will use this result to update frontend.
});
// close the database connection
db.close();
EDITED: working code below.
let sql = `SELECT CRS_CDE course_code,
Column3 start_time,
Column4 end_time
FROM spring_schedule
WHERE CRS_TITLE IN `;
let classNames = ['Spanish I', 'Accounting II', 'College Algebra']
let where = '(?' + ',?'.repeat(classNames.length-1) + ')';
sql += where;
db.each(sql, [...classNames], (err, row) => {
if (err) {
throw err;
}
console.log(`${row.course_code} ${row.start_time} - ${row.end_time}`);
// Will use this result to update frontend.
});
db.close();
You may try building a dynamic WHERE IN clause using the exact number of parameters to match your input array:
let sql = `SELECT CRS_CDE course_code, Column3 start_time, Column4 end_time
FROM spring_schedule
WHERE CRS_TITLE IN `;
let classNames = ['Accounting I', 'English', 'Math'];
let where = '(?' + ',?'.repeat(classNames.length-1) + ')';
sql += where;
db.all(sql, [], (err, rows) => {
if (err) {
throw err;
}
rows.forEach((row) => {
console.log(row.name);
});
});
db.close();

Creating my own sql wrapper for nodejs/express

I'm trying to create my own wrapper for mysql for my nodejs application. I have two questions here one of which theres a work around and one where I'm unsure what to do as my javascript skills are still in the learning phase.
First thing: As of right now when you navigate to /api/finance it directs you to the finance controller and the index method. This is currently just for testing purposes trying to figure out how to this kind of stuff.
FinanceController:
const sql = require('../../sql.js')
module.exports = {
index: (req, res, next) => {
sql.get('test').then((result) => {
res.status(200).json(result);
})
}
}
sql.js
const mysql = require('mysql');
const { DB } = require('./config')
var connection = mysql.createConnection(DB)
module.exports = {
get: function(table, columns = '*') {
return new Promise((resolve, reject) => {
connection.query('SELECT ?? FROM ?? ', [columns, table], function (error, results, fields) {
if (error) throw error;
resolve(results);
});
})
},
all: function(table) {
return new Promise((resolve, reject) => {
connection.query('SELECT * FROM ?? ', table, function (error, results, fields) {
if (error) throw error;
resolve(results);
});
})
},
where: function(){
console.log('where called')
}
}
As you can see, I have a get() and all(). get() allows you to pass the table name and an array of columns for example: ['id', 'name'] would get you the id column and name column. columns = '*' was an attempt on being able to use one function to either get all columns of the table or specify specific columns however it returns an error: Unknown column in 'field list' so all() was my "workaround" however i'd like it to be one function.
Next I can't figure out how to stack/pipe methods? if thats the word.
The goal here would be so I could call the function like this:
index: (req, res, next) => {
sql.all('test').where().then((result) => {
res.status(200).json(result);
})
}
}
obviously within the .where() I would have it like: .where('id', '=', 'userID') or something along those lines.
however I'm unsure on how to go about doing that and would like some guidance if its possible. I receive the error: sql.all(...).where is not a function
Instead of immediately launching the SQL, you should simply register the provided information in an instance object (having the same methods) and return that object, and let each method enrich the SQL until the end of the chain is reached and you call a method that will launch the SQL.
The object that is passed from one method to the next (as this) maintains state, and collects the different elements of the SQL statement.
Here is one way to do it.
NB: In this demo I used a mock-object for connection. This mock object will not actually connect to a database. Its query method will just produce the final SQL (with all ? resolved) instead of a real data set.
// Mock connection object for this snippet only. Just produces the final SQL, not the result
var connection = {
query(sql, params, callback) {
let i = 0;
sql = sql.replace(/\?\??/g, (m) => {
if (m.length > 1) return [].concat(params[i++]).map(p => "`" + p + "`").join(", ");
if (typeof params[i] === "string") return "'" + params[i++].replace(/'/g, "''") + "'";
return params[i++];
});
setTimeout(callback(null, sql));
}
}
// Function to create an instance when a first method call is made on the `sql` object directly
// Chained method calls will keep using that instance
function getInstance(inst) {
if (inst.columns) return inst; // Keep using same instance in the chain
inst = Object.create(inst); // No instance yet: create one
inst.table = null;
inst.params = [];
inst.columns = [];
inst.conditions = [];
inst.order = [];
return inst;
}
// This sql object serves a starting point as well
// as proto object for the instance object that
// passes through the chain:
var sql = {
from(table) {
let inst = getInstance(this);
inst.table = table;
return inst;
},
select(...columns) {
let inst = getInstance(this);
inst.columns = inst.columns.concat(columns);
return inst;
},
where(column, cmp, value) {
if (!["<",">","<=",">=","=","!="].includes(cmp)) throw "invalid operator";
let inst = getInstance(this);
inst.params.push(column, value);
inst.conditions.push(cmp);
return inst;
},
orderBy(...columns) {
let inst = getInstance(this);
inst.order = inst.order.concat(columns);
return inst;
},
promise() {
if (!this.table) throw "no table specified";
// build SQL and parameter list
let sql = "SELECT *";
let params = [];
if (this.columns.length && this.columns != "*") {
sql = "SELECT ??";
params.push(this.columns);
}
sql += " FROM ??";
params.push(this.table);
if (this.conditions.length) {
sql += " WHERE " + this.conditions.map(cmp => `?? ${cmp} ?`).join(" AND ");
params.push(...this.params);
}
if (this.order.length) {
sql += " ORDER BY ??";
params.push(this.order);
}
return new Promise(resolve => {
connection.query(sql, params, function (error, results) {
if (error) throw error;
resolve(results);
});
});
}
};
// demo
sql.from("customer")
.select("id", "name")
.where("name", ">", "john")
.where("name", "<", "thomas")
.orderBy("name", "id")
.promise()
.then(console.log);
Note that in this implementation it does not matter in which order you chain the from, select, where and order method calls. You could even do the following if you wanted to:
sql .orderBy("name", "id")
.where("name", ">", "john")
.from("customer")
.where("name", "<", "thomas")
.select("id", "name")
.promise()
.then(console.log);

Inserting multiple records in mySQL with NodeJS preventing injection and getting each record's ID

I have an ajax request that adds one or multiple records to a table (the following code is server side):
app.post('/saveLesson',function(req, res) {
let sections = JSON.parse(req.body.sections);
let sql = 'INSERT INTO sections (title, content, duration) VALUES ';
for (let i = 0; i < sections.length; i++) {
if (i == sections.length-1) {
sql += '("' + sections[i].title + '","' + sections[i].content + '","' + sections[i].duration + '");';
} else {
sql += '("' + sections[i].title + '","' + sections[i].content + '","' + sections[i].duration + '"),';
}
}
connection.query(sql,
function (error, result) {
if (error) throw error;
});
});
I want to prevent SQL injection, but I'm not sure how to do it for multiple records.
Generally I know that I would have to build my sql statement as follows:
connection.query("SELECT * FROM bank_accounts WHERE dob = ? AND bank_account = ?",
[
req.body.dob,
req.body.account_number
],
function(error, results) {
}
);
But I'm not sure how to achieve this with multiple records (not knowing how many they are). Is the .query parameter just a regular array?
Also, I need to store somewhere the created IDs and send them back to the client page. How can I achieve this? Thank you.
***************************** UPDATE *****************************
Although someone posted a solution, I thought this might be useful. With the following code you can add multiple records preventing SQL injections.
app.post('/saveLesson',function(req, res) {
let sections = JSON.parse(req.body.sections);
console.log(sections);
let sql = 'INSERT INTO sections (title, duration, content) VALUES ';
// I make a new array to pass the list of values to the query
let sectionsParamList = [];
for (let i = 0; i < sections.length; i++) {
if (i == sections.length-1) {
sql += '(?,?,?);';
} else {
sql += '(?,?,?),';
}
sectionsParamList.push(sections[i].title);
sectionsParamList.push(sections[i].duration);
sectionsParamList.push(sections[i].content);
}
connection.query(sql, sectionsParamList,
function (error, result) {
if (error) throw error;
});
});
The way MySQL works, when you perform a multi-row INSERT operation like the one you propose, you only get back the automatically generated unique id of the last row inserted. It shows up in your result object as result.insertId. Don't try to guess the id values of the other rows, by subtraction for example, because there's no guarantee of that.
The fact that you need that id for every row you insert means you should not use a multi-row insert, but rather a sequence of single row inserts. That neatly solves your SQL injection issue too.
But you will have to figure out how to do a sequence of INSERT operations. You may want to do it with an async / await / promise setup. Something like this, not debugged.
/* do one insert with a Promise so you can await it */
function doInsert (section, connection) {
const values = [section.title, section.content, section.duration];
return new Promise( function ( resolve, reject ) {
const sql = "INSERT INTO sections (title, content, duration) VALUES (?,?,?);"
connection.query (sql, values, function ( error, result ) {
if (error) reject (error)
resolve (result.insertId)
} )
} )
}
/* do all the inserts, awaiting each one */
async function doInserts (sections, connection) {
let ids = []
for (let i = 0; i < sections.length; i++) {
const id = await doInsert (sections[i], connection)
ids.push(id)
}
return ids
}
/* handle your post */
app.post('/saveLesson',function(req, res) {
let sections = JSON.parse(req.body.sections)
/* get the result back from an async function with .then / .catch */
doInserts (sections, connection)
.then (function (resultIds) {
/* respond with the id values in a JSON object */
res.status(200).json(resultIds)
} )
.catch ( function (error) {
/* respond with an error */
res.status(500).json(error)
} )
} )
These async / await and Promise language constructions are really worth your trouble to learn, if you don't already know them.

sql is only returning 1 row, how do I access the rest?

sql.get(`SELECT * FROM scores ORDER BY points DESC`).then(allScores => {
console.log(allScores);
});
This should give me all of the rows ordered by points, but I'm only getting the first row.
How do I access all of the other rows using javascript?
Use sql.all instead of sql.get refer http://www.sqlitetutorial.net/sqlite-nodejs/query/
Define your db instance as follows:
const sqlite3 = require('sqlite3').verbose();
// open the database
let db = new sqlite3.Database('./db/yourCoolDB');
And then,
let sql = `SELECT * FROM scores ORDER BY points DESC`;
db.all(sql, [], (err, rows) => {
if (err) {
throw err;
}
rows.forEach((row) => {
console.log(row.Id); // You can use row.yourAnotherAttributeName
});
});

Categories