Python to Postgres interface with real prepared statements? - python

I've been trying to find a postgres interface for python 2.x that supports real prepared statements, but can't seem to find anything. I don't want one that just escapes quotes in the params you pass in and then interpolates them into the query before executing it. Anyone have any suggestions?

Either py-postgresql for Python3 or pg_proboscis for Python2 will do this.
Python-pgsql will also do this but is not threadsafe. Notably, SQLAlchemy does not make use of prepared statements.

have a look at web.py's db module
examples can be found at
http://webpy.org/cookbook/select
http://webpy.org/cookbook/update
http://webpy.org/cookbook/delete
http://webpy.org/Insert

These links hint at the answer when using psycopg2. You don't need special API extensions.
Re: psycopg2 and prepared statements
Prepared Statements in Postgresql
Transparently execute SQL queries as prepared statements with
Postgresql (Python recipe)
Here's an example that I played with. A word of caution though, it didn't give me the expected performance increase I had hoped for. In fact, it was even slower (just slightly) in a contrived case where I tried to read the whole table of one million rows, one row at a time.
cur.execute('''
PREPARE prepared_select(text, int) AS
SELECT * FROM test
WHERE (name = $1 and rowid > $2) or name > $1
ORDER BY name, rowid
LIMIT 1
''')
name = ''
rowid = 0
cur.execute('EXECUTE prepared_select(%s, %s)', (name, rowid))

Related

How to insert user variable into an SQL Update/Select statement using python [duplicate]

This question already has answers here:
How to use variables in SQL statement in Python?
(5 answers)
Closed 2 months ago.
def update_inv_quant():
new_quant = int(input("Enter the updated quantity in stock: "))
Hello! I'm wondering how to insert a user variable into an sql statement so that a record is updated to said variable. Also, it'd be really helpful if you could also help me figure out how to print records of the database into the actual python console. Thank you!
I tried doing soemthing like ("INSERT INTO Inv(ItemName) Value {user_iname)") but i'm not surprised it didnt work
It would have been more helpful if you specified an actual database.
First method (Bad)
The usual way (which is highly discouraged as Graybeard said in the comments) is using python's f-string. You can google what it is and how to use it more in-depth.
but basically, say you have two variables user_id = 1 and user_name = 'fish', f-string turns something like f"INSERT INTO mytable(id, name) values({user_id},'{user_name}')" into the string INSERT INTO mytable(id,name) values(1,'fish').
As we mentioned before, this causes something called SQL injection. There are many good youtube videos that demonstrate what that is and why it's dangerous.
Second method
The second method is dependent on what database you are using. For example, in Psycopg2 (Driver for PostgreSQL database), the cursor.execute method uses the following syntax to pass variables cur.execute('SELECT id FROM users WHERE cookie_id = %s',(cookieid,)), notice that the variables are passed in a tuple as a second argument.
All databases use similar methods, with minor differences. For example, I believe SQLite3 uses ? instead of psycopg2's %s. That's why I said that specifying the actual database would have been more helpful.
Fetching records
I am most familiar with PostgreSQL and psycopg2, so you will have to read the docs of your database of choice.
To fetch records, you send the query with cursor.execute() like we said before, and then call cursor.fetchone() which returns a single row, or cursor.fetchall() which returns all rows in an iterable that you can directly print.
Execute didn't update the database?
Statements executing from drivers are transactional, which is a whole topic by itself that I am sure will find people on the internet who can explain it better than I can. To keep things short, for the statement to physically change the database, you call connection.commit() after cursor.execute()
So finally to answer both of your questions, read the documentation of the database's driver and look for the execute method.
This is what I do (which is for sqlite3 and would be similar for other SQL type databases):
Assuming that you have connected to the database and the table exists (otherwise you need to create the table). For the purpose of the example, i have used a table called trades.
new_quant = 1000
# insert one record (row)
command = f"""INSERT INTO trades VALUES (
'some_ticker', {new_quant}, other_values, ...
) """
cur.execute(command)
con.commit()
print('trade inserted !!')
You can then wrap the above into your function accordingly.

Getting error when running a sql select statement in python

I am new to this and trying to learn python. I wrote a select statement in python where I used a parameter
Select """cln.customer_uid = """[(num_cuid_number)])
TypeError: string indices must be integers
Agree with the others, this doesn't look really like Python by itself.
I will see even without seeing the rest of that code I'll guess the [(num_cuid_number)] value(s) being returned is a string, so you'll want to convert it to integer for the select statement to process.
num_cuid_number is most likely a string in your code; the string indices are the ones in the square brackets. So please first check your data variable to see what you received there. Also, I think that num_cuid_number is a string, while it should be in an integer value.
Let me give you an example for the python code to execute: (Just for the reference: I have used SQLAlchemy with flask)
#app.route('/get_data/')
def get_data():
base_sql="""
SELECT cln.customer_uid='%s' from cln
""" % (num_cuid_number)
data = db.session.execute(base_sql).fetchall()
Pretty sure you are trying to create a select statement with a "where" clause here. There are many ways to do this, for example using raw sql, the query should look similar to this:
query = "SELECT * FROM cln WHERE customer_uid = %s"
parameters = (num_cuid_number,)
separating the parameters from the query is secure. You can then take these 2 variables and execute them with your db engine like
results = db.execute(query, parameters)
This will work, however, especially in Python, it is more common to use a package like SQLAlchemy to make queries more "flexible" (in other words, without manually constructing an actual string as a query string). You can do the same thing using SQLAlchemy core functionality
query = cln.select()
query = query.where(cln.customer_uid == num_cuid_number)
results = db.execute(query)
Note: I simplified "db" in both examples, you'd actually use a cursor, session, engine or similar to execute your queries, but that wasn't your question.

Python MySQLdb/mysqlclient: bind a named set (or tuple or list) as parameter

Background: Not much documentation on MySQLdb Connector
Maybe I'm looking in the wrong places, but there's not much documentation about Python's MySQLdb family of connectors. Perhaps PEP249 is meant to do the job. Oracle's MySQL/Python connector seems to have much better docs, but at the moment I'm working with mysqlclient (the 3.x version of MySQLdb, which wraps around the C connector).
Named Parameters in MySQLdb: working for single values
After much searching, I stumbled upon beautiful syntax for binding named parameters, so long as they are a single value. For instance (made-up query to simplify the case):
query = """
SELECT...
WHERE
name = %(name)s AND
gender = %(gender)s
"""
parameters = {'name': name, 'gender': gender}
cursor.execute(query, parameters)
This properly escapes the parameters. Terrific.
Named Parameters in MySQLdb: how to use iterables?
Now I'd like to use a set, list or tuple to build queries with IN. Something like:
query = """
SELECT...
WHERE
gender = %(gender)s AND
name IN %(nameset)s
"""
I found a similar question here but that query doesn't use named parameters (the placeholder is named, but not the iterable).
What am I missing? Would someone know the magic syntax to make this work?
I see in the MySQLdb code that paramstyle is set to format rather than pyformat, but pyformat does work for single values.
To clarify,
I am not interested in an answer that just builds a string like ('sophie', 'jane', 'chloe') and concatenates it to the query. I need bound parameters to guarantee proper escaping.
I am also not interested in concatenating a join that uses db.escape_string(), although I may end up going that route if nothing else works.
What I'm really after is a clean idiom that binds named iterable parameters, if there is one.
Don't love answering my own question, but it's been a day...
Having looked inside the MySQLdb code, it looks like I won't get my wish. The quoting function will always add one set of quotes too many.
This is where I've ended up (the fallback option I had mentioned):
idset = ('Chloe', 'Zoe', "Noe';drip dautobus")
quoted_ids = [mdb.escape_string(identifier).decode('utf-8') for identifier in idset]
sql_idset = "('" + "', '".join(quoted_ids) + "')"
query = """
SELECT ...
FROM ...
JOIN ...
WHERE
someid = %(someid)s AND
namae IN """ + sql_idset
parameters = {'someid': someid}
cursor.execute(query, parameters)
Only one of the parameters is bound. The set in the IN clause is pre-quoted.
Not my favorite thing to have to do, but at least each value is run through the MySQLdb quoting function in order to quote any potentially harmful stuff.
The decode is there because escape_string prepares a byte string, but the query being built with the bound parameter someid is a string. Maybe there is an easier way. If you find one, let me know.
I don't know how to express it specifically in MySQLdb but it just works out of the box with the MySQL connector library (version: 1.4.4, MySQL 5.7.x) as suggested in this other answer:
cursor.execute('SELECT * FROM test WHERE id in %(l)s', {'l': (1,2,3)})
If MySQLdb messes it up then I suggest acquiring direct cursor somehow.

Lightweight DBAL for python

can somebody please recomend me some python DBAL library that will best suit my requirements. I would like to write my sql statements directly, most of the logics will be in db stored procedures (postgresql), so I only need to invoke db procedures, pass arguments to them and fetch the results. The library should help me with quoting (preventing sql inject).
I played with sqlalchemy, but i think that there is no quoting helper when writing sql statement directly to engine.execute method.
Thank you
You should have given sqlalchemy a deeper look; It does a fine job of quoting placeholders:
>>> engine = sqlalchemy.create_engine("sqlite:///:memory:")
>>> engine.execute("select ?", 5).fetchall()
[(5,)]
>>> engine.execute("select ?", "; drop table users; --").fetchall()
[(u'; drop table users; --',)]
psycopg2 (via DB-API) will automatically quote to prevent SQL injection, IF you use it properly. (The python way is wrong; you have to pass the parameters as arguments to the query command itself.)
WRONG:
cur.execute('select * from table where last="%s" and first="%s"'
% (last, first))
RIGHT:
cur.execute('select * from table where last=%s and first=%s',
(last, first))
Note: you don't use %, and you don't put quotes around your values.
The syntax is slightly different for MySQLdb and sqlite3. (For example, sqlite uses ? instead of %s.)
Also, for psycopg2, always use %s even if you're dealing with numbers or some other type.

question about postgresql bind variables

I was looking at the question and decided to try using the bind variables. I use
sql = 'insert into abc2 (interfield,textfield) values (%s,%s)'
a = time.time()
for i in range(10000):
#just a wrapper around cursor.execute
db.executeUpdateCommand(sql,(i,'test'))
db.commit()
and
sql = 'insert into abc2 (intfield,textfield) values (%(x)s,%(y)s)'
for i in range(10000):
db.executeUpdateCommand(sql,{'x':i,'y':'test'})
db.commit()
Looking at the time taken for the two sets, above it seems like there isn't much time difference. In fact, the second one takes longer. Can someone correct me if I've made a mistake somewhere? using psycopg2 here.
The queries are equivalent in Postgresql.
Bind is oracle lingo. When you use it will save the query plan so the next execution will be a little faster. prepare does the same thing in Postgres.
http://www.postgresql.org/docs/current/static/sql-prepare.html
psycopg2 supports an internal 'bind', not prepare with cursor.executemany() and cursor.execute()
(But don't call it bind to pg people. Call it prepare or they may not know what you mean:)
IMPORTANT UPDATE :
I've seen into source of all python libraries to connect to PostgreSQL in FreeBSD ports and can say, that only py-postgresql does real prepared statements! But it is Python 3+ only.
also py-pg_queue is funny lib implementing official DB protocol (python 2.4+)
You've missed answer for that question about prepared statements to use as many as possible. "Binded variables" are better form of this, let's see:
sql_q = 'insert into abc (intfield, textfield) values (?, ?)' # common form
sql_b = 'insert into abc2 (intfield, textfield) values (:x , :y)' # should have driver and db support
so your test should be this:
sql = 'insert into abc2 (intfield, textfield) values (:x , :y)'
for i in range (10000):
cur.execute(sql, x=i, y='test')
or this:
def _data(n):
for i in range (n):
yield (i, 'test')
sql = 'insert into abc2 (intfield, textfield) values (? , ?)'
cur.executemany(sql, _data(10000))
and so on.
UPDATE:
I've just found interest reciple how to transparently replace SQL queries with prepared and with usage of %(name)s
As far as I know, psycopg2 has never supported server-side parameter binding ("bind variables" in Oracle parlance). Current versions of PostgreSQL do support it at the protocol level using prepared statements, but only a few connector libraries make use of it. The Postgres wiki notes this here. Here are some connectors that you might want to try: (I haven't used these myself.)
pg8000
python-pgsql
py-postgresql
As long as you're using DB-API calls, you probably ought to consider cursor.executemany() instead of repeatedly calling cursor.execute().
Also, binding parameters to their query in the server (instead of in the connector) is not always going to be faster in PostgreSQL. Note this FAQ entry.

Categories