Building a kitchen sink query. Error passing # parameters to SQL Server - node.js

Im building a kitchen sink query on NODE, where several parameters, for about 20 tables are being passed. They all form a single SQL Query:
if (data.basics.memberId) { sqlString += ` memberid=#memberId and` };
etc...
Im passing several parameters (about 40), but Im not able to pass the parameters when sending the query to the database:
const pool = await utils.poolPromise
const recordset = await pool.request()
//basics
if (data.basics.memberId) {
.input('memberId', utils.sql.Int, data.basics.memberId)
};
.query(sqlString)
if (recordset.rowsAffected[0] > 0) {
...
...
...
}
and Im getting the error:
Declaration or statement expected.
at
.input('memberId', utils.sql.Int, data.basics.memberId)
and
.query(sqlString)
I've read parameters are not the right way to build dynamic queries, so I thought of using ES6, as
if (data.basics.memberId) { sqlString += ` memberid=${data.basics.memberId} and` };
But I've read that ES6 does not prevent SQL injection on SQL Server.
The question is how do I approach this scenario where the SQL string is variable and changes based on the user selection?
Thanks.

Without a query builder library (e.g. Knex), you'll need to
form the SQL query (as a string)
put the parameters into place
e.g. something like this:
const whereClauses = [];
const inputs = {}; // map input name -> [type, value]
// (1) Process data into WHERE clauses and inputs
if (data.basics.memberId) {
whereClauses.push(`memberid=#memberId`);
inputs.memberId = [utils.sql.Int, data.basics.memberId];
}
if (data.basics.somethingElse) {
whereClauses.push(`somethingElse=#somethingElse`);
inputs.somethingElse = [utils.sql.Int, data.basics.somethingElse];
}
// (etc..., you could use a loop or something for the above)
// (2) Form the final SQL query
const sqlStringBits = ["SELECT * FROM ... WHERE "];
for (let whereClause of whereClauses) {
sqlStringBits.push(whereClause);
sqlStringBits.push("AND");
}
if (whereClauses.length) {
sqlStringBits.pop(); // Remove final AND if we had added one
}
const sqlString = sqlStringBits.join(" ");
// (3) Form the `request` and put the inputs into place
const pool = await utils.poolPromise;
let request = pool.request();
for (let inputName in inputs) {
request = request.input(inputName, ...inputs[inputName]);
}
// (4) Use the request (endowed with inputs) with the query
const recordSet = await request.query(sqlString);
// (5) Do something with the record set!

Related

Array as sql request parameter

I handle my SQL queries like this (which works):
const sql = require("mssql/msnodesqlv8");
const conn = new sql.ConnectionPool({
database: "MyDatabase",
server: "localhost\\SQLEXPRESS",
driver: "msnodesqlv8",
options: {
trustedConnection: true
}
});
async function runSQLQuery(insertReq, query) {
try {
await conn.connect();
var result = await insertReq.query(query);
await conn.close();
return result;
} catch (ex) {
console.log(ex);
return undefined;
} finally {
if (conn.connected)
conn.close();
}
}
and create the querys like this (which also works):
exports.getClientByID = async function (ID) {
var insertReq = conn.request();
insertReq.input("ID", sql.UniqueIdentifier, ID);
const request = await runSQLQuery(insertReq, `SELECT TOP (1) * FROM ${ClientTabel} WHERE ID = #ID`);
return request.recordset[0]
};
But now I want to add an Array as Parameter like this (and this doesn't work):
exports.getUsersWithProperty = async function (properties) {
var insertReq = conn.request();
insertReq.input("properties", sql.NVarChar, properties);
const request = await runSQLQuery(insertReq, `SELECT * FROM ${ClientTabel} WHERE Property IN #properties`);
return request.recordset;
};
But with this I only get a
Request Error" Wrong Syntax near "#properties".
I guess the type sql.NVarChar is wrong but I don't know what the right type is. Whats the solution for this?
OK, for a start, you need to add brackets around the values.
An IN clause is like this:
WHERE somecolumn IN ('value1','value2','value3')
you'll also have to make sure that after your #properties string replacement is done, you end up with a statement that looks like the clause above, with the quotes and commas in the right places.
Alternately, if #properties is a string like Value1,Value2,Value3 and so on, you could pass it to a T-SQL table-valued function that returns a table like this:
WHERE somecolumn IN dbo.ExtractStringList(#StringList)

How to use other repos in a transactions

I have several repos with methods and some of these methods use transaction (.tx).
For example, in my DevicesRepository below, the 'add' method have to insert a new Device, which means:
1. Insert a System and return the ID (SystemsRepository)
2. insert the device with the returner systemId and get the new id
3. Insert other pieces (other repos) that uses the deviceId
My problem is that in that transaction I don't know how to access to the other repo methods.
I could use the other repos from my Database object (Database.systems.add, Database.OtherRepo.add, [...]), but if I do that
tx doc
When invoked on the root Database object, the method allocates the connection from the pool, executes the callback, and once finished - releases the connection back to the pool. However, when invoked inside another task or transaction, the method reuses the parent connection.
task doc
When executing more than one request at a time, one should allocate and release the connection only once, while executing all the required queries within the same connection session. More importantly, a transaction can only work within a single connection.
Thanks! :)
P.S : I can add how I initialize the DB and repos
./db/repos/devices.js
'use strict';
var Database = null, pgp = null, Collections = null;
async function add(params) {
// I can use Database.systems.add
return Database.tx('Insert-New-Device', async function(transaction) {
let device = params.data.device;
const system = await transaction.systems.add(params);
device.systemid = system.systemId;
const query = pgp.helpers.insert(device, Collections.insert);
query += " RETURNING deviceId";
device.deviceId = await transaction.one(query);
const OtherRepoInsert = await transaction.otherRepos.add(params);
device.otherRepos.id = OtherRepoInsert.otherReposId;
return device
})
.then(data => { return data; })
.catch(ex => { throw new Error(ex); });
}
function createColumnsets() { /* hidden for brevity (almost the same as the pg-promise-demo */ }
const DevicesRepository = {
add: add
};
module.exports = (db) => {
Database = db;
pgp = db.$config.pgp;
Collections = createColumnsets();
return DevicesRepository;
}
./db/repos/systems.js
'use strict';
var Database = null, pgp = null, Collections = null;
async function add(params) {
var system = params.data.system;
system.archid=2;
system.distributionid=3;
var query = pgp.helpers.insert(system, Collections.insert);
if(params.return) query += " RETURNING *";
return Database.any(query)
.then(data => { return data; })
.catch(ex => { throw new Error(ex); });
}
function createColumnsets() { /* hidden for brevity (almost the same as the pg-promise-demo */ }
const SystemsRepository = {
add: add
};
module.exports = (db) => {
Database = db;
pgp = db.$config.pgp;
Collections = createColumnsets();
return SystemsRepository;
}
I found the real problem.
If you go to my first post, you can see that each of my repo exports an initialization function :
1. which is called by the pg-promise 'extend' event
2. which takes one param : the context
3. which uses this param to initialize the 'pgp' variable in the repo with db.$config.pgp
As explained in the demo, this event occurs when the db is loaded for the first time in the appl and for every task and transaction.
In my case :
The first time the event occurs (full app initialization), the event's param 'obj' is the database context (containing $config, $pool, ...) so it works
When the event occurs for a task or transaction, the event's param 'obj' is a Task context, where $config does not exists so the event can not extend the context with my repo. An exception 'can not read property helpers of undefined' is thrown but does not appear and does not crash my app, I don't know why, maybe catched in the event. That is why I could not use my repo in the transaction.
I modified my code like it and it works :
./db/index.js
'use strict';
/* hidden for brevity */
// pg-promise initialization options:
const initOptions = {
promiseLib: promise,
extend(obj, dc) {
obj.roles = repos.Roles(obj, pgp);
obj.shells = repos.Shells(obj, pgp);
obj.systems = repos.Systems(obj, pgp);
obj.devices = repos.Devices(obj, pgp);
}
};
const pgp = require('pg-promise')(initOptions);
const db = pgp(config);
/* hidden for brevity */
./db/index.js
'use strict';
/* hidden for brevity */
// pg-promise initialization options:
const initOptions = {
promiseLib: promise,
extend(obj, dc) {
obj.roles = repos.Roles(obj, pgp);
obj.shells = repos.Shells(obj, pgp);
obj.systems = repos.Systems(obj, pgp);
obj.devices = repos.Devices(obj, pgp);
}
};
const pgp = require('pg-promise')(initOptions);
const db = pgp(config);
/* hidden for brevity */
./db/repos/{repoFiles}.js
/* hidden for brevity */
module.exports = (db, pgpLib) => {
Database = db;
pgp = pgpLib;
Collections = createColumnsets();
return DevicesRepository;
}
Property $config is there for integration purposes. That's why it exists only on the root Database level, and not inside tasks or transactions.
In order to make use of the helpers namespace, you should pass pgp into repositories when you initialize them, as shown within pg-promise-demo:
extend(obj, dc) {
obj.users = new repos.Users(obj, pgp);
obj.products = new repos.Products(obj, pgp);
}
You can establish the transaction outside the calls, then pass it in to those functions to have them use it.
That said, I'd recommend looking into a slightly higher-level query builder library such as Knex.js to save you from some of these (and future) headaches.

Return from then - nodejs

I am sort of new to NodeJS and I'm learning as I code but I can't wrap my head around Promise/then.
Here is the piece of code - I'm using a library function to Read database values.
var collection = 'Students';
var query = {};
query.name = 'name';
//readFromDatabse returns -{Function} a promise to return the document found, or null if not found
var temp = readFromDatabase(collection, query).then(function(studentData) {
var result = {
resultDetails: {
username: studentData.username,
password: studentData.password
}
};
return callback(null,resultDetails);
});
but when I read see the values in temp, it contains {"isFulfilled":false,"isRejected":false}!! how can I get the result details into temp?
You have to think of Promises as containers for values. readFromDatabase returns such a container, which might eventually hold the requested value unless the query fails. Your temp variable points to the container, but not the response. The properties isFullfilled and isRejected are attributes of the Promise telling you that it has neither been resolved with a value or rejected with an error.
To get to the response you have to use the then method. The function you register there will be called for you, when the query yields a result or an error.
var collection = 'Students';
var query = {};
query.name = 'name';
var temp = null;
readFromDatabase(collection, query).then(function(studentData) {
var result = {
resultDetails: {
username: studentData.username,
password: studentData.password
}
};
temp = result;
});
// temp is still null here

Run Sequelize raw query for each result of another sequelize query

i am using sequelize.js in node app to fetch data from a query and then run another sequelize raw query for each result element. but dude to callbacks i am not getting results.
my code:
var raw_query1 = "select id,name,has_results from users";
sequelize.query(raw_query1).then(function(results) {
var outputArray = []; // to store use results with additional results
for(i=0;i<results.length;i++){
outputArray[i].name = results[i].name;
var raw_query2 = "select * from meta where user_id = "+resulsts[i].id;
sequelize.query(raw_query2).then(function(meta_results) {
outputArray[i].meta = meta_results;
}
}
return res.json(outputArray); //Returning parsed results
});
i think we need to use promise here but i am a newbie from php and dont know how to proceed thanks in advance.
You need to use bluebird.map.
var raw_query1 = "select id,name,has_results from users";
sequelize.query(raw_query1).then(function(results) {
return bluebird.map(results, function(user){
var raw_query2 = "select * from meta where user_id = "+user.id;
return sequelize.query(raw_query2)
})
.then(function(metas){
return res.json(metas);
});
});

Why is my for-loop only counting to one object in the JSON-response?

I'm trying to make a map with map-annotations which are being generated from a REST-JSON response. I've succeeded with making one, the JSON response contains two objects. Why are only one printed out?
I'm using RestSharp and Xamarin.iOS.
Here's a Gist-clone of the original respones
The function that grabs the data to later-on make map-annotations on our map:
Action getAllMarkers = () => {
var client = new RestClient("http://www.example.com/");
var request = new RestRequest(String.Format("api/?function=searchByName&key=&name=Sundsvall"));
client.ExecuteAsync (request, response => {
JsonValue data = JsonValue.Parse(response.Content);
for (var i = 0; i < data.Count; i++){
Double lat = data["result"][i]["lat"];
Double lng = data["result"][i]["lng"];
String name = data["result"][i]["title"];
String adress = data["result"][i]["adress"];
var store = new BasicMapAnnotation (new CLLocationCoordinate2D(lat, lng), name, adress);
Console.WriteLine(response.Content);
InvokeOnMainThread ( () => {
// manipulate UI controls
map.AddAnnotation(store);
});
}
});
};
getAllMarkers();
data.Count is 1, because there is one top level "result" node in your json. Use data["result"].Count instead.
your result is main array in which rest of the data is so use:data["result"].Count instead of data.Count

Resources