How to use exec_query with dynamic SQL - sql

I am working on a query and am using exec_query with binds to avoid potential SQL injection. However, I am running into an issue when trying to check that an id is in an array.
SELECT JSON_AGG(agg_date)
FROM (
SELECT t1.col1, t1.col2, t2.col1, t2.col2, t3.col3, t3.col4, t4.col7, t4.col8, t5.col5, t5.col6
FROM t1
JOIN t2 ON t1.id = t2.t1_id
JOIN t3 ON t1.id = t3.t3_id
JOIN t4 ON t2.is = t4.t2_id
JOIN t5 ON t3.id = t5.t3_id
WHERE t2.id IN ($1) AND t4.id = $2
) agg_data
this gives an error of invalid input syntax for integer: '1,2,3,4,5'
And SELECT ... WHERE t.id = ANY($1) gives ERROR: malformed array literal: "1,2,3,4,5,6,7" DETAIL: Array value must start with "{" or dimension information.
If I add the curly braces around the bind variable I get invalid input syntax for integer: "$1"
Here is the way I'm using exec_query
connection.exec_query(<<~EOQ, "-- CUSTOM SQL --", [[nil, array_of_ids], [nil, model_id]], prepare: true)
SELECT ... WHERE t.id IN ($1)
EOQ
I have tried with plain interpolation but that throws brakeman errors about sql injection so I can't use that way :(
Any help on being able to make this check is greatly appreciated. And if exec_query is the wrong way to go about this, I'm definitely down to try other things :D
In my class, I am using AR's internal sql injection prevention to search for the first bind variable ids, then plucking the ids and joining into a string for the sql query. I am doing the same for the other bind variable, finding the object and using that id. Just as a further precaution. So by the time the user inputs are used for the query, they've been through AR already. It's a brakeman scan that it throwing the error. I ahve a meeting on monday with our security team about this, but wanted to check here also :D

Let Rails do the sanitization for you:
ar = [1,2,8,9,100,800]
MyModel.where(id: ar)
your concern for sql injection suggests that ar is derived from user input. It's superfluous, but maybe want to make sure it's a list of integers. ar = user_ar.map(&:to_i).
# with just Rails sanitization
ar = "; drop table users;" # sql injection
MyModel.where(id: ar)
# query is:
# SELECT `my_models`.* from `my_models` WHERE `my_models`.`id` = NULL;
# or
ar = [1,2,8,100,"; drop table users;"]
MyModel.where(id: ar)
# query is
# SELECT `my_models`.* from `my_models` WHERE `my_models`.`id` in (1,2,8,100);
Rails has got you covered!

With Arel you could compose that query as:
class Aggregator
def initialize(connection: ActiveRecord::Base.connection)
#connection = connection
#t1 = Arel::Table.new('t1')
#t2 = Arel::Table.new('t2')
#t3 = Arel::Table.new('t3')
#t4 = Arel::Table.new('t4')
#t5 = Arel::Table.new('t5')
#columns = [
:col1,
:col2,
#t2[:col1],
#t2[:col2],
#t3[:col3],
#t3[:col4],
#t4[:col7],
#t4[:col8],
#t5[:col5],
#t5[:col6]
]
end
def query(t2_ids:, t4_id:)
agg_data = t1.project(*columns)
.where(
t2[:id].in(t2_ids)
.and(t4[:id].eq(t4_id))
)
.join(t2).on(t1[:id].eq(t2[:t1_id]))
.join(t3).on(t1[:id].eq(t3[:t1_id]))
.join(t4).on(t1[:id].eq(t4[:t1_id]))
.join(t5).on(t1[:id].eq(t5[:t1_id]))
.as('agg_data')
yield agg_data if block_given?
t1.project('JSON_AGG(agg_data)')
.from(agg_data)
end
def exec_query(t2_ids:, t4_id:)
connection.exec_query(
query(t2_ids: t2_ids, t4_id: t4_id),
"-- CUSTOM SQL --"
)
end
private
attr_reader :connection, :t1, :t2, :t3, :t4, :t5, :columns
end
Of course it would be a lot cleaner to just setup some models so that you can do t1.joins(:t2, :t3, :t4, ...). Your performance concerns are pretty unfounded as ActiveRecord has quite a few methods to query and get raw results instead of model instances.
Using bind variables for a WHERE IN () condition is somewhat problematic as you have to use a matching number of bind variables to the number of elements in the list:
irb(main):118:0> T1.where(id: [1, 2, 3])
T1 Load (0.2ms) SELECT "t1s".* FROM "t1s" WHERE "t1s"."id" IN (?, ?, ?) /* loading for inspect */ LIMIT ?
Which means that you have to know the number of bind variables beforehand when preparing the query. As a hacky workaround you can use some creative typecasting to get Postgres to split a comma seperated string into an array:
class Aggregator
# ...
def query
agg_data = t1.project(*columns)
.where(
t2[:id].eq('any (string_to_array(?)::int[])')
.and(t4[:id].eq(Arel::Nodes::BindParam.new('$2')))
)
.join(t2).on(t1[:id].eq(t2[:t1_id]))
.join(t3).on(t1[:id].eq(t3[:t1_id]))
.join(t4).on(t1[:id].eq(t4[:t1_id]))
.join(t5).on(t1[:id].eq(t5[:t1_id]))
.as('agg_data')
yield agg_data if block_given?
t1.project('JSON_AGG(agg_data)')
.from(agg_data)
end
def exec_query(t2_ids:, t4_id:)
connection.exec_query(
query,
"-- CUSTOM SQL --"
[
[t2_ids.map {|id| Arel::Nodes.build_quoted(id) }.join(',')],
[t4_id]
]
)
end
# ...
end

Related

Rails query to SQL statement

I'm trying to write an write this:
Team.last.players.sum("goals")
erb:
SELECT SUM("players"."goals")
FROM "players"
WHERE "players"."team_id" = $1 [["team_id", 2]]
how to rewrite this so that I could use it in a method:
def sql_search
sql = "SELECT SUM \"players\".\"goals\" FROM \"players\" WHERE \"players\".\"team_id\" = $1 [[\"team_id\", #{self.id}"
connection.execute(sql);
end
keep getting this error:
PG::SyntaxError: ERROR: syntax error at or near "."
LINE 1: SELECT SUM "players"."goals" FROM "players" WHERE "players"....
Any ideas would be appreciated
You don't need to add \" in sql statement, just remove them.
def sql_search
sql = "SELECT sum(goals) FROM players WHERE team_id = #{self.id};"
connection.execute(sql);
end
Is there some reason that you want to hard code the SQL query? It's generally bad practice to use string interpolation to insert parameters to SQL queries because of SQL injection attacks. Instead it's recommended to use ActiveRecord's SQL query parameter binding like this:
user_input = 5
Player.where('team_id = ?', user_input).sum(:goals)
Basically what this does is insert the parameter 5 after sanitization. This means you're safe from attacks where a hacker attempts to insert arbitrary SQL into parameter variables attempting to return sensitive data or delete data entirely!

Perl DBI - binding a list

How do I bind a variable to a SQL set for an IN query in Perl DBI?
Example:
my #nature = ('TYPE1','TYPE2'); # This is normally populated from elsewhere
my $qh = $dbh->prepare(
"SELECT count(ref_no) FROM fm_fault WHERE nature IN ?"
) || die("Failed to prepare query: $DBI::errstr");
# Using the array here only takes the first entry in this example, using a array ref gives no result
# bind_param and named bind variables gives similar results
$qh->execute(#nature) || die("Failed to execute query: $DBI::errstr");
print $qh->fetchrow_array();
The result for the code as above results in only the count for TYPE1, while the required output is the sum of the count for TYPE1 and TYPE2. Replacing the bind entry with a reference to #nature (\#nature), results in 0 results.
The main use-case for this is to allow a user to check multiple options using something like a checkbox group and it is to return all the results. A work-around is to construct a string to insert into the query - it works, however it needs a whole lot of filtering to avoid SQL injection issues and it is ugly...
In my case, the database is Oracle, ideally I want a generic solution that isn't affected by the database.
There should be as many ? placeholders as there is elements in #nature, ie. in (?,?,..)
my #nature = ('TYPE1','TYPE2');
my $pholders = join ",", ("?") x #nature;
my $qh = $dbh->prepare(
"SELECT count(ref_no) FROM fm_fault WHERE nature IN ($pholders)"
) or die("Failed to prepare query: $DBI::errstr");

How to pass parameter to exists clause?

I have the following method in my model:
def is_user_in_role (security_user_id, role)
SecurityUser.joins(:security_users_roles)
.where(security_users_roles:{role:role})
.exists?("security_users.id=#{security_user_id}")
end
The issue is that the "security_user_id" is not "translated" correctly in the SQL statements. It is always interpreted as "0".
This is a simple output of the generated SQL passing 'Instructor' and '9' as parameters values:
SecurityUser Exists (0.0ms) SELECT 1 AS one FROM security_users INNER JOIN security_users_manage_securities ON security_users_manage_securities.security_user_id = security_users.id INNER JOIN security_users_roles ON security_users_roles.id = security_users_manage_securities.security_users_role_id WHERE security_users_roles.role = 'Instructor' AND security_users.id = 0 FETCH FIRST ROW ONLY
You can see at the end:
security_users.id = 0
Could you tell me how should I transform the exists clause in order to use it with parameter?
I have found it. In order to pass parameters in the exists clause, you should use an array like this:
def is_user_in_role (security_user_id, role)
SecurityUser.joins(:security_users_roles)
.where(security_users_roles:{role:role})
.exists?(["security_users.id=#{security_user_id}"])
end

find_by_sql fails with exception: wrong number of parameters

In the following code find_by_sql fails with exception: wrong number of parameters (0 for 1).
Any idea what's going on?
def filter_new_unfollowers(unfollower_ids)
relationships = TwitterRelationship.find_by_sql["SELECT * FROM twitter_relationships
INNER JOIN twitter_identities ON (twitter_identities.twitter_id=twitter_relationships.source_twitter_id)
INNER JOIN member_twitter_identities ON (member_twitter_identities.twitter_identity_id = twitter_identities.id)
WHERE member_twitter_identities.member_id IN (?)", unfollower_ids]
end
The way you wrote it, you are trying to execute find_by_sql with no arguments, and then call the [] operator on the result (but it failed before you got that far).
You need a space before the "[". To be even more clear, I would put parentheses around the array argument "...find_by_sql([...])".
Try adding brackets:
relationships = TwitterRelationship.find_by_sql(["SELECT * FROM twitter_relationships
INNER JOIN twitter_identities ON (twitter_identities.twitter_id=twitter_relationships.source_twitter_id)
INNER JOIN member_twitter_identities ON (member_twitter_identities.twitter_identity_id = twitter_identities.id)
WHERE member_twitter_identities.member_id IN (?)", unfollower_ids])

How to execute query with subqueries on a table and get a Rowset object as a result in Zend?

I'm currently struggling on how to execute my query on a Table object in Zend and get a Rowset in return. Reason I need particularly THIS is because I'm modifying a code for existing project and I don't have much flexibility.
Query:
SELECT *
FROM `tblname` ud
WHERE ud.user_id = some_id
AND
(
(ud.reputation_level > 1)
OR
(
(SELECT COUNT( * )
FROM `tblname` t
WHERE t.user_id = ud.user_id
AND t.category_id <=> ud.category_id
AND t.city_id <=> ud.city_id
) = 1
)
)
Is there a way to describe this query using Select object?
Previous SQL solution was very simple and consisted of one WHERE clause:
$where = $this->getAdapter()->quoteInto("user_id = ?",$user_id);
return $this->fetchAll($where);
I need to produce same type of the result (so that it could be processed by existing code) but for more complicated query.
Things I've tried
$db = Zend_Db_Table::getDefaultAdapter();
return $db->query($sql)->fetchAll();
---------------- OR ----------------------
return $this->fetchAll($select);
---------------- OR ----------------------
return $this->_db->query($sql)->fetchAll();
But they either produce arrays instead of objects or fail with Cardinality violation message.
I would appreciate any help on how to handle SQL text queries in Zend.
$dbAdapter = Zend_Db_Table::getDefaultAdapter();
//change the fetch mode becouse you don't like the array
$dbAdapter->setFetchMode(Zend_Db::FETCH_OBJ);
$sql = "you're long sql here";
$result = $dbAdapter->fetchAll($sql);
Zend_Debug::dump($result);
exit;
For a list of all fetch modes go to Zend_Db_Adapter
To write you're query using Zend_Db_Select instead of manual string , look at Zend_Db_Slect