Python how to replace %s with actual values

Question:

Using the below code as an example, sometimes I want to be able to see what values are being entered into a select statement to verify the query is accurate. How do I do this so that what gets printed would look something like this:
SQL with values: SELECT column FROM table WHERE id=’1234′ and name=’bob’

sql = "SELECT column FROM table WHERE id=%s and name=%s"
args = [id, name]
query_string = ???
print('SQL with values: ' + query_string)
with connection.cursor() as cursor:
    cursor.execute(sql, args)
    result = cursor.fetchone()
Asked By: Chris Utter

||

Answers:

You can use this

sql = "SELECT column FROM table WHERE id=%s and name=%s"
args = [id, name]
query_string = sql % (id, name)
print('SQL with values: ' + query_string)
with connection.cursor() as cursor:
    cursor.execute(sql, args)
    result = cursor.fetchone()
Answered By: tonno7103
# ...
query_string = sql % (args[0], """ + args[1] + """)
# ...
Answered By: erjemin

If you’re using the official mysql-connector-python package from MySQL and you’re satisfied with printing the exact SQL after it was executed, then print(cursor._executed).

They don’t seem to expose a simple processing function to display what would be executed, you’d have to lift some code from around here: https://github.com/mysql/mysql-connector-python/blob/master/lib/mysql/connector/cursor.py#L539 and make your own function if you need that.

You could approximate it somewhat with code like this:

def render_sql(sql, args):
    quoted = list()
    for arg in args:
        if type(arg) in (int, float):
            quoted.append(str(arg))
        elif type(arg) is str:
            quoted.append(f"'{arg}'")
        else:
            raise Exception(f"got an unknown type: {type(arg} for SQL param: {arg}")

    return sql % tuple(quoted)

sql = "SELECT column FROM table WHERE id=%s and name=%s"
print(render_sql(sql, (1, 1)))
# SELECT column FROM table WHERE id=1 and name=1
print(render_sql(sql, (1, 1.5)))
# SELECT column FROM table WHERE id=1 and name=1.5
print(render_sql(sql, (1, "test")))
# SELECT column FROM table WHERE id=1 and name='test'
print(render_sql(sql, (1, "text with spaces")))
# SELECT column FROM table WHERE id=1 and name='text with spaces'

You can extend that with additional types. But, as jarmod said, use this only for debugging, do not use it to execute on the database without reviewing the rendered SQL first.

Update:

I came up with this, but it’s specific to the PyMySQL implementation and not general:

from pymysql.connections import Connection

def render_sql(operation, params):
    connection = Connection(defer_connect=True)
    connection.server_status = 0
    return connection.cursor().mogrify(operation, params)

So this:

render_sql(
    "SELECT * FROM Users WHERE id = %s and name = %s and password = %s",
    (1, "user", "I'm past")
)

yields:

SELECT * FROM Users WHERE id = 1 and name = 'user' and password = 'I'm past'

Security Demo:

For those that aren’t familiar with SQL Injection attacks.

I used the above function and a naive implementation where the %s placeholders are simply replaced with the values passed in.

def naive_sql(operation, params):
    return operation % params

Two "parameterized" queries:

good = "SELECT * FROM Users WHERE name = %s AND password = %s"
bad =  "SELECT * FROM Users WHERE name = '%s' AND password = '%s'"

Example 1:

params = ("bobby", "PaSsWoRd")
print("Good:", render_sql(good, params))
print(" Bad:", naive_sql(bad, params))
print("so far so good")

Output:

Good: SELECT * FROM Users WHERE name = 'bobby' AND password = 'PaSsWoRd'
 Bad: SELECT * FROM Users WHERE name = 'bobby' AND password = 'PaSsWoRd'
so far so good

Example 2:

params = ("bobby", "' OR 'a' = 'a")
print("Good:", render_sql(good, params))
print(" Bad:", naive_sql(bad, params))
print("just granted access without a password")

Output:

Good: SELECT * FROM Users WHERE name = 'bobby' AND password = '' OR 'a' = 'a'
 Bad: SELECT * FROM Users WHERE name = 'bobby' AND password = '' OR 'a' = 'a'
just granted access without a password

Example 3:

params = ("bobby", "' ; UPDATE USERS SET password = 'letmein' WHERE name = 'bobby' ; -- ")
print("Good:", render_sql(good, params))
print(" Bad:", naive_sql(bad, params))
print("updated bobby's password")

Output:

Good: SELECT * FROM Users WHERE name = 'bobby' AND password = '' ; UPDATE USERS SET password = 'letmein' WHERE name = 'bobby' ; -- '
 Bad: SELECT * FROM Users WHERE name = 'bobby' AND password = '' ; UPDATE USERS SET password = 'letmein' WHERE name = 'bobby' ; -- '
updated bobby's password

Example 4:

params = ("bobby", "' ; DROP TABLE Users ; -- ")
print("Good:", render_sql(good, params))
print(" Bad:", naive_sql(bad, params))
print("your site is now down")

Output:

Good: SELECT * FROM Users WHERE name = 'bobby' AND password = '' ; DROP TABLE Users ; -- '
 Bad: SELECT * FROM Users WHERE name = 'bobby' AND password = '' ; DROP TABLE Users ; -- '
your site is now down

Obligatory XKCD: https://xkcd.com/327/

Answered By: brunson
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.