web-dev-qa-db-fra.com

MySQLdb.cursor.execute ne peut pas exécuter plusieurs requêtes

Nous essayons d'exécuter des fichiers SQL contenant plusieurs instructions d'insertion sous la forme d'une requête unique, mais il semble que rollback échoue lorsqu'une de ces instructions contient une erreur.

Configuration MySQLd:

sql_mode = STRICT_ALL_TABLES
default-storage-engine = innodb

Code Python:

from contextlib import closing
import MySQLdb
database_connection = MySQLdb.connect(Host="127.0.0.1", user="root")
with closing(database_connection.cursor()) as cursor:
    database_connection.begin()
    cursor.execute('DROP DATABASE IF EXISTS db_name')
    cursor.execute('CREATE DATABASE db_name')
    cursor.execute('USE db_name')
    cursor.execute('CREATE TABLE table_name(first_field INTEGER)')
with closing(database_connection.cursor()) as cursor:
    try:
        database_connection.begin()
        cursor.execute('USE db_name')
        cursor.execute('INSERT INTO table_name VALUES (1)')
        cursor.execute('INSERT INTO table_name VALUES ("non-integer value")')
        database_connection.commit()
    except Exception as error:
        print("Exception thrown: {0}".format(error))
        database_connection.rollback()
        print("Rolled back")
with closing(database_connection.cursor()) as cursor:
    try:
        database_connection.begin()
        cursor.execute('USE db_name')
        cursor.execute('INSERT INTO table_name VALUES (1); INSERT INTO table_name VALUES ("non-integer value")')
        database_connection.commit()
    except:
        print("Exception thrown: {0}".format(error))
        database_connection.rollback()
        print("Rolled back")

Résultat attendu: "Exception levée" et "Annulation" imprimées deux fois.

Résultat actuel avec MySQL-python 1.2.4:

Exception thrown: (1366, "Incorrect integer value: 'non-integer value' for column 'first_field' at row 1")
Rolled back
Exception thrown: (1366, "Incorrect integer value: 'non-integer value' for column 'first_field' at row 1")
Traceback (most recent call last):
  File "test.py", line 30, in <module>
    print("Rolled back")
  File ".../python-2.7/lib/python2.7/contextlib.py", line 154, in __exit__
    self.thing.close()
  File ".../virtualenv-python-2.7/lib/python2.7/site-packages/MySQLdb/cursors.py", line 100, in close
    while self.nextset(): pass
  File ".../virtualenv-python-2.7/lib/python2.7/site-packages/MySQLdb/cursors.py", line 132, in nextset
    nr = db.next_result()
_mysql_exceptions.OperationalError: (1366, "Incorrect integer value: 'non-integer value' for column 'first_field' at row 1")

Ce qui donne? Devons-nous vraiment analyser le code SQL pour scinder les instructions (avec tous les traitements d'échappement et de cotes que cela implique) afin de les exécuter dans plusieurs executes?

11
l0b0

Apparemment, il n'y a aucun moyen de faire cela dans MySQLdb (aka. MySQL-python), nous avons donc fini par communicateing les données à subprocess.Popen([mysql, ...], stdin=subprocess.PIPE) et en vérifiant la returncode.

3
l0b0

Comme toutes les implémentations de Python DB-API 2.0 , la méthode cursor.execute() est conçue pour ne prendre que one , car elle donne des garanties quant à l’état du curseur par la suite.

Utilisez la méthode cursor.executemany() à la place. Notez que, conformément à la spécification DB-API 2.0 :

L'utilisation de cette méthode pour une opération produisant un ou plusieurs jeux de résultats constitue un comportement indéfini, et l'implémentation est autorisée (mais pas obligatoire) à déclencher une exception lorsqu'elle détecte qu'un jeu de résultats a été créé par un appel de l'opération.

L'utilisation de ceci pour plusieurs instructions INSERT devrait suffire:

cursor.executemany('INSERT INTO table_name VALUES (%s)',
    [(1,), ("non-integer value",)]
)

Si vous devez exécuter une série d'instructions disparates, comme à partir d'un script, dans la plupart des cas, vous pouvez simplement scinder les instructions sur ; et alimenter chaque instruction dans cursor.execute() séparément.

13
Martijn Pieters

Je pense que vous devez passer multi=True à execute lorsque vous utilisez plusieurs instructions, voir http://dev.mysql.com/doc/connector-python/en/connector-python-api-mysqlcursor-execute.html

Update: Ceci s'applique au module mysql.connector et non à la variable MySQLdb utilisée dans ce cas.

4
Johannes

J'ai essayé la méthode multi=True, mais j'ai fini par scinder le fichier en semi-boucle et en boucle. Évidemment, ne pas aller au travail si vous avez échappé aux demi-finales, mais semblait être la meilleure méthode pour moi.

with connection.cursor() as cursor:
    for statement in script.split(';'):
        if len(statement) > 0:
             cursor.execute(statement + ';')
1
DavidSM

Utiliser le programme mysql via Popen fonctionnera certainement, mais si vous voulez simplement utiliser une connexion existante (et un curseur), le paquetage sqlparse a une fonction split qui se scinde en instructions. Je ne suis pas sûr de la compatibilité, mais j'ai un script qui fait:

with open('file.sql', 'rb') as f:
    for statement in sqlparse.split(f.read()):
        if not statement:
            continue
        cur.execute(statement)

Il ne fait que nourrir les instructions DROP TABLE et CREATE TABLE, mais fonctionne pour moi.

https://pypi.python.org/pypi/sqlparse

0
morganwahl