29

For some reasons, I would like to do an explicit quoting of a string value (becoming a part of constructed SQL query) instead of waiting for implicit quotation performed by cursor.execute method on contents of its second parameter.

By "implicit quotation" I mean:

value = "Unsafe string"
query = "SELECT * FROM some_table WHERE some_char_field = %s;"
cursor.execute( query, (value,) ) # value will be correctly quoted

I would prefer something like that:

value = "Unsafe string"
query = "SELECT * FROM some_table WHERE some_char_field = %s;" % \
    READY_TO_USE_QUOTING_FUNCTION(value)
cursor.execute( query ) # value will be correctly quoted, too

Is such low level READY_TO_USE_QUOTING_FUNCTION expected by Python DB API specification (I couldn't find such functionality in PEP 249 document). If not, maybe Psycopg2 provides such function? If not, maybe Django provides such function? I would prefer not to write such function myself...

Dariusz Walczak
  • 4,848
  • 5
  • 36
  • 39
  • 1
    I suggest that you look into SQLAlchemy's SQL builder API, even if you aren't interested in the ORM components; this will let you bind the values while still retaining flexibility. – Charles Duffy Nov 21 '08 at 22:46
  • 1
    Something like `db_cur.execute('''UPDATE test_table SET field_1="%s" WHERE field_2="%s"''' % (data, condition))` Note the triple single quotes and double quotes around `%s` – zelusp Oct 29 '16 at 01:30
  • '''' allows prevents having to keep the whole sql on one line – Golden Lion Feb 25 '21 at 18:30

10 Answers10

34

Ok, so I was curious and went and looked at the source of psycopg2. Turns out I didn't have to go further than the examples folder :)

And yes, this is psycopg2-specific. Basically, if you just want to quote a string you'd do this:

from psycopg2.extensions import adapt

print adapt("Hello World'; DROP DATABASE World;")

But what you probably want to do is to write and register your own adapter;

In the examples folder of psycopg2 you find the file 'myfirstrecipe.py' there is an example of how to cast and quote a specific type in a special way.

If you have objects for the stuff you want to do, you can just create an adapter that conforms to the 'IPsycopgSQLQuote' protocol (see pydocs for the myfirstrecipe.py-example...actually that's the only reference I can find to that name) that quotes your object and then registering it like so:

from psycopg2.extensions import register_adapter

register_adapter(mytype, myadapter)

Also, the other examples are interesting; esp. 'dialtone.py' and 'simple.py'.

Daniel Serodio
  • 4,229
  • 5
  • 37
  • 33
Henrik Gustafsson
  • 51,180
  • 9
  • 47
  • 60
  • It looks that your solution is what I sought. If I use default adapter for quoting string values (to use it as string constants) will it prevent me against SQL injection? It looks so but maybe I miss something... – Dariusz Walczak Nov 24 '08 at 13:49
17

I guess you're looking for the mogrify function.

Example:

>>> cur.mogrify("INSERT INTO test (num, data) VALUES (%s, %s)", (42, 'bar'))
"INSERT INTO test (num, data) VALUES (42, E'bar')"
Beli
  • 594
  • 5
  • 16
2

You should try to avoid doing your own quoting. Not only will it be DB-specific as people have pointed out, but flaws in quoting are the source of SQL injection bugs.

If you don't want to pass around queries and values separately, then pass around a list of the parameters:

def make_my_query():
    # ...
    return sql, (value1, value2)

def do_it():
    query = make_my_query()
    cursor.execute(*query)

(I probably have the syntax of cursor.execute wrong) The point here is that just because cursor.execute takes a number of arguments, that doesn't mean you have to handle them all separately. You can deal with them as one list.

Ned Batchelder
  • 364,293
  • 75
  • 561
  • 662
  • You can also pass them as a dict: cursor.execute( "SOME SQL %(aparam)s, %(another)s, %(aparam)s", adict) – Brian C. Lane Nov 23 '08 at 15:28
  • Still, this won't work in all cases; it's afaict impossible to make that work when doing multi-row inserts and similar constructs. – Henrik Gustafsson Nov 23 '08 at 15:31
  • Whatever structure of database calls you need to make, you can build an abstraction to hold the data until it's time to use it. Wanting to build queries abstractly is no excuse for hacking together your own quoting. – Ned Batchelder Nov 23 '08 at 16:59
  • Certainly true, but at some point you do need to quote your data no matter how many layers of abstraction you hide it beneath, and the mechanisms provided by execute/executemany can not support all uses of values in SQL. – Henrik Gustafsson Nov 23 '08 at 17:11
  • Ok, so it's possible to do eg. sql = 'insert into x values'+','.join(('(%s, %s)',) * len(values)) and so on, but I don't find that a very good solution. – Henrik Gustafsson Nov 23 '08 at 17:32
  • I fully agree that I shouldn't write my own quoting function (this is why I've asked the question). :-) I thought about returning param values as a list/dict together with SQL string but IMHO it's bug prone in case of more than one "SQL generator" (I generate parts of SQL, e.g. sets of WHERE conds). – Dariusz Walczak Nov 24 '08 at 13:18
1

Your code snippet would get just like this, according to psycopg extension docs

from psycopg2.extensions import adapt

value = "Unsafe string"
query = "SELECT * FROM some_table WHERE some_char_field = %s;" % \
    adapt(value).getquoted()
cursor.execute( query ) # value will be correctly quoted, too

The getquoted function returns the value as a quoted and escaped string, so you could also go: "SELECT * FROM some_table WHERE some_char_field = " + adapt(value).getquoted() .

Roberto
  • 2,115
  • 24
  • 32
1

This'll be database dependent (iirc, mysql allows \ as an escape character, while something like oracle expects quotes to be doubled: 'my '' quoted string').

Someone correct me if i'm wrong, but the double-quoting method is the standard method.

It may be worth looking at what other db abstraction libraries do (sqlalchemy, cx_Oracle, sqlite, etc).

I've got to ask - why do you want to inline the values instead of bind them?

Richard Levasseur
  • 14,562
  • 6
  • 50
  • 63
  • I have a lots of rather non trivial queries which contain some repeating patterns (e.g. sets of multiple WHERE conditions). I've defined some classes which encapsulate necessary data and SQL generating code. It would be uncomfortable to return separately SQL code and param sequence from them. – Dariusz Walczak Nov 21 '08 at 22:31
  • FWIW when dealing with complex query generation I usually do pass back a parameterised query string and separate param list. It's not really that bad. – bobince Nov 21 '08 at 23:35
0

This is going to be DB dependent. In the case of MySQLdb, for example, the connection class has a literal method that will convert the value to the correct escaped representation for passing to MySQL (that's what cursor.execute uses).

I imagine Postgres has something similar, but I don't think there is a function to escape values as part of the DB API 2.0 spec.

davidavr
  • 14,143
  • 4
  • 27
  • 31
0

I don't think you give any sufficient reasoning behind your avoidance to do this The Right Way. Please, use the APi as it is designed and don't try so hard to make your code less readable for the next guy and more fragile.

ironfroggy
  • 7,991
  • 7
  • 33
  • 44
  • "...WHERE some_field IN (%s)" % ", ".join(...well, what?..) – Roman Odaisky Sep 09 '10 at 22:09
  • 1
    Then I would do something like "... WHERE some_field IN (%s)" % ",".join('%s') and generate the parameterized query. I can then still apply the parameter values afterwards. – ironfroggy Sep 10 '10 at 12:25
0

PyPika in another good option for building SQL statements. Usage example (based on an example on the project's homepage):

>>> from pypika import Order, Query
>>> Query.from_('customers').select('id', 'fname', 'lname', 'phone').orderby('id', order=Order.desc)
SELECT "id","fname","lname","phone" FROM "customers" ORDER BY "id" DESC
asherbret
  • 5,439
  • 4
  • 38
  • 58
-1

If you use django you might want to use the quoting function which is automatically adapted to the currently configured DBMS :

from django.db import backend
my_quoted_variable = backend.DatabaseOperations().quote_name(myvar)
vincent
  • 6,368
  • 3
  • 25
  • 23
  • `quote_name` encloses result string with double quotation marks. PostgreSQL (8.2.5) doesn't allow it as string constant delimiters (it looks they are used for so called quoted identifiers). Using function provided by django would be best for me, unfortunately I don't see proper function in db ops. – Dariusz Walczak Nov 24 '08 at 13:03
  • 1
    Looks like I'm completely wrong, quote_name is for escaping table and column names ! – vincent Nov 24 '08 at 21:08
-3
import re

def db_quote(s):
  return "\"" + re.escape(s) + "\""

can do the job of simple quoting that works at least with MySQL. What we really need, though is cursor.format() function that would work like cursor.execute() except it would return the resulting query instead of executing it. There are times when you do not want the query to be executed quite yet - e.g you may want to log it first, or print it out for debugging before you go ahead with it.

Sasha Pachev
  • 5,162
  • 3
  • 20
  • 20
  • Works great with mysql as long as you're heavily restricting what can actually be passed into it. This solution here is very dangerous. – Chris Cogdon Apr 12 '16 at 23:19
  • For standard SQL `'...'` literals, this is completely wrong. For postgresql's non-standard `E'...'` style of literals, which is close to C strings, this is _closer_ but still dangerous. When serializing/escaping, *always* use tools designed *for the target syntax*! For example, it'd legal for `re.escape` to use `[.]` to escape `.`, which works in regexps but entirely not what you want in SQL. – Beni Cherniavsky-Paskin Nov 20 '19 at 12:28