Passing string variable to MySQL, fails as tuple - python

Working with a newly purchased RaspberryPi and I am very new to Python/MySQL so please excuse the naive question I am posting.
I have looked at many Q&A's about this but I cannot seem to get my head around 'why' this is failing. I get error: "must be string or read-only buffer, not tuple". My variable appears as a string if I test it with TYPE so now I am lost.
import MySQLdb
import time
db = MySQLdb.connect(host="localhost", user="user",passwd="easypwd", db="imagepi")
cursor = db.cursor()
current_time = time.strftime("%H:%M:%S")
current_date = time.strftime("%Y-%m-%d")
filename = (current_time+'.jpg')
sql = ("""INSERT INTO imagelocator(batch, date, time, filename) VALUES
('1001', current_date, current_time, %s)""", filename)
cursor.execute(sql)
db.commit()
db.close()
Thanks so much for offering me a little push in the right direction.

The sql variable is a tuple. One half of it is your SQL statement, and the other half is the token value for the %s parameter in your statement. However, simply passing a tuple to an argument does not break it apart and use each element in the tuple as a separate parameter. For that, you have to use an asterisk: function_to_call(*tuple_args) ... but I think you'll have a problem with that, as well, since the database cursor expects a string for the statement argument, and a sequence for the parameters argument. The parameters argument must be a sequence (tuple, list, set, etc.) even if there is only one value.
TL;DR - You need to do something more like this:
sql = "INSERT INTO table_name (a_column, b_column) VALUES ('asdf', %s)"
args = (filename,)
cursor.execute(sql, args)
... or, if you really wanted to be tricksy and use a tuple for everything:
sql = ("INSERT INTO table_name (a_column, b_column) VALUES ('asdf', %s)", (filename,))
cursor.execute(*sql)
Edit: I guess I didn't clarify... while enclosing a string with parentheses does not create a tuple, the addition of a comma does. So, (string_var) is not a tuple, while (string_var,) is. Hopefully, that removes any confusion with how the above code operates.
Also, here's some documentation on the asterisk stuff; both the boring official docs and an easier-to-understand blog post:
Boring Python docs
Blog post

Related

Use of '.format()' vs. '%s' in cursor.execute() for mysql JSON field, with Python mysql.connector,

My objective is to store a JSON object into a MySQL database field of type json, using the mysql.connector library.
import mysql.connector
import json
jsonData = json.dumps(origin_of_jsonData)
cnx = mysql.connector.connect(**config_defined_elsewhere)
cursor = cnx.cursor()
cursor.execute('CREATE DATABASE dataBase')
cnx.database = 'dataBase'
cursor = cnx.cursor()
cursor.execute('CREATE TABLE table (id_field INT NOT NULL, json_data_field JSON NOT NULL, PRIMARY KEY (id_field))')
Now, the code below WORKS just fine, the focus of my question is the use of '%s':
insert_statement = "INSERT INTO table (id_field, json_data_field) VALUES (%s, %s)"
values_to_insert = (1, jsonData)
cursor.execute(insert_statement, values_to_insert)
My problem with that: I am very strictly adhering to the use of '...{}'.format(aValue) (or f'...{aValue}') when combining variable aValue(s) into a string, thus avoiding the use of %s (whatever my reasons for that, let's not debate them here - but it is how I would like to keep it wherever possible, hence my question).
In any case, I am simply unable, whichever way I try, to create something that stores the jsonData into the mySql dataBase using something that resembles the above structure and uses '...{}'.format() (in whatever shape or form) instead of %s. For example, I have (among many iterations) tried
insert_statement = "INSERT INTO table (id_field, json_data_field) VALUES ({}, {})".format(1, jsonData)
cursor.execute(insert_statement)
but no matter how I turn and twist it, I keep getting the following error:
ProgrammingError: 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '[some_content_from_jsonData})]' at line 1
Now my question(s):
1) Is there a way to avoid the use of %s here that I am missing?
2) If not, why? What is it that makes this impossible? Is it the cursor.execute() function, or is it the fact that it is a JSON object, or is it something completely different? Shouldn't {}.format() be able to do everything that %s could do, and more?
First of all: NEVER DIRECTLY INSERT YOUR DATA INTO YOUR QUERY STRING!
Using %s in a MySQL query string is not the same as using it in a python string.
In python, you just format the string and 'hello %s!' % 'world' becomes 'hello world!'. In SQL, the %s signals parameter insertion. This sends your query and data to the server separately. You are also not bound to this syntax. The python DB-API specification specifies more styles for this: DB-API parameter styles (PEP 249). This has several advantages over inserting your data directly into the query string:
Prevents SQL injection
Say you have a query to authenticate users by password. You would do that with the following query (of course you would normally salt and hash the password, but that is not the topic of this question):
SELECT 1 FROM users WHERE username='foo' AND password='bar'
The naive way to construct this query would be:
"SELECT 1 FROM users WHERE username='{}' AND password='{}'".format(username, password)
However, what would happen if someone inputs ' OR 1=1 as password. The formatted query would then become
SELECT 1 FROM users WHERE username='foo' AND password='' OR 1=1
which will allways return 1. When using parameter insertion:
execute('SELECT 1 FROM users WHERE username=%s AND password=%s', username, password)
this will never happen, as the query will be interpreted by the server separately.
Performance
If you run the same query many times with different data, the performance difference between using a formatted query and parameter insertion can be significant. With parameter insertion, the server only has to compile the query once (as it is the same every time) and execute it with different data, but with string formatting, it will have to compile it over and over again.
In addition to what was said above, I would like to add some details that I did not immediately understand, and that other (newbies like me ;)) may also find helpful:
1) "parameter insertion" is meant for only for values, it will not work for table names, column names, etc. - for those, the Python string substitution works fine in the sql syntax defintion
2) the cursor.execute function requires a tuple to work (as specified here, albeit not immediately clear, at least to me: https://dev.mysql.com/doc/connector-python/en/connector-python-api-mysqlcursor-execute.html)
EXAMPLE for both in one function:
def checkIfRecordExists(column, table, condition_name, condition_value):
...
sqlSyntax = 'SELECT {} FROM {} WHERE {} = %s'.format(column, table, condition_name)
cursor.execute(sqlSyntax, (condition_value,))
Note both the use of .format in the initial sql syntax definition and the use of (condition_value,) in the execute function.

Inserting python variable into SQlite Database

I have seen some posts that suggesting using a ? as a place holder when inserting python variables into a SQL Query but all of these examples show the question mark at the end of the query followed by the python variable. What if you want to insert a python variable in the middle of a query and want to avoid SQL injection? I am using Python 3.6 and SQLite.
Update* - This code is working:
id='13'
text='YES'
db=sqlite3.connect('NEW_Inventory.sqlite')
cursor=db.cursor()
query=('''
INSERT
OR REPLACE
INTO
text (id, text)
VALUES
(?,
(SELECT
CASE
WHEN exists(SELECT 1 FROM text WHERE id=?)
THEN 'good'
ELSE 'Hello'
END
)
)''')
cursor.execute(query, (id, id))
db.commit()
You need to pass the parameters to execute() as a tuple. In your case you need to call it like this:
cursor.execute(query, (id, id))
where query is your parameterised SQL query string.
I assume that your code defines id somewhere, otherwise, execute() will try to use the builtin function id() to construct the query, resulting in another error.
It also worth mentioning that if you have only one parameter it must also be passed as a tuple like this (id,). Avoid the common mistake of this: (id) which is not a tuple.

What do SQL queries Parameterized with '%s' look like?

Consider a particular SQL query in the form
cursor.execute(string, array)
Where string is some string containing '%s' and array is some array satisfying len(array) == string.count("%s"), not necessarily containing only strings.
For example:
cursor.execute("INSERT INTO tablename(col_one, col_two, col_three) VALUES (%s,%s,%s)",("text", 123, datetime.time(12,0)))
When I run this, I get an unhelpful error message about 'You have an error in your SQL syntax...' and then a partial text of the query. However, to debug this, I want to know the full text of the query.
When the query cursor.execute(string, array) is run, what is the actual text of the query the the cursor executes?
As you can read here:
Syntax:
cursor.execute(operation, params=None, multi=False)
iterator = cursor.execute(operation, params=None, multi=True)
This method executes the given database operation (query or command).
The parameters found in the tuple or dictionary params are bound to
the variables in the operation. Specify variables using %s or %(name)s
parameter style (that is, using format or pyformat style). execute()
returns an iterator if multi is True.
So when you use %s, it will replace that value with the one in the params list.
In case you want to debug your statement, you can print the last executed query with: cursor._last_executed:
try:
cursor.execute(sql, (arg1, arg2))
connection.commit()
except:
print("Error: "+cursor._last_executed)
raise
finally :
print(cursor._last_executed)
source
Your string is actually your parameterized query, where you should pass your elements to match your %s.
You can get examples in the mySql documentation
Note in there that the parameters are not in an array but in a tuple.
Your example becomes :
cursor.execute("INSERT INTO tablename(col_one, col_two, col_three) VALUES (%s,%s,%s)", ('text', 123, datetime.time(12,0)))
I also changed your " to 'as I doubt it liked it too much.
I'm also never sure of the date format, try without a date if you still have trouble (then fix the date format if needed).

psycopg2 TypeError: not all arguments converted during string formatting

I'm trying execute a simple query, but getting this error no matter how I pass the parameters.
Here is the query (I'm using Trac db object to connect to a DB):
cursor.execute("""SELECT name FROM "%s".customer WHERE firm_id='%s'""" % (schema, each['id']))
schema and each['id'] both are simple strings
print("""SELECT name FROM "%s".customer WHERE firm_id='%s'""" % (schema, each['id']))
Result:
SELECT name FROM "Planing".customer WHERE firm_id='135'
There is on error is a remove quote after firm_id=, but that way parameter is treated a an integer and ::text leads to the very same error.
In my case I didn't realize that you had to pass a tuple to cursor.execute. I had this:
cursor.execute(query, (id))
But I needed to pass a tuple instead
cursor.execute(query, (id,))
I got this same error and couldn't for the life of me work out how to fix, in the end it was my mistake because I didn't have enough parameters matching the number of elements in the tuple:
con.execute("INSERT INTO table VALUES (%s,%s,%s,%s,%s)",(1,2,3,4,5,6))
Note that I have 5 elements in the values to be inserted into the table, but 6 in the tuple.
It is recommended to not use string interpolation for passing variables in database queries, but using string interpolation to set the table name is fine as long as it's not an external input or you restrict the allowed value. Try:
cursor.execute("""
SELECT name FROM %s.customer WHERE firm_id=%%s
""" % schema, (each['id'],))
Rules for DB API usage provides guidance for programming against the database.
Use AsIs
from psycopg2.extensions import AsIs
cursor.execute("""
select name
from %s.customer
where firm_id = %s
""",
(AsIs(schema), each['id'])
)
You could try this:
cursor.execute("INSERT INTO table_name (key) VALUES(%s)",(value1,))
You will get an error if you are missing a (,) after value1.
The correct way to pass variables in a SQL command is using the second argument of the execute() method. And i think you should remove single quotes from second parameter, read about it here - http://initd.org/psycopg/docs/usage.html#the-problem-with-the-query-parameters.
Note that you cant pass table name as parameter to execute and it considered as bad practice but there is some workarounds:
Passing table name as a parameter in psycopg2
psycopg2 cursor.execute() with SQL query parameter causes syntax error
To pass table name try this:
cursor.execute("""SELECT name FROM "%s".customer WHERE firm_id=%s""" % (schema, '%s'), (each['id'],))
Every time I have this kind of error, I am passing the wrong amount of values. Try check it

How to quote a string value explicitly (Python DB API/Psycopg2)

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...
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'.
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')"
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.
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?
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.
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.
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() .
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
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)
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.

Categories

Resources