it-swarm-eu.dev

exécutez le fichier * .sql avec python MySQLdb

Comment exécuter un script SQL stocké dans un fichier * .sql en utilisant le pilote python MySQLdb. j'essayais


cursor.execute(file(PATH_TO_FILE).read())

mais cela ne fonctionne pas parce que curseur.exécute ne peut exécuter qu'une seule commande SQL à la fois. Mon script SQL contient plusieurs instructions SQL à la place. Aussi j'essayais


cursor.execute('source %s'%PATH_TO_FILE)

mais aussi sans succès.

20
Mykola Kharechko
for line in open(PATH_TO_FILE):
    cursor.execute(line)

Cela suppose que vous avez une instruction SQL par ligne dans votre fichier. Sinon, vous devrez écrire des règles pour joindre des lignes.

10
Thomas K

Depuis python, je lance un processus mysql pour exécuter le fichier pour moi:

from subprocess import Popen, PIPE
process = Popen(['mysql', db, '-u', user, '-p', passwd],
                stdout=PIPE, stdin=PIPE)
output = process.communicate('source ' + filename)[0]
33
jdferreira

J'avais également besoin d'exécuter un fichier SQL, mais le problème était qu'il n'y avait pas une instruction par ligne, donc la réponse acceptée ne fonctionnait pas pour moi. 

Le fichier SQL que je voulais exécuter ressemblait à ceci:

-- SQL script to bootstrap the DB:
--
CREATE USER 'x'@'%' IDENTIFIED BY 'x';
GRANT ALL PRIVILEGES ON mystore.* TO 'x'@'%';
GRANT ALL ON `%`.* TO 'x'@`%`;
FLUSH PRIVILEGES;
--
--
CREATE DATABASE oozie;
GRANT ALL PRIVILEGES ON oozie.* TO 'oozie'@'localhost' IDENTIFIED BY 'oozie';
GRANT ALL PRIVILEGES ON oozie.* TO 'oozie'@'%' IDENTIFIED BY 'oozie';
FLUSH PRIVILEGES;
--
USE oozie;
--
CREATE TABLE `BUNDLE_ACTIONS` (
  `bundle_action_id` varchar(255) NOT NULL,
  `bundle_id` varchar(255) DEFAULT NULL,
  `coord_id` varchar(255) DEFAULT NULL,
  `coord_name` varchar(255) DEFAULT NULL,
  `critical` int(11) DEFAULT NULL,
  `last_modified_time` datetime DEFAULT NULL,
  `pending` int(11) DEFAULT NULL,
  `status` varchar(255) DEFAULT NULL,
  `bean_type` varchar(31) DEFAULT NULL,
  PRIMARY KEY (`bundle_action_id`),
  KEY `I_BNDLTNS_DTYPE` (`bean_type`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
--

Certaines instructions du fichier ci-dessus se trouvent sur une seule ligne et certaines s'étendent également sur plusieurs lignes (comme CREATE TABLE à la fin). Il existe également quelques lignes de commentaires en ligne SQL qui commencent par "-".

Comme suggéré par ThomasK, j'ai dû écrire quelques règles simples pour joindre des lignes dans une déclaration. Je me suis retrouvé avec une fonction pour exécuter un fichier SQL:

def exec_sql_file(cursor, sql_file):
    print "\n[INFO] Executing SQL script file: '%s'" % (sql_file)
    statement = ""

    for line in open(sql_file):
        if re.match(r'--', line):  # ignore sql comment lines
            continue
        if not re.search(r'[^-;]+;', line):  # keep appending lines that don't end in ';'
            statement = statement + line
        else:  # when you get a line ending in ';' then exec statement and reset for next statement
            statement = statement + line
            #print "\n\n[DEBUG] Executing SQL statement:\n%s" % (statement)
            try:
                cursor.execute(statement)
            except (OperationalError, ProgrammingError) as e:
                print "\n[WARN] MySQLError during execute statement \n\tArgs: '%s'" % (str(e.args))

            statement = ""

Je suis sûr que des améliorations sont possibles, mais pour l’instant, cela fonctionne plutôt bien pour moi. J'espère que quelqu'un le trouvera utile.

13
nonbeing

Au moins MySQLdb 1.2.3 semble permettre cela, il vous suffit d'appeler cursor.nextset() pour parcourir les ensembles de résultats renvoyés.

db = conn.cursor()
db.execute('SELECT 1; SELECT 2;')

more = True
while more:
    print db.fetchall()
    more = db.nextset()

Si vous voulez être absolument sûr que le support pour cela est activé et/ou le désactiver, vous pouvez utiliser quelque chose comme ceci:

MYSQL_OPTION_MULTI_STATEMENTS_ON = 0
MYSQL_OPTION_MULTI_STATEMENTS_OFF = 1

conn.set_server_option(MYSQL_OPTION_MULTI_STATEMENTS_ON)
# Multiple statement execution here...
conn.set_server_option(MYSQL_OPTION_MULTI_STATEMENTS_OFF)
8
Aleksi Torhamo

Cela a fonctionné pour moi:

with open('schema.sql') as f:
    cursor.execute(f.read().decode('utf-8'), multi=True)
6
madogan

Une autre solution permettant d’utiliser l’interpréteur MySQL sans aucune analyse consiste à utiliser la commande os.system pour exécuter une commande MySQL Prompt directement dans python:

from os import system
USERNAME = "root"
PASSWORD = "root"
DBNAME = "pablo"
Host = "localhost"
PORT = 3306
FILE = "file.sql"
command = """mysql -u %s -p"%s" --Host %s --port %s %s < %s""" %(USERNAME, PASSWORD, Host, PORT, DBNAME, FILE)
system(command)

Cela évite toute erreur d'analyse lorsque vous avez par exemple une variable de chaîne avec un smiley ;-) ou si vous recherchez le ; comme dernier caractère, si vous avez ensuite des commentaires comme SELECT * FROM foo_table; # selecting data

4
Yohan Obadia

La réponse acceptée rencontrera des problèmes lorsque votre script SQL contient des lignes vides et que votre phrase de requête s'étend sur plusieurs lignes. En utilisant plutôt l'approche suivante, le problème sera résolu:

f = open(filename, 'r')
query = " ".join(f.readlines())
c.execute(query)
3
Yuhao

Beaucoup de réponses ici ont de graves défauts ...

D'abord, n'essayez pas d'analyser vous-même un script SQL à composition non limitée! Si vous pensez que cela est facile à faire, vous ne savez pas à quel point SQL peut être robuste et compliqué. Les scripts SQL sérieux impliquent certainement des instructions et des définitions de procédures couvrant plusieurs lignes. Il est également courant de déclarer et de modifier explicitement les délimiteurs au milieu de vos scripts. Vous pouvez également imbriquer des commandes sources les unes dans les autres. Pour de nombreuses raisons, vous souhaitez exécuter le script via le client MySQL et lui permettre de gérer le gros du travail. Essayer de réinventer cela représente un risque considérable et une énorme perte de temps. Peut-être que si vous êtes le seul à écrire ces scripts et que vous n'écrivez rien de sophistiqué, vous pourriez vous en tirer, mais pourquoi vous limiter à un tel degré? Qu'en est-il des scripts générés par la machine ou de ceux d'autres développeurs? 

La réponse de @jdferreira est sur la bonne voie, mais présente également des problèmes et des faiblesses. Le plus important est qu'un trou de sécurité est en train d'être ouvert en envoyant les paramètres de connexion au processus de cette manière.

Voici une solution/exemple pour votre plaisir de copier/coller. Ma discussion prolongée suit:

Tout d’abord, créez un fichier de configuration séparé pour enregistrer votre nom d’utilisateur et votre mot de passe. 

db-creds.cfg

[client]
user     = XXXXXXX
password = YYYYYYY

Ajustez les autorisations de système de fichiers appropriées sur ce point afin que le processus python puisse en lire un, mais personne ne peut savoir qui ne devrait pas pouvoir le faire.

Ensuite, utilisez ce Python (dans mon exemple, le fichier creds est adjacent au script py):

#!/usr/bin/python

import os
import sys
import MySQLdb
from subprocess import Popen, PIPE, STDOUT

__MYSQL_CLIENT_PATH = "mysql"

__THIS_DIR = os.path.dirname( os.path.realpath( sys.argv[0] ) )

__DB_CONFIG_PATH    = os.path.join( __THIS_DIR, "db-creds.cfg" )
__DB_CONFIG_SECTION = "client"

__DB_CONN_Host = "localhost"
__DB_CONN_PORT = 3306

# ----------------------------------------------------------------

class MySqlScriptError( Exception ):

    def __init__( self, dbName, scriptPath, stdOut, stdErr ):
        Exception.__init__( self )
        self.dbName = dbName
        self.scriptPath = scriptPath
        self.priorOutput = stdOut
        self.errorMsg = stdErr                
        errNumParts = stdErr.split("(")        
        try : self.errorNum = long( errNumParts[0].replace("ERROR","").strip() )
        except: self.errorNum = None        
        try : self.sqlState = long( errNumParts[1].split(")")[0].strip() )
        except: self.sqlState = None

    def __str__( self ): 
        return ("--- MySqlScriptError ---\n" +
                "Script: %s\n" % (self.scriptPath,) +
                "Database: %s\n" % (self.dbName,) +
                self.errorMsg ) 

    def __repr__( self ): return self.__str__()

# ----------------------------------------------------------------

def databaseLoginParms() :        
    from ConfigParser import RawConfigParser
    parser = RawConfigParser()
    parser.read( __DB_CONFIG_PATH )   
    return ( parser.get( __DB_CONFIG_SECTION, "user" ).strip(), 
             parser.get( __DB_CONFIG_SECTION, "password" ).strip() )

def databaseConn( username, password, dbName ):        
    return MySQLdb.connect( Host=__DB_CONN_Host, port=__DB_CONN_PORT,
                            user=username, passwd=password, db=dbName )

def executeSqlScript( dbName, scriptPath, ignoreErrors=False ) :       
    scriptDirPath = os.path.dirname( os.path.realpath( scriptPath ) )
    sourceCmd = "SOURCE %s" % (scriptPath,)
    cmdList = [ __MYSQL_CLIENT_PATH,                
               "--defaults-extra-file=%s" % (__DB_CONFIG_PATH,) , 
               "--database", dbName,
               "--unbuffered" ] 
    if ignoreErrors : 
        cmdList.append( "--force" )
    else:
        cmdList.extend( ["--execute", sourceCmd ] )
    process = Popen( cmdList 
                   , cwd=scriptDirPath
                   , stdout=PIPE 
                   , stderr=(STDOUT if ignoreErrors else PIPE) 
                   , stdin=(PIPE if ignoreErrors else None) )
    stdOut, stdErr = process.communicate( sourceCmd if ignoreErrors else None )
    if stdErr is not None and len(stdErr) > 0 : 
        raise MySqlScriptError( dbName, scriptPath, stdOut, stdErr )
    return stdOut

Si vous voulez le tester, ajoutez ceci:

if __== "__main__": 

    ( username, password ) = databaseLoginParms()
    dbName = "ExampleDatabase"

    print "MySQLdb Test"
    print   
    conn = databaseConn( username, password, dbName )
    cursor = conn.cursor()
    cursor.execute( "show tables" )
    print cursor.fetchall()
    cursor.close()
    conn.close()
    print   

    print "-----------------"
    print "Execute Script with ignore errors"
    print   
    scriptPath = "test.sql"
    print executeSqlScript( dbName, scriptPath, 
                            ignoreErrors=True )
    print   

    print "-----------------"
    print "Execute Script WITHOUT ignore errors"                            
    print   
    try : print executeSqlScript( dbName, scriptPath )
    except MySqlScriptError as e :        
        print "dbName: %s" % (e.dbName,)
        print "scriptPath: %s" % (e.scriptPath,)
        print "errorNum: %s" % (str(e.errorNum),)
        print "sqlState: %s" % (str(e.sqlState),)
        print "priorOutput:"        
        print e.priorOutput
        print
        print "errorMsg:"
        print e.errorMsg           
        print
        print e
    print   

Et pour faire bonne mesure, voici un exemple de script SQL à utiliser: 

test.sql

show tables;
blow up;
show tables;

Alors, maintenant pour une discussion.

Premièrement, je montre comment utiliser MySQLdb avec cette exécution de script externe, tout en stockant les creds dans un fichier partagé que vous pouvez utiliser pour les deux.

En utilisant --defaults-extra-file sur la ligne de commande, vous pouvez SÉCURISER vos paramètres de connexion.

La combinaison de --force avec stdin transmettant en continu la commande source OR --execute exécutant la commande à l'extérieur vous permet de dicter le mode d'exécution du script. Cela consiste à ignorer les erreurs et à continuer à fonctionner, ou à vous arrêter dès qu'une erreur se produit. 

L'ordre dans lequel le retour des résultats sera également préservé via --unbuffered. Sans cela, vos flux stdout et stderr seront mélangés et indéfinis dans leur ordre, ce qui rend très difficile de comprendre ce qui a fonctionné et ce qui n'a pas fonctionné en comparant cela à l'entrée sql.

Avec Popen cwd=scriptDirPath, vous pouvez imbriquer des commandes sources les unes dans les autres en utilisant des chemins relatifs. Si vos scripts se trouvent tous dans le même répertoire (ou un chemin d'accès connu par rapport à celui-ci), vous pouvez ainsi référencer ceux relatifs à l'emplacement du script de niveau supérieur.

Enfin, j’ai jeté une classe d’exception contenant toutes les informations que vous pourriez souhaiter sur ce qui s’est passé. Si vous n'utilisez pas l'option ignoreErrors, l'une de ces exceptions sera renvoyée dans votre python lorsque quelque chose ne va pas et que le script a cessé de fonctionner en raison de cette erreur.

3
BuvinJ

Charger le fichier mysqldump:

for line in open(PATH_TO_FILE).read().split(';\n'):
    cursor.execute(line)
1
Антон Дзык

Pourquoi ne pas utiliser la bibliothèque pexpect ? L'idée est que vous pouvez démarrer un processus pexpect.spawn(...) et attendre que la sortie de ce processus contienne un certain motif process.expect(pattern).

En fait, je l’ai utilisée pour me connecter au client mysql et exécuter quelques scripts SQL.

Connexion :

import pexpect
process = pexpect.spawn("mysql", ["-u", user, "-p"])
process.expect("Enter password")
process.sendline(password)
process.expect("mysql>")

Ainsi, le mot de passe n'est pas codé en dur dans le paramètre de ligne de commande (élimine le risque de sécurité).

Exécution même de plusieurs scripts SQL :

error = False
for script in sql_scripts:
    process.sendline("source {};".format(script))
    index = process.expect(["mysql>", "ERROR"])

    # Error occurred, interrupt
    if index == 1:
        error = True
        break

if not error:
    # commit changes of the scripts
    process.sendline("COMMIT;")
    process.expect("mysql>")

    print "Everything fine"
else:
    # don't commit + print error message
    print "Your scripts have errors"

Notez que vous appelez toujours expect(pattern) et que cela correspond, sinon vous obtiendrez une erreur de dépassement de délai. J'avais besoin de ce morceau de code pour exécuter plusieurs scripts SQL et ne valider leurs modifications que si aucune erreur ne se produisait, mais il est facilement adaptable aux cas d'utilisation avec un seul script.

0
New2HTML

Pouvez-vous utiliser un pilote de base de données différent?
Si oui: ce que vous voulez est possible avec le pilote MySQL Connector/Python driver de MySQL.

Sa méthode cursor.execute prend en charge l'exécution simultanée de plusieurs instructions SQL en transmettant Multi=True.

Fractionner les instructions SQL dans le fichier par un point-virgule est pas nécessaire.

Exemple simple (principalement copier-coller du deuxième lien, je viens d’ajouter la lecture du code SQL du fichier)} _:

import mysql.connector

file = open('test.sql')
sql = file.read()

cnx = mysql.connector.connect(user='uuu', password='ppp', Host='hhh', database='ddd')
cursor = cnx.cursor()

for result in cursor.execute(sql, multi=True):
  if result.with_rows:
    print("Rows produced by statement '{}':".format(
      result.statement))
    print(result.fetchall())
  else:
    print("Number of rows affected by statement '{}': {}".format(
      result.statement, result.rowcount))

cnx.close()

J'utilise ceci pour importer des sauvegardes MySQL (créées dans phpMyAdmin en exportant l'intégralité de la base de données dans un fichier SQL) à partir du fichier * .sql dans une base de données.

0
Christian Specht

Voici un extrait de code qui importera un .sql typique provenant d’une exportation. (Je l'ai utilisé avec succès avec les exportations de Sequel Pro.) Traite les requêtes multilignes et les commentaires (#).

  • Note 1: J'ai utilisé les premières lignes de la réponse de Thomas K mais en ai ajouté plus.
  • Remarque 2: pour les débutants, remplacez DB_Host, DB_PASS, etc. par vos informations de connexion à la base de données.

import MySQLdb
from configdb import DB_Host, DB_PASS, DB_USER, DB_DATABASE_NAME

db = MySQLdb.connect(Host=DB_Host,    # your Host, usually localhost
                     user=DB_USER,         # your username
                     passwd=DB_PASS,  # your password
                     db=DB_DATABASE_NAME)        # name of the data base

cur = db.cursor()

PATH_TO_FILE = "db-testcases.sql"

fullLine = ''

for line in open(PATH_TO_FILE):
  tempLine = line.strip()

  # Skip empty lines.
  # However, it seems "strip" doesn't remove every sort of whitespace.
  # So, we also catch the "Query was empty" error below.
  if len(tempLine) == 0:
    continue

  # Skip comments
  if tempLine[0] == '#':
    continue

  fullLine += line

  if not ';' in line:
    continue

  # You can remove this. It's for debugging purposes.
  print "[line] ", fullLine, "[/line]"

  try:
    cur.execute(fullLine)
  except MySQLdb.OperationalError as e:
    if e[1] == 'Query was empty':
      continue

    raise e

  fullLine = ''

db.close()
0
SilentSteel