Reputation: 539
I'm writing some scripts in Python 3 and need to read data from PostgreSQL database. In a scenario, I need to append a query to another query and also prevent SQL Injection. I tried different ways (format string, sql.SQL) but couldn't success. Below is the code I used:
data = {
'id': 14,
'user_id': (16, 'Administrator'),
'from_date': datetime.datetime(2019, 1, 6, 5, 45, 15),
'to_date': datetime.datetime(2020, 5, 3, 5, 45, 15),
'state': 'to_be_review',
'language': 'english',
}
query = " AND hr.unit_identification_code_id IN (%s)" % ','.join(map(str, uic_ids))
user_id = data.get('user_id') and data.get('user_id')[0] or 0
if uic_ids:
if data.get('state') != 'reject_submit':
self._cr.execute(sql.SQL("""
SELECT
elh.id
FROM
employee_log_history elh
LEFT JOIN
hr_employee hr
ON elh.hr_employee_id = hr.id
WHERE
elh.create_date >= %s
AND elh.create_date <=%s
AND elh.create_uid = %s
AND elh.state = %s {}
""").format(sql.Identifier(query)), (data.get('from_date'), data.get('to_date'), user_id, data.get('state'),)
)
but when I run the script as the result I get the first query in double quotes and bellow error:
psycopg2.ProgrammingError: syntax error at or near "" AND hr.unit_identification_code_id IN (33,34,35,36,37,38,39,40,41,42,49,47,60,61,63,64,62,43,46,58,59,50,57,48,44,51,52)""
LINE 13: ... AND elh.state = 'to_be_review' " AND hr.u...
Now I wonder if Is it possible to execute this query and prevent SQL Injection and also append first query in second one?
Upvotes: 2
Views: 708
Reputation: 55619
sql.Identifier is for quoting identifiers such as table names or column names, not for adapting chunks of SQL. To do what you want, use sql.SQL.
Here's a simplified example, adding an additional condition to a WHERE
clause:
import psycopg2
from psycopg2 import sql
conn = psycopg2.connect(database='test')
cur = conn.cursor()
stmt1 = "SELECT name, password FROM users WHERE name = %s"
stmt2 = ' AND {} = %s'
composed = sql.SQL(stmt2).format('password')
print(composed)
Composed([SQL(' AND '), Identifier('password'), SQL(' = %s')])
print(repr(composed.as_string(cur)))
' AND "password" = %s'
cur.mogrify(stmt1 + composed.as_string(cur), ('Alice', 'secret'))
b'SELECT name, password FROM users WHERE name = \'Alice\' AND "password" = \'secret\''
Mixing strings (stmt
) and objects (composed
) feels a bit hacky. You might want to use objects consistently in your code, but that's up to you.
Upvotes: 2