On 04/20/2017 03:39 PM, George London wrote:
I noticed some strange behavior while using the SQLAlchemy expression language. I'm using Python 3.5 and SQLAlchemy 1.1.19 against MySQL 5.7.13 via the mysqlclient v1.3.10 connector.

The problem happens when I build up a boolean expression using and_() / or_(), then give that expression a name using .label(), and then negate the expression using not_().

For example,

disjunction = or_(user.c.user_name == "jack", user.c.user_name == "james")
disjunction_with_label = disjunction.label("banned_users")
negated_disjunction = not_(disjunction_with_label)

This is causing two problems:

1) If I use this expression with .where to filter a select query, the SQL that SQLAlchemy emits doesn't wrap the negated clause in parentheses, meaning that only the first clause in the disjunction will be negated (instead of the whole inner expression, as I'd expect). E.g.:

SELECT "user".user_id, "user".user_name, "user".email_address, "user".password
FROM "user"
WHERE NOT "user".user_name = :user_name_1 OR "user".user_name = :user_name_2

OK so, this could be improved, but also, you shouldn't be putting a label() expression into the WHERE clause. That's the part here that the API does not expect.





However, if I don't .label() the disjunction, SQLAlchemy emits a query that *does* have parentheses:


SQL: SELECT "user".user_id, "user".user_name, "user".email_address, 
"user".password
FROM "user"
WHERE NOT ("user".user_name = :user_name_1 OR "user".user_name = :user_name_2)

right because, negation works in the absense of label(). a negation of a label in SQL doesn't make sense. Label means this:

        "some_sql_expression AS some_label"

how do you "negate" that in SQL? "not (some_sq_expression AS some_label)"? that's invalid SQL. I know you expect it to "dig in" to the element, e.g. modify the thing that is labeled, but this would be an exception to the usual structural semantics of the SQL expression language. We probably make exceptions like this in some places but they are special cases that need to be added and tested.



2) actually running the emitted query produces strange results (that are different from the results I get by binding the literals and running the textual query directly.)


Please see this gist https://gist.github.com/rogueleaderr/e28cb1f707b6637421137c0c4bf3c282 for a full working reproduction of the issue in a Jupyter notebook and in an executable Python script.

so when you do this stuff it is very important to turn the echo=True on the engine. Because the SQLite compiler is returning a different result than the default one:

compare:
        
        str(query_with_negated_disjunction_with_label)

to:

        str(query_with_negated_disjunction_with_label.compile(engine))


Why that is, I have no idea.  Let's see.

from sqlalchemy import *

expr = or_(column('x') == 1, column('y') == 2)

bug = not_(expr.label('foo'))

from sqlalchemy.dialects import sqlite

print bug.compile()
print bug.compile(dialect=sqlite.dialect())

output:

NOT x = :x_1 OR y = :y_1
x = ? OR y = ? = 0

So, this seems very odd at first but this is in fact hitting another element in the code that even has a TODO around it (https://github.com/zzzeek/sqlalchemy/blob/master/lib/sqlalchemy/sql/elements.py#L656), because it doesn't know what to do with your label() (again, unsupported / never anticipated use case) it assumes that you're giving it a true_() / false_() constant, so you see in the case of SQLite it is trying to compare the whole thing to zero because SQLite doesn't have true/false constants.

I've put this up at https://bitbucket.org/zzzeek/sqlalchemy/issues/3969/negation-of-labeled-element . SQL element changes like these are never trivial things because they can have far reaching affects so I encourage you do just not use label() until the moment you are putting your expression into the top-level columns clause. thanks for the clear test case!





So...is there a SQL reason I might not be aware of why negating a labelled expression should behave in this way? If not, should I consider this a bug in SQLAlchemy and report it?


Thank you!

--
SQLAlchemy -
The Python SQL Toolkit and Object Relational Mapper

http://www.sqlalchemy.org/

To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description.
---
You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected] <mailto:[email protected]>. To post to this group, send email to [email protected] <mailto:[email protected]>.
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

--
SQLAlchemy - The Python SQL Toolkit and Object Relational Mapper

http://www.sqlalchemy.org/

To post example code, please provide an MCVE: Minimal, Complete, and Verifiable 
Example.  See  http://stackoverflow.com/help/mcve for a full description.
--- You received this message because you are subscribed to the Google Groups "sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

Reply via email to