14

Example:

from sqlalchemy.dialects import mysql
from sqlalchemy import Integer, Column, update, insert
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Test(Base):
    __tablename__ = "test"

    a = Column(Integer, primary_key=True)
    b = Column(Integer)


update_stmt = update(Test).where(Test.a == 1).values(b=2)
print update_stmt.compile(dialect=mysql.dialect(), compile_kwargs=  {"literal_binds": True})

insert_stmt = insert(Test).values(a=1, b=1)
print insert_stmt.compile(dialect=mysql.dialect())

Result of this is:

UPDATE test SET b=%s WHERE test.a = %s
INSERT INTO test (a, b) VALUES (%s, %s)

The question is how to make sqlalchemy generate smth like this:

UPDATE test SET b=2 WHERE test.a = 1
INSERT INTO test (a, b) VALUES (1, 1)

For select, compile_kwargs= {"literal_binds": True} solves the issue, but it doesn't work for update, insert.

Thanks for any help.

P.S. I need to build raw sql queries from orm, so any suggestions of any other orms, that have easy way to generate raw sql, are welcome.

tk111
  • 151
  • 1
  • 1
  • 6
  • possible duplicate of [SQLAlchemy: print the actual query](http://stackoverflow.com/questions/5631078/sqlalchemy-print-the-actual-query) – metatoaster Jun 23 '15 at 01:23
  • try putting it in quotes? – Busturdust Jun 23 '15 at 01:35
  • See SQLAlchemy FAQ: [How do I render SQL expressions as strings, possibly with bound parameters inlined?](http://docs.sqlalchemy.org/en/rel_1_0/faq/sqlexpressions.html#how-do-i-render-sql-expressions-as-strings-possibly-with-bound-parameters-inlined). You should use `literal_binds=True`. – van Jun 23 '15 at 05:59
  • 2
    `literal_binds` is ok for `select`, but it doesn't work for `insert`, `update`. – tk111 Jun 23 '15 at 13:39
  • @tk111 did you come up with a solution? – m79lkm Aug 11 '15 at 18:15
  • @m79lkm unfortunatelly, no solution so far in sqlachemy aspect; I switched to http://docs.peewee-orm.com/en/latest/, it does exactly what i need. – tk111 Aug 15 '15 at 04:21

3 Answers3

4

The solution using compile_kwargs -- as advertised elsewhere -- works only partially, i.e. for simple data types like Integer or String (as of SQLAlchemy v1.1.6).

+-------------+----------------+--------+---------------+----------+
| SQA version | compile_kwargs | SELECT | INSERT/UPDATE | datetime |
+=============+================+========+===============+==========+
| 0.7.9       |       --       |    --  |        --     |    --    |
+-------------+----------------+--------+---------------+----------+
| 0.9.4       |       √        |   1/2  |        --     |    --    |
+-------------+----------------+--------+---------------+----------+
| 1.0.11      |       √        |    √   |        --     |    --    |
+-------------+----------------+--------+---------------+----------+
| 1.0.13      |       √        |    √   |       1/2     |    --    |
+-------------+----------------+--------+---------------+----------+
| 1.1.6       |       √        |    √   |       1/2     |    --    |
+-------------+----------------+--------+---------------+----------+

Depending on requirements and restrictions, the following solutions will do the job:

  • If you can use SQLAlchemy v1.0.13 or above, the simplest solution is provided in this answer. The LiteralDialect class presented there is still needed to provide correct quoting of data types like datetime. Although it is not complete, adding missing data types is very straightforward. And unfortunately you have to fix None to null() for INSERT/UPDATE.

  • If you can use SQLAlchemy v1.0.11 and need only SELECT statements (not INSERT/UPDATE), you can also use the solution from the above answer. SQLAlchemy v1.0.11 is distributed with ubuntu 16.04 xenial, the current LTS version (as of Mar 2017).

If you are stuck with some older version of SQLAlchemy, there is no sweet and simple approach to the problem.

So I came up with some code supporting SQL compilation of queries (SELECT) as well as INSERT and UPDATE statements. The code works with SQLAlchemy v0.7.9 - v1.1.6 and Python2/Python3.

A detailed (and hilarious) analysis of the various features is included as doctest.

The relevant code sections are:

Community
  • 1
  • 1
wolfmanx
  • 481
  • 5
  • 12
  • Great work. Unfortunately I'm currently not sure how to implement a literal quoting for further data types. In my case I'm running into a TypeError when trying to literalize statements (against a MySQL database) that include where clauses referencing columns with the *enum* data type: *__init__() got an unexpected keyword argument '_enums'* is the corresponding message. I suppose I have to define a conversion to literal values for this type, yet I don't know how. – le_affan Jun 26 '18 at 10:39
  • 1
    @le_affan SQLAlchemy v1.2.9 introduces a new keyword argument _enums, which causes the TypeError in StringLiteral for Enum. I have prepared a new version 0.2, at the same site above. – wolfmanx Jun 30 '18 at 00:23
2

I just run this snippet on python 2.7 and SQLAlchemy (1.0.13) and it worked.

from sqlalchemy.dialects import mysql
from sqlalchemy import Integer, Column, update, insert
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()


class Test(Base):
    __tablename__ = "test"

    a = Column(Integer, primary_key=True)
    b = Column(Integer)


update_stmt = update(Test).where(Test.a == 1).values(b=2)
print update_stmt.compile(dialect=mysql.dialect(), compile_kwargs={"literal_binds": True})

insert_stmt = insert(Test).values(a=1, b=1)
print insert_stmt.compile(dialect=mysql.dialect(), compile_kwargs={"literal_binds": True})

my output is:

UPDATE test SET b=2 WHERE test.a = 1
INSERT INTO test (a, b) VALUES (1, 1)

Maybe something is wrong with your environment?

kardaj
  • 1,897
  • 19
  • 19
  • It does not work with slqlalchemy v1.0.11, which ships with ubuntu 16.04 xenial. – wolfmanx Feb 06 '17 at 07:06
  • And it fails, as soon as you use a datetime.datetime value: NotImplementedError: Don't know how to literal-quote value datetime.date(2016, 10, 3) – wolfmanx Mar 08 '17 at 01:31
0

This isn't as complete of an answer as above. I would recommend the approach linked by wolfmanx and others.

I used this shortcut which fit my use-case. If it fails compiling with literal binds due to some unsupported type like datetime or UUID, then manually converting the values to strings works.

I just used the .compile method which exposed the binds.

try:
    sql = str(query.compile(dialect=postgresql.dialect(), compile_kwargs={'literal_binds': True}))
except NotImplementedError:
    # This has special types which cannot be converted to string automatically.
    compiler = query.compile()
    variables = {key: str(value.value) for key, value in compiler.binds.items() if not key.startswith('%')}
    sql = str(query)
    # Manually replace the variables in the sql statement with their values.
    # This won't perfectly match the original statement, as this converts everything to str and wraps in double-quotes for simplicity.
    for variable, value in variables.items():
        # Variables are prefixed with :
        # Example: "DELETE FROM table WHERE skill.id = :id_1"
        sql = sql.replace(f':{variable}', f'"{value}"')
Flair
  • 2,609
  • 1
  • 29
  • 41