Выполнить *.sql файл с помощью python MySQLdb

Как выполнить sql script, хранящийся в файле *.sql, используя драйвер python MySQLdb. Я пытался


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

но это не работает, потому что cursor.execute может запускать только одну команду sql сразу. Мой sql script содержит несколько операторов sql. Также я пытался


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

но также без успеха.

Ответы

Ответ 1

for line in open(PATH_TO_FILE):
    cursor.execute(line)

Это предполагает, что у вас есть один оператор SQL для каждой строки в вашем файле. В противном случае вам нужно будет написать несколько правил для объединения строк.

Ответ 2

Из python я запускаю процесс mysql для выполнения файла для меня:

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

Ответ 3

Мне также нужно было выполнить файл SQL, но суть в том, что в каждой строке не было одного оператора, поэтому принятый ответ мне не помог.

Файл SQL, который я хотел выполнить, выглядел так:

-- 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;
--
--

Некоторые операторы в приведенном выше файле лежат в одной строке, а некоторые операторы также занимают несколько строк (например, CREATE TABLE в конце). Есть также несколько строчных комментариев SQL, которые начинаются с "-".

По предложению ThomasK мне пришлось написать несколько простых правил, чтобы объединить строки в утверждении. Я закончил с функцией для выполнения файла 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 = ""

Я уверен, что есть возможности для улучшения, но пока он работает довольно хорошо для меня. Надеюсь, кто-то найдет это полезным.

Ответ 4

По крайней мере, MySQLdb 1.2.3, похоже, разрешает это из коробки, вам просто нужно вызвать cursor.nextset() для циклического переключения возвращаемых наборов результатов.

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

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

Если вы хотите быть абсолютно уверены, что поддержка этого включена и/или отключить поддержку, вы можете использовать что-то вроде этого:

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)

Ответ 5

Это сработало для меня:

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

Ответ 6

Другим решением, которое позволяет использовать интерпретатор MySQL без разбора, является использование команды os.system для запуска командной строки MySQL непосредственно внутри 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)

Он избегает любой ошибки синтаксического анализа, когда, например, у вас будет строковая переменная с смайликом ;-) в ней или если вы проверяете ; как последний символ, если у вас есть комментарии позже, как SELECT * FROM foo_table; # selecting data

Ответ 7

Многие из ответов здесь имеют серьезные недостатки...

Во-первых, не пытайтесь разобрать открытый скрипт SQL! Если вы думаете, что это легко сделать, вы не знаете, насколько надежным и сложным может быть SQL. Серьезные сценарии sql, безусловно, включают в себя операторы и определения процедур, занимающие несколько строк. Также обычно явно объявлять и изменять разделители в середине ваших сценариев. Вы также можете вкладывать исходные команды друг в друга. По многим причинам вы хотите запустить скрипт через клиент MySQL и позволить ему справляться с тяжелыми операциями. Попытка изобрести это чревато опасностью и огромной тратой времени. Может быть, если вы являетесь единственным, кто пишет эти сценарии, и вы не пишете ничего сложного, вам это сойдет с рук, но зачем ограничивать себя до такой степени? А как насчет машинно-сгенерированных скриптов или скриптов, написанных другими разработчиками?

Ответ @jdferreira находится на правильном пути, но также имеет проблемы и недостатки. Наиболее важным является то, что дыра в безопасности открывается путем отправки параметров соединения процессу таким образом.

Вот решение/пример для вашего удовольствия копирования и вставки. Мое расширенное обсуждение следующее:

Сначала создайте отдельный файл конфигурации для сохранения вашего имени пользователя и пароля.

дб-creds.cfg

[client]
user     = XXXXXXX
password = YYYYYYY

Подберите подходящие разрешения для файловой системы, чтобы процесс python мог читать с него, но никто не может увидеть того, кто не должен этого делать.

Затем используйте этот Python (в моем примере файл creds находится рядом со скриптом 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

Если вы хотите проверить это, добавьте это:

if __name__ == "__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   

И для хорошей меры, вот пример сценария sql для подачи в него:

Test.sql

show tables;
blow up;
show tables;

Итак, теперь для некоторого обсуждения.

Сначала я покажу, как использовать MySQLdb вместе с выполнением этого внешнего скрипта, сохраняя при этом кредиты в одном общем файле, который вы можете использовать для обоих.

Используя --defaults-extra-file в командной строке, вы можете БЕЗОПАСНО передавать параметры подключения в.

Сочетание либо --force с потоковой передачей stdin исходной команды, либо ИЛИ --execute выполняющего команду снаружи, позволяет вам определять, как будет выполняться скрипт. Это происходит путем игнорирования ошибок и продолжения работы или остановки, как только возникает ошибка.

Порядок, в котором возвращаются результаты, также будет сохранен через --unbuffered. Без этого ваши потоки stdout и stderr будут перемешаны и не определены в своем порядке, что очень затруднит понимание того, что сработало, а что нет, при сравнении этого с вводом sql.

Использование cwd=scriptDirPath позволяет вам cwd=scriptDirPath исходные команды друг в друга, используя относительные пути. Если все ваши скрипты будут находиться в одном каталоге (или известном пути к нему), то это позволит вам ссылаться на те, в которых находится скрипт верхнего уровня.

Наконец, я добавил класс исключений, в котором содержится вся информация о том, что произошло. Если вы не используете опцию ignoreErrors, одно из этих исключений будет брошено в ваш питон, когда что-то пойдет не так, и скрипт остановится из-за этой ошибки.

Ответ 8

Принятый ответ столкнется с проблемами, когда ваш sql script содержит пустые строки, а ваше предложение предложения охватывает несколько строк. Вместо этого, используя следующий подход, вы решите проблему:

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

Ответ 9

Загрузите файл mysqldump:

for line in open(PATH_TO_FILE).read().split(';\n'):
    cursor.execute(line)

Ответ 10

Вот фрагмент кода, который импортирует типичный .sql, который поступает из экспорта. (Я использовал его с экспортом из Sequel Pro успешно.) Сделки с многострочными запросами и комментариями (#).

  • Примечание 1: Я использовал начальные строки ответа Thomas K, но добавил больше.
  • Примечание 2: Для новичков замените DB_HOST, DB_PASS и т.д. информацией о подключении к базе данных.

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 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()

Ответ 11

Вы можете использовать другой драйвер базы данных?
Если да: то, что вы хотите, возможно с драйвером MySQL Connector/Python от MySQL.

Его метод cursor.execute поддерживает одновременное выполнение нескольких операторов SQL путем передачи Multi=True.

Расщепление операторы SQL в файле точкой с запятой не обязательно.

Простой пример (в основном копирование и вставка со второй ссылки, я только что добавил чтение SQL из файла):

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()

Я использую это для импорта дампов MySQL (созданных в phpMyAdmin путем экспорта всей базы данных в файл SQL) из файла *.sql обратно в базу данных.

Ответ 12

Как насчет использования библиотеки pexpect? Идея состоит в том, что вы можете запустить процесс pexpect.spawn(...) и подождать, пока на выходе этого процесса не появится определенный шаблон process.expect(pattern).

Я фактически использовал это для подключения к клиенту mysql и выполнения некоторых сценариев sql.

Подключение:

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

Таким образом, пароль не жестко запрограммирован в параметре командной строки (устраняет угрозу безопасности).

Выполнение даже нескольких скриптов 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"

Остерегайтесь того, что вы всегда вызываете expect(pattern), и что он совпадает, иначе вы получите ошибку тайм-аута. Мне понадобился этот фрагмент кода для выполнения нескольких сценариев sql и фиксации их изменений только в случае отсутствия ошибок, но его легко адаптировать к сценариям использования только с одним сценарием.