How to build safe dynamic query with sqlx in rust? - rust

sqlx has a query builder. Documentation can be seen here
I see it supports dynamically buidling queries of the form:
SELECT * FROM users WHERE (id, username) IN ((1, "test_user_1"), (2, "test_user_2"))
But I am interested in building more complex queries likle
SELECT * from users where id = "id" AND username = "username" AND age > "10" AND age < "70" AND last_visited < 12324235435 AND last_visited > 214324324234234
Where any of the where clause is optional. So following should also be dynamically built
SELECT * from users where id = "id" AND age > "10" AND last_visited < 12324235435
I can't seem to find a way to do this with sqlx except from having to manually concatenate the where string myself

I got the following to work locally. Of course, I don't have your database, but the constructed SQL looks correct. I just picked postgres because you didn't specify what database you're actually using.
use sqlx::{query_builder::QueryBuilder, Execute};
struct Search {
id: i64,
username: Option<String>,
min_age: Option<i8>,
max_age: Option<i8>,
}
fn search_query(search: Search) -> String {
let mut query = QueryBuilder::new("SELECT * from users where id = ");
query.push_bind(search.id);
if let Some(username) = search.username {
query.push(" AND username = ");
query.push_bind(username);
}
if let Some(min_age) = search.min_age {
query.push(" AND age > ");
query.push_bind(min_age);
}
if let Some(max_age) = search.max_age {
query.push(" AND age < ");
query.push_bind(max_age);
}
query.build().sql().into()
}
fn main() {
dbg!(search_query(Search {
id: 12,
username: None,
min_age: None,
max_age: None,
})); // "SELECT * from users where id = $1"
dbg!(search_query(Search {
id: 12,
username: Some("Bob".into()),
min_age: None,
max_age: None,
})); // "SELECT * from users where id = $1 AND username = $2"
dbg!(search_query(Search {
id: 12,
username: Some("Bob".into()),
min_age: Some(10),
max_age: Some(70),
})); // "SELECT * from users where id = $1 AND username = $2 AND age > $3 AND age < $4"
}
I didn't make the id optional but I'm sure you can figure out how to omit the where entirely if no parameters are provided.

Related

Conditionally add filter predicaments before returning data in Mongodb

since I cant word properly what I am trying to achieve as many topics can mistakenly fall under the terminology of conditionally filtering mongodb query. Here is what I want to achieve,
I have a users DB, I am trying to send a search query to my backend with the following data {"country":"all","category":"all","gender":"all","ageRange":[25, 40]}
And I am trying to add these parameters into my query programatically. Eg,
const search = db.collection("users").find();
if (params.country == "ALL") {
search.addFilter( { country: "..." } )
}
if (params.age < 18) {
search.addFilter( { minor : true } )
}
const data = search.limit(30).toArray();
Is something like this possible? As when country is equal to "all" I dont want to apply any filter but if country is equal to some country I want to apply that country as the filter and so on. Can I programatically add to the filter predicament?
Using Nodejs (Nextjs api), so javascript and mongodb library
var filters = {}
var andObj = []
if (params.country == "ALL") {
andObj.push( { country: {$regex:".*." }} )
}
if (params.age < 18) {
andObj.push( { minor : true } )
}
filters['$and'] = andObj
const data = await db.collection("users").find(filters).limit(30).toArray();
or
var filters = {}
var andObj = []
if (params.country != "ALL") {
andObj.push( { country: params.country} )
}
if (params.age < 18) {
andObj.push( { minor : true } )
}
filters['$and'] = andObj
const data = await db.collection("users").find(filters).limit(30).toArray();

PostgreSQL query returning values that are not in my database

I am working on constructing a query to my database to return some data. Here is the link to a previous post describing my intentions Finding database data that best fits user variable responses. I want to return all of the columns for each data object, however the id that is returned is not correct and an additional VALUE field is being returned.
My database is set up like this
venues
id name parking decorations hotel
1 park 1 2 1
2 beach 1 2 2
3 theater 2 2 2
4 yard 2 1 1
and an enum table
id value
1 TRUE
2 FALSE
3 MAYBE
I am building a query on my backend as follows:
let searchConstraintsTrue = 'WHERE';
let firstItemTrue = 0;
for (const prop in req.body) {
if (req.body[prop] === 'TRUE') {
if (firstItemTrue === 0) {
searchConstraintsTrue += ` ${prop} = 1`;
firstItemTrue++;
} else {
searchConstraintsTrue += ` AND ${prop} = 1`;
}
}
}
let searchConstraintsMaybe = 'ORDER BY';
let firstItemMaybe = 0;
for (const prop in req.body) {
if (req.body[prop] === 'MAYBE') {
if (firstItemMaybe === 0) {
searchConstraintsMaybe += ` (${prop} = 1)::integer`;
firstItemMaybe++;
} else {
searchConstraintsMaybe += ` + (${prop} = 1)::integer`;
}
}
}
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
let sqlText = `SELECT * FROM venues
INNER JOIN response_enum rp ON rp.id = venues.parking
INNER JOIN response_enum rd ON rd.id = venues.decorations
INNER JOIN response_enum rh ON rh.id = venues.hotel
${searchConstraintsTrue} ${searchConstraintsMaybe} DESC`;
I realize that my searchConstraintsTrue and searchConstraintsMaybe are not properly using the enum table but right now I am just trying to get things working.
An example response looks like this:
[ {
id: 1,
name: 'beach',
parking: 1,
decorations: 2,
hotel: 1,
value: 'TRUE'
},
{
id: 2,
name: 'yard',
parking: 1,
decorations: 2,
hotel: 2,
value: 'FALSE'
}]
So I am returning the desired data however the id's are incorrect and there is a value column which doesn't exist in my database.
SELECT * will select all fields from the joined tables. You need to specify a list of fully qualified field names like so:
SELECT v.id,v.name,v.parking,v.decorations,v.hotel FROM venues v
INNER JOIN response_enum rp ON rp.id = venues.parking
INNER JOIN response_enum rd ON rd.id = venues.decorations
INNER JOIN response_enum rh ON rh.id = venues.hotel
${searchConstraintsTrue} ${searchConstraintsMaybe} DESC

TypeORM : Generate query with nested AND and OR

I am using NodeJS + TypeORM + PostgreSQL
I find it difficult to generate queries based on my requirements.
I need to generate the following query:
select * from clinics where status = 1 and (last_sync_date < x or last_sync_date is null)
Here x is current date - 10 days.
I tried the following query:
let now = Date();
now.setDate(now.getDate() - 10);
let clinics = await clinicRepo.find({
where: [
{ status: 1, last_sync_date: LessThan(now) },
{ last_sync_date: IsNull() }
]
});
But the result is this:
select * from clinics where (status = 1 and last_sync_date < x) or last_sync_date is null;
What do I need to change in the code above?
I want to use find so that I can load relations as well.
You can solve this by creating the query with js conditions and then assign it to the FindConditions.
For example:
const whereCondition = testResultId ?
{patientId, id: Not(testResultId), clinicId} :
{patientId, clinicId}
const tr = await TestResult.findOne({
where: whereCondition,
})
Or you can use Raw operator:
let clinics= await clinicRepo.find({
where : [
{status: 1,
last_sync_date: Raw(alias => `(${alias} < ${now} OR ${alias} IS NULL)`}
]
});

How to pass order info to sequelize.js raw query?

In sequelize, I can use
my_table.findAll({ order: [['datetime', 'desc']] })
to query data and order by a column. But when I try to use parameterized raw query like:
var input_parameters = {order_column: 'datetime', order: 'desc'};
sequelize.query('select * from my_table order by :order_column :order', { replacements: input_parameters, type: models.sequelize.QueryTypes.SELECT });
It can't return the correct order because
the order info asc/desc is escaped in the query, the final prepared query is like 'select * from my_table order by 'datetime' 'desc''.
Is there a way to pass order info to raw parameterized query?
This might not be the sequelize way, but...what if:
let order_column = 'something';
let order = 'DESC';
sequelize.query(`select * from my_table order by ${order_column} ${order}`, { type: models.sequelize.QueryTypes.SELECT });
UPDATE:
This is the right answer
await sequelize.query(
'SELECT * FROM projects ORDER BY ? ?',
{
replacements: ['something', 'desc'],
type: QueryTypes.SELECT,
}
);
This way sequelize still protects you from sql injection.
I wondered samething. But I think there's no options in raw query.
So I usually define class methods in model to use method much sequelize-like follows.
/**
* usage :
*
* model.findSomething({
* where: whereCondition,
* limit: limit,
* offset: offset,
* order: [
* ['col1', 'asc'],
* ['col2', 'desc']
* ]})
*/
model.findSomething = function ({where, limit, offset, order}) {
let sql = 'SELECT col1, col2 FROM some_table '
... (build where conditions)
// build order conditions
if (order && order.length > 0) {
let orderBy = ' ORDER BY '
for (let i = 0; i < order.length; i++) {
if (order[i].length > 0) { // [column] or [column, asc/desc]
orderBy += (i > 0 ? ', ' : ' ') + order[i].join(' ')
}
}
sql += orderBy
} else {
sql += ` ORDER BY comment_group, comment_depth, comments.comment_id`
}
... (build limit and offset)
}
Before you call sequelize.query, just build the sql statement first.
Too late answer, but I hope this let help you.
let order_column = 'something';
let order = 'DESC';
sequelize.query('select * from my_table order by " + order_column +' ' order, { replacements: input_parameters, type: models.sequelize.QueryTypes.SELECT })

Match range for value with MongoDB

I have a campaign collection, which is for advertisers. It's schema is:
var campaignSchema = new mongoose.Schema({
name: String,
sponsor: String,
[...]
target: {
age: {
lower: Number,
upper: Number
},
gender: String,
locations: Array, // [ { radius: '', lon: '', lat: '' } ]
activities: Array
}
});
I need to run a query with a specific age, and return all campaigns where that age is between age.lower and age.higher.
I have read the docs for $gt and $lt, however they appear to only work one way (so I could specify a range and match a value, however I need to specify a value and match a range).
Any suggestions?
Sorry I misunderstood the problem, I got it now.
db.collection.find( { "age.lower": { $lt: value1 }, "age.upper": { $gt: value1 } } );
So, for example, if your range is 25 to 40, and value1 is 30, 25 < 30 and 40 > 30 -- match!
If you use the same range with 20, 25 !< 20 -- will not match.
You could first create a query on the lower bound of that value, sort the results in descending order and get the top document from the result. Compare that document with the upper bound, if there is a match then that document has a range which contains that age. For example:
var age = 23;
var ads = db.campaign.find({ "age.lower": {"$lte": age } }).sort({"age.lower": -1}).limit(1);
if (ads != null) {
var result = ads[0];
if (result.age.upper > age) {
return result;
} else {
return null;
}
}

Resources