token = Token( user=user, client_sig=sigstring, ) session.add(token) # session.flush() INSERT INTO tokens(id, user_id, client_sig)SELECT '51…bb', 'fd…b3', 'some string'FROM dualWHERE NOT EXISTS ( SELECT * FROM tokens WHERE user_id='fd…b3' AND client_sig='some string' );that's the first red flag here, why can't you put a unique constraint here?
I doubt MySQL requires the "Dual" part, here is a demo
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Token(Base):
__tablename__ = 'tokens'
id = Column(String(50), primary_key=True)
user_id = Column(String(50))
client_sig = Column(String(50))
e = create_engine("mysql://scott:tiger@localhost/test", echo=True)
Base.metadata.drop_all(e)
Base.metadata.create_all(e)
stmt = select([
literal_column("'abc'"),
literal_column("'def'"),
literal_column("'ghi'"),
]).where(
~exists().where(and_(Token.user_id == 'def', Token.client_sig == 'ghi'))
)
e.execute(
insert(Token).from_select(['id', 'user_id', 'client_sig'], stmt)
)
# Create a proxy object to return to the caller.token = Token(
id=uuid4(), user=user_id, client_sig=sigstring, )# If I don't expunge then the token is automatically added/inserted?# I thought I'd always have to dbsession.add().dbsession.expunge(token)
# Now issue the SQL statement much like the on from my initial question.sql = (
"INSERT INTO tokens(id, user_id, client_sig) " "SELECT '" + token.id + "', '" + token.user_id + "', '" + token.client_sig + "' " "FROM dual " "WHERE NOT EXISTS " "(SELECT * FROM tokens WHERE user_id='" + token.user_id + "' AND client_sig='" + token.client_sig + "')" )dbsession.execute(sql)
# Return the proxy object to the Pyramid view function.return token>>> 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_12017-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_12017-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_12017-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 ROLLBACK2017-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
>>> 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 ROLLBACKTraceback (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'}]from sqlalchemy.sql.elements import quoted_name from sqlalchemy.sql.expression import literal_column Hah 🤔 Boy this is (not really) funny. Thank you for digging into this, Mike!I had to make two minor import adjustmentsfrom sqlalchemy.sql.elements import quoted_namefrom 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 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.
To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+unsubscribe@googlegroups.com.
dbsession.execute("LOCK TABLES tokens WRITE") dbsession.execute(cond_insert) # As per above discussion. dbsession.execute("UNLOCK TABLES")is that session using "autocommit" mode?
it looks like the error is raised on the UNLOCK ?
--> autocommit False2017-12-07 11:23:52,101 INFO [sqlalchemy.engine.base.Engine][MainThread] LOCK TABLES tokens WRITE2017-12-07 11:23:52,101 INFO [sqlalchemy.engine.base.Engine][MainThread] {}2017-12-07 11:23:52,102 INFO [sqlalchemy.engine.base.Engine][MainThread] INSERT INTO tokens (id, user_id, client_sig) SELECT '84…20', '39…30', '0b…87' FROM dual WHERE NOT (EXISTS (SELECT * FROM tokens WHERE tokens.user_id = %(user_id_1)s AND tokens.client_sig = %(client_sig_1)s))2017-12-07 11:23:52,102 INFO [sqlalchemy.engine.base.Engine][MainThread] {'user_id_1': '39…30', 'client_sig_1': '0b…87'}2017-12-07 11:23:52,103 ERROR [srv.views.exceptions][MainThread] Internal server error detected, stacktrace follows.