Hah 🤔 Boy this is (not really) funny. Thank you for digging into this, 
Mike!

I had to make two minor import adjustments

from sqlalchemy.sql.elements import quoted_name                            
                         
from sqlalchemy.sql.expression import literal_column                        
                        

but otherwise the code works now. I still think it’s a poor solution to my 
problem. The proper solution, I think, would be a CHECK constraint 
<https://en.wikipedia.org/wiki/Check_constraint> across the columns and a 
simple INSERT which can fail the constraint.

Alas, MySQL doesn’t do CHECK constraints—another reason to migrate to 
PostgreSQL as soon as possible. 

Jens


On Tuesday, November 28, 2017 at 9:23:46 AM UTC+10, Mike Bayer wrote:
>
> On Mon, Nov 27, 2017 at 4:02 PM,  <[email protected] <javascript:>> 
> wrote: 
> > 
> > 
> > No problem, here it is. To work with your initial code example... 
> > 
> >>>> e = 
> >>>> 
> create_engine("mysql+pymysql://jens@localhost/test?charset=utf8&unix_socket=/opt/local/var/run/mysql56/mysqld.sock",
>  
>
> >>>> echo=True) 
> >>>> Base.metadata.drop_all(e) 
> > 2017-11-28 06:47:21,171 INFO sqlalchemy.engine.base.Engine SHOW 
> VARIABLES 
> > LIKE 'sql_mode' 
> > 2017-11-28 06:47:21,171 INFO sqlalchemy.engine.base.Engine {} 
> > 2017-11-28 06:47:21,173 INFO sqlalchemy.engine.base.Engine SELECT 
> DATABASE() 
> > 2017-11-28 06:47:21,173 INFO sqlalchemy.engine.base.Engine {} 
> > 2017-11-28 06:47:21,174 INFO sqlalchemy.engine.base.Engine show 
> collation 
> > where `Charset` = 'utf8' and `Collation` = 'utf8_bin' 
> > 2017-11-28 06:47:21,174 INFO sqlalchemy.engine.base.Engine {} 
> > 2017-11-28 06:47:21,175 INFO sqlalchemy.engine.base.Engine SELECT 
> CAST('test 
> > plain returns' AS CHAR(60)) AS anon_1 
> > 2017-11-28 06:47:21,175 INFO sqlalchemy.engine.base.Engine {} 
> > 2017-11-28 06:47:21,176 INFO sqlalchemy.engine.base.Engine SELECT 
> CAST('test 
> > unicode returns' AS CHAR(60)) AS anon_1 
> > 2017-11-28 06:47:21,176 INFO sqlalchemy.engine.base.Engine {} 
> > 2017-11-28 06:47:21,176 INFO sqlalchemy.engine.base.Engine SELECT 
> CAST('test 
> > collated returns' AS CHAR CHARACTER SET utf8) COLLATE utf8_bin AS anon_1 
> > 2017-11-28 06:47:21,176 INFO sqlalchemy.engine.base.Engine {} 
> > 2017-11-28 06:47:21,177 INFO sqlalchemy.engine.base.Engine DESCRIBE 
> `tokens` 
> > 2017-11-28 06:47:21,177 INFO sqlalchemy.engine.base.Engine {} 
> > 2017-11-28 06:47:21,179 INFO sqlalchemy.engine.base.Engine ROLLBACK 
> >>>> Base.metadata.create_all(e) 
> > 2017-11-28 06:47:23,490 INFO sqlalchemy.engine.base.Engine DESCRIBE 
> `tokens` 
> > 2017-11-28 06:47:23,490 INFO sqlalchemy.engine.base.Engine {} 
> > 2017-11-28 06:47:23,495 INFO sqlalchemy.engine.base.Engine ROLLBACK 
> > 2017-11-28 06:47:23,496 INFO sqlalchemy.engine.base.Engine 
> > CREATE TABLE tokens ( 
> > id VARCHAR(50) NOT NULL, 
> > user_id VARCHAR(50), 
> > client_sig VARCHAR(50), 
> > PRIMARY KEY (id) 
> > ) 
> > 2017-11-28 06:47:23,496 INFO sqlalchemy.engine.base.Engine {} 
> > 2017-11-28 06:47:23,507 INFO sqlalchemy.engine.base.Engine COMMIT 
> >>>> stmt = select([ 
> > ...     literal_column("'abc'"), 
> > ...     literal_column("'def'"), 
> > ...     literal_column("'ghi'"), 
> > ... ]).where( 
> > ...     ~exists().where(and_(Token.user_id == 'def', Token.client_sig == 
> > 'ghi')) 
> > ... ) 
> >>>> 
> >>>> print(stmt) 
> > SELECT 'abc', 'def', 'ghi' 
> > WHERE NOT (EXISTS (SELECT * 
> > FROM tokens 
> > WHERE tokens.user_id = :user_id_1 AND tokens.client_sig = 
> :client_sig_1)) 
> > 
> > 
> >> OK I don't recall what conditions MySQL needs "FROM dual" can you share 
> >> with me: 
> >> 
> >> 1. the full version of MySQL 
>
> Well in standard MySQL / MariaDB fashion, they have made this as fun 
> as possible (well, more fun would be one database *rejects* FROM DUAL, 
> at least it isn't that bad): 
>
> 1. MySQL 5.6 requires "FROM DUAL" 
>
> 2. MySQL 5.7 does not require FROM DUAL but accepts it. 
>
> 3. MariaDB 10.1 does not require FROM DUAL but accepts it. 
>
> 4. MariaDB 10.2 *does* require FROM DUAL.     The two vendors have 
> **flip-flopped** on their preference of this issue. 
>
> Anyway, here's your dual, as is typical, to make it work completely we 
> need an esoteric trick to avoid quoting the "dual" word: 
>
> from sqlalchemy.sql import quoted_name 
> dual = table(quoted_name("dual", quote=False)) 
>
> then your statement: 
>
>     stmt = select([ 
>         literal_column("'abc'"), 
>         literal_column("'def'"), 
>         literal_column("'ghi'"), 
>     ]).select_from(dual).where( 
>         ~exists().where(and_(Token.user_id == 'def', Token.client_sig == 
> 'ghi')) 
>     ) 
>
>
>
>
>
> > 
> > 
> >>>> e.dialect.name, e.dialect.driver, e.dialect.server_version_info 
> > ('mysql', 'pymysql', (5, 6, 34)) 
> > 
> >> 2. the output of "SHOW VARIABLES LIKE '%SQL_MODE%' 
> >> 
> > 
> > mysql> show variables like '%sql_mode%'; 
> > +---------------+------------------------+ 
> > | Variable_name | Value                  | 
> > +---------------+------------------------+ 
> > | sql_mode      | NO_ENGINE_SUBSTITUTION | 
> > +---------------+------------------------+ 
> > 1 row in set (0.00 sec) 
> > 
> > 
> >> 3. stack trace + error message 
> > 
> > 
> >>>> e.execute( 
> > ...     insert(Token).from_select(['id', 'user_id', 'client_sig'], stmt) 
> > ... ) 
> > 2017-11-28 06:47:49,489 INFO sqlalchemy.engine.base.Engine INSERT INTO 
> > tokens (id, user_id, client_sig) SELECT 'abc', 'def', 'ghi' 
> > WHERE NOT (EXISTS (SELECT * 
> > FROM tokens 
> > WHERE tokens.user_id = %(user_id_1)s AND tokens.client_sig = 
> > %(client_sig_1)s)) 
> > 2017-11-28 06:47:49,489 INFO sqlalchemy.engine.base.Engine 
> {'client_sig_1': 
> > 'ghi', 'user_id_1': 'def'} 
> > 2017-11-28 06:47:49,491 INFO sqlalchemy.engine.base.Engine ROLLBACK 
> > Traceback (most recent call last): 
> >   File "/…/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 
> > 1182, in _execute_context 
> >     context) 
> >   File "/…/lib/python3.5/site-packages/sqlalchemy/engine/default.py", 
> line 
> > 470, in do_execute 
> >     cursor.execute(statement, parameters) 
> >   File "/…/lib/python3.5/site-packages/pymysql/cursors.py", line 166, in 
> > execute 
> >     result = self._query(query) 
> >   File "/…/lib/python3.5/site-packages/pymysql/cursors.py", line 322, in 
> > _query 
> >     conn.query(q) 
> >   File "/…/lib/python3.5/site-packages/pymysql/connections.py", line 
> 856, in 
> > query 
> >     self._affected_rows = self._read_query_result(unbuffered=unbuffered) 
> >   File "/…/lib/python3.5/site-packages/pymysql/connections.py", line 
> 1057, 
> > in _read_query_result 
> >     result.read() 
> >   File "/…/lib/python3.5/site-packages/pymysql/connections.py", line 
> 1340, 
> > in read 
> >     first_packet = self.connection._read_packet() 
> >   File "/…/lib/python3.5/site-packages/pymysql/connections.py", line 
> 1014, 
> > in _read_packet 
> >     packet.check_error() 
> >   File "/…/lib/python3.5/site-packages/pymysql/connections.py", line 
> 393, in 
> > check_error 
> >     err.raise_mysql_exception(self._data) 
> >   File "/…/lib/python3.5/site-packages/pymysql/err.py", line 107, in 
> > raise_mysql_exception 
> >     raise errorclass(errno, errval) 
> > pymysql.err.ProgrammingError: (1064, "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 'WHERE NOT (EXISTS (SELECT * \nFROM tokens \nWHERE 
> > tokens.user_id = 'def' AND token' at line 2") 
> > 
> > The above exception was the direct cause of the following exception: 
> > 
> > Traceback (most recent call last): 
> >   File "<stdin>", line 2, in <module> 
> >   File "/…/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 
> > 2064, in execute 
> >     return connection.execute(statement, *multiparams, **params) 
> >   File "/…/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 
> 945, 
> > in execute 
> >     return meth(self, multiparams, params) 
> >   File "/…/lib/python3.5/site-packages/sqlalchemy/sql/elements.py", line 
> > 263, in _execute_on_connection 
> >     return connection._execute_clauseelement(self, multiparams, params) 
> >   File "/…/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 
> > 1053, in _execute_clauseelement 
> >     compiled_sql, distilled_params 
> >   File "/…/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 
> > 1189, in _execute_context 
> >     context) 
> >   File "/…/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 
> > 1402, in _handle_dbapi_exception 
> >     exc_info 
> >   File "/…/lib/python3.5/site-packages/sqlalchemy/util/compat.py", line 
> 203, 
> > in raise_from_cause 
> >     reraise(type(exception), exception, tb=exc_tb, cause=cause) 
> >   File "/…/lib/python3.5/site-packages/sqlalchemy/util/compat.py", line 
> 186, 
> > in reraise 
> >     raise value.with_traceback(tb) 
> >   File "/…/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 
> > 1182, in _execute_context 
> >     context) 
> >   File "/…/lib/python3.5/site-packages/sqlalchemy/engine/default.py", 
> line 
> > 470, in do_execute 
> >     cursor.execute(statement, parameters) 
> >   File "/…/lib/python3.5/site-packages/pymysql/cursors.py", line 166, in 
> > execute 
> >     result = self._query(query) 
> >   File "/…/lib/python3.5/site-packages/pymysql/cursors.py", line 322, in 
> > _query 
> >     conn.query(q) 
> >   File "/…/lib/python3.5/site-packages/pymysql/connections.py", line 
> 856, in 
> > query 
> >     self._affected_rows = self._read_query_result(unbuffered=unbuffered) 
> >   File "/…/lib/python3.5/site-packages/pymysql/connections.py", line 
> 1057, 
> > in _read_query_result 
> >     result.read() 
> >   File "/…/lib/python3.5/site-packages/pymysql/connections.py", line 
> 1340, 
> > in read 
> >     first_packet = self.connection._read_packet() 
> >   File "/…/lib/python3.5/site-packages/pymysql/connections.py", line 
> 1014, 
> > in _read_packet 
> >     packet.check_error() 
> >   File "/…/lib/python3.5/site-packages/pymysql/connections.py", line 
> 393, in 
> > check_error 
> >     err.raise_mysql_exception(self._data) 
> >   File "/…/lib/python3.5/site-packages/pymysql/err.py", line 107, in 
> > raise_mysql_exception 
> >     raise errorclass(errno, errval) 
> > sqlalchemy.exc.ProgrammingError: (pymysql.err.ProgrammingError) (1064, 
> "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 'WHERE NOT (EXISTS 
> > (SELECT * \nFROM tokens \nWHERE tokens.user_id = 'def' AND token' at 
> line 
> > 2") [SQL: "INSERT INTO tokens (id, user_id, client_sig) SELECT 'abc', 
> 'def', 
> > 'ghi' \nWHERE NOT (EXISTS (SELECT * \nFROM tokens \nWHERE tokens.user_id 
> = 
> > %(user_id_1)s AND tokens.client_sig = %(client_sig_1)s))"] [parameters: 
> > {'client_sig_1': 'ghi', 'user_id_1': 'def'}] 
> > 
> >  Please let me know of anything else you need from me! 
> > Jens 
> > 
> > -- 
> > 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] <javascript:>. 
> > To post to this group, send email to [email protected] 
> <javascript:>. 
> > 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