简体   繁体   English

读取 .sql 文件以在 Python 中执行(pymysql)

[英]Reading .sql File in for Execution in Python (pymysql)

I'm attempting to create a multiscript tool, that will take an argument of a .sql file and execute it.我正在尝试创建一个多脚本工具,它将采用 .sql 文件的参数并执行它。

I've set up a simple test, just executing on one database, however the syntax is giving me issues every time.我设置了一个简单的测试,只在一个数据库上执行,但是语法每次都会给我带来问题。

DELIMITER $$
CREATE DEFINER=`a_user`@`%` PROCEDURE `a_procedure`(
    IN DirectEmployeeID TEXT,
    IN StartRange DATE,
    IN EndRange DATE
)
BEGIN
SELECT aColumn
WHERE thisThing = 1;
END$$
DELIMITER ;

To be clear, this script has been tested, and works when passed like :需要明确的是,这个脚本已经过测试,并且在通过时可以工作:

mysql -uuser -p -hhost -Pport databaseName < file.sql

and also works through mysql workbench.也可以通过 mysql 工作台工作。

I saw this type of solution on another site:我在另一个网站上看到了这种类型的解决方案:

with conn.cursor() as cursor:
    f = sys.argv[1]
    file = open(f, 'r')
    sql = " ".join(file.readlines())
    cursor.execute(sql)

which gives me a MySQL syntax error:这给了我一个 MySQL 语法错误:

pymysql.err.ProgrammingError: (1064, u"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 'DELIMITER $$\n CREATE DEFINER=`a_user`@`%` PROCEDURE 
`MyCommissionsDirect`(\n \tIN ' at line 1")

as you can see, there are newline characters within the script that mysql isn't liking.如您所见,脚本中有 mysql 不喜欢的换行符。

I then tried this:然后我尝试了这个:

with conn.cursor() as cursor:
    f = sys.argv[1]
    file = open(f, 'r')
    sql = ''
    line = file.readline()
    while line:
        sql += ' ' + line.strip('\n').strip('\t')
        line = file.readline()
    print sql
    cursor.execute(sql)

and get another syntax issue, the print shows that this is all one line, which is not working in mysqlworkbench.并得到另一个语法问题,打印显示这都是一行,这在 mysqlworkbench 中不起作用。 doesn't even try to execute it, which is strange.甚至不尝试执行它,这很奇怪。

When I put the DELIMETER $$ on a separate line first, it executes in mysqlworkbench.当我首先将 DELIMETER $$ 放在单独的行上时,它会在 mysqlworkbench 中执行。

This is one of those situations where I feel like I may be making this more and more complicated.这是我觉得我可能会让事情变得越来越复杂的情况之一。 I'm very surprised pymysql doesn't have a way of simply executing a sql file directly.我很惊讶 pymysql 没有直接执行 sql 文件的方法。 I'm weary of trying to do string manipulation and get this working for this particular file, because then the dream of making this tool ambiguous and reusable kind of goes out the door.我厌倦了尝试进行字符串操作并使其适用于这个特定的文件,因为这样使这个工具模棱两可和可重用的梦想就破灭了。

Am I going about this in the complete incorrect way?我是否以完全不正确的方式解决这个问题?

Thanks!谢谢!

Here is my solution for using an SQL file with PyMySQL . 这是我使用PyMySQL的SQL文件的解决方案。 The files contain many requests ended by ; 这些文件包含许多请求结束; which is used to split requests in a list. 用于在列表中拆分请求。 So beware of the missing ; 所以要小心失踪; in the list. 在列表中。

I decided to add the missing ; 我决定补充遗失; not in the function to spar a for loop. 不在spar for for循环的函数中。 Maybe there is a better way. 也许有更好的方法。

create-db-loff.sql : create-db-loff.sql

DROP DATABASE IF EXISTS loff;
CREATE DATABASE loff CHARACTER SET 'utf8';
USE loff;

CREATE TABLE product(
    `id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `code` BIGINT UNSIGNED NOT NULL UNIQUE,
    `name` VARCHAR(200),
    `nutrition_grades` VARCHAR(1)
);

CREATE TABLE category(
    `id`INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `name` VARCHAR(200)
);

CREATE TABLE asso_prod_cat(
    `category_id` INT UNSIGNED NOT NULL,
    `product_id` INT UNSIGNED NOT NULL,
    CONSTRAINT `fk_asso_prod_cat_category`
        FOREIGN KEY(category_id)
        REFERENCES category(id)
        ON DELETE CASCADE,
    CONSTRAINT `fk_asso_prod_cat_product`
        FOREIGN KEY(product_id)
        REFERENCES product(id)
        ON DELETE CASCADE
);

db.py : db.py

DB_CONFIG = {
    'host': 'localhost',
    'user': 'loff',
    'pass': 'loff',
    'db': 'loff',
    'char': 'utf8',
    'file': 'create-db-loff.sql'
}

def get_sql_from_file(filename=DB_CONFIG['file']):
    """
    Get the SQL instruction from a file

    :return: a list of each SQL query whithout the trailing ";"
    """
    from os import path

    # File did not exists
    if path.isfile(filename) is False:
        print("File load error : {}".format(filename))
        return False

    else:
        with open(filename, "r") as sql_file:
            # Split file in list
            ret = sql_file.read().split(';')
            # drop last empty entry
            ret.pop()
            return ret

request_list = self.get_sql_from_file()

if request_list is not False:

    for idx, sql_request in enumerate(request_list):
        self.message = self.MSG['request'].format(idx, sql_request)
        cursor.execute(sql_request + ';')

DELIMITER is command used by a MySQL interpreter, such as the command line or Workbench, and not an actual MySQL command. DELIMITER是MySQL解释器使用的命令,例如命令行或Workbench,而不是实际的MySQL命令。

I ended up working in some logic in my Python application to disable execution of MySQL queries when DELIMITER has been defined, then to execute when DELIMITER has been defined again: 我最终在我的Python应用程序中使用某些逻辑来在定义DELIMITER时禁用MySQL查询的执行,然后在再次定义DELIMITER时执行:

import MySQLdb
import re

file = 'somesql.sql'
conn = MySQLdb.Connection(mysqlserver, mysqluser, mysqlpass, mysqldb)
curs = conn.cursor()
ignorestatement = False # by default each time we get a ';' that's our cue to execute.
statement = ""
for line in open(file):
    if line.startswith('DELIMITER'):
        if not ignorestatement:
            ignorestatement = True # disable executing when we get a ';'
            continue
        else:
            ignorestatement = False # re-enable execution of sql queries on ';'
            line = " ;" # Rewrite the DELIMITER command to allow the block of sql to execute
    if re.match(r'--', line):  # ignore sql comment lines
        continue
    if not re.search(r'[^-;]+;', line) or ignorestatement:  # keep appending lines that don't end in ';' or DELIMITER has been called
        statement = statement + line
    else:  # when you get a line ending in ';' then exec statement and reset for next statement providing the DELIMITER hasn't been set
        statement = statement + line
        # print "\n\n[DEBUG] Executing SQL statement:\n%s" % (statement)
        try:
            curs.execute(statement)
            conn.commit()
            statement = ""
        except curs.Error, e:
            print(file + " - Error applying (" + str(e) + ")\nTerminating.")
            sys.exit(1)

It's a bit hacky, but seems to work well enough. 这有点hacky,但似乎运作良好。

It's simple code这是简单的代码

import pymysql

class ScriptRunner:

def __init__(self, connection, delimiter=";", autocommit=True):
    self.connection = connection
    self.delimiter = delimiter
    self.autocommit = autocommit

def run_script(self, sql):
    try:
        script = ""
        for line in sql.splitlines():
            strip_line = line.strip()
            if "DELIMITER $$" in strip_line:
                self.delimiter = "$$"
                continue
            if "DELIMITER ;" in strip_line:
                self.delimiter = ";"
                continue

            if strip_line and not strip_line.startswith("//") and not strip_line.startswith("--"):
                script += line + "\n"
                if strip_line.endswith(self.delimiter):
                    if self.delimiter == "$$":
                        script = script[:-1].rstrip("$") + ";"
                    cursor = self.connection.cursor()
                    print(script)
                    cursor.execute(script)
                    script = ""

        if script.strip():
            raise Exception("Line missing end-of-line terminator (" + self.delimiter + ") => " + script)

        if not self.connection.get_autocommit():
            self.connection.commit()
    except Exception:
        if not self.connection.get_autocommit():
            self.connection.rollback()
        raise

if __name__ == '__main__':
    connection = pymysql.connect(host="127.0.0.1", user="root", password="root", db="test", autocommit=True)
    sql = ""
    ScriptRunner(connection).run_script(sql)

Most SQL files contain interpreter commands such as DELIMITER that make passing the commands through to pymysql somewhat difficult, this code snippet allows you to separate out the statements in the sql file into a list for sequential execution.大多数 SQL 文件包含解释器命令,例如 DELIMITER,这使得将命令传递给 pymysql 有点困难,这个代码片段允许您将 sql 文件中的语句分离到一个列表中以便顺序执行。

def parse_sql(filename):
    data = open(filename, 'r').readlines()
    stmts = []
    DELIMITER = ';'
    stmt = ''

    for lineno, line in enumerate(data):
        if not line.strip():
            continue

        if line.startswith('--'):
            continue

        if 'DELIMITER' in line:
            DELIMITER = line.split()[1]
            continue

        if (DELIMITER not in line):
            stmt += line.replace(DELIMITER, ';')
            continue

        if stmt:
            stmt += line
            stmts.append(stmt.strip())
            stmt = ''
        else:
            stmts.append(line.strip())
    return stmts

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM