FC55 Python编程
出自Full Circle 中文项目主页
PYTHON编程 - 第29部分 作者:Greg Walters 翻译:文宁 校对:吴云、黄傲妮
不久之前,有人请我把MySQL数据库转换成SQLite。在网上找到了一种简单快捷(并且免费)的解决方案,但发现对于我现在的MySQL版本来说没起什么用。所以我决定自己捣鼓捣鼓。
MySQL Administrator程序允许你将一个数据库备份为普通的文本文件。许多SQLite浏览器允许你读取一个普通的sql定义文件并以此创建这个数据库。然而,有很多MySQL支持的功能却不被SQLite不支持。那么这个月,我们将写一个转换程序用来读取一个MySQL的转储文件并且创建一个SQLite版本的数据库。
让我们从观察MySQL转储文件开始吧。它由一个创建数据库的部分组成,接着在数据库内由这些部分来创建每张表,如果其中包含转储文件的话,那张表中就会有数据。(MySQL有一个选项就是“只导出表模式(table schema(s) only)”)。右上方显示的就是一个创建表部分的范例。
我们要做的第一件事就是去除最后一行。右括号后面的所有东西都要去掉。(SQLite不支持InnoDB数据库)。此外,SQLite不支持“PRIMARY KEY”这一行。对于SQLite,当我们定义主键字段时,用“INTEGER PRIMARY KEY AUTOINCREMENT”来设置就好。另一个SQLite不支持的关键字是“unsigned”。
当涉及到数据的时候,“INSERT INTO”声明也不匹配。这个问题是出于SQLite不允许在同一个声明里有多个插入。这里有一个简单的MySql转储文件例子。注意(右边)行末结束标识是一个分号。
我们也将跳过所有的注释行,以及创建数据库(CREATE DATABASE)和选择数据库(USE)的声明。一旦拥有了转换后的SQL文件,我们将用一个类似公共域名程序的SQLite数据库浏览器的程序来实际处理创建数据库,表以及数据的过程。
开动吧!创建一个新的项目文件和一个新的Python文件。将它命名为MySQL2SQLite.py。
右上方展示的是import声明,类的定义,以及__init__函数。
这将是一个命令行驱动程序,因此我们将需要建立“if __name__”声明,一个命令行参数处理函数,以及一个用法函数(假设用户不知道如何使用这个程序)。这个函数将出现在程序的最后面。我们将像这样创建以上其他代码:
def error(message):
print >> sys.stderr, str(message)
下面是做打印用法声明的处理函数。
我们的设计是,如果程序从命令行单独执行时,则调用DoIt()函数。然而,如果我们希望将它作为一个可以被另一个程序在另一时间包含的库的话,那就只需用到类即可。在此我们建立了许多变量以确保每个事件都能正确执行。右下方展示的代码接着分析传递给我们的程序命令行参数,并为主函数整装就绪。
当程序运行时,我们需要为命令行提供至少两个变量。它们是输入文件和输出文件。我们也将为用户提供查看程序运行时发生的事件支持,一个只创建表而不填数据的选项,以及为用户调用帮助文档的选项。程序运行时的“正常”命令行看起来应该像这样:
MySQL2SQLite Infile=Foo Outfile=Bar
“Foo”是MySQL转储文件的名字,“Bar”是我们想要程序创建SQLite的sql文件名。
你也可以这样调用它:
MySQL2SQLite Infile=Foo Outfile=Bar Debug SchemaOnly
它将添加这个选项来显示调试信息,同时只建立表而不导入数据。
最后,如果用户寻求帮助,我们只要去编写这个程序的用法部分就好了。
在继续之前,让我们再小撇一眼命令行参数是如何支持工作的吧。
当用户从命令行(终端)输入程序名字时,操作系统保存输入的信息并把它传递给此程序,这样做只是以防输入了其他任何选项。如果没有选项(也称为参数)输入,参数的个数为1,它就是这个应用程序的名字——对于我们的程序来说就是MySQL2SQLite.py。我们可以通过调用sys.arg命令来获取这些参数。如果参数个数大于1,我们将在一个for循环内访问到它们。同时将通过参数列表对参数进行逐一检查。某些程序会要求你按一定的顺序输入参数。通过利用for循环方法,就能以任意顺序输入参数。如果用户没有提供任何参数,或者使用了帮助参数,我们就会显示用法窗口。上面显示的代码就是用法提示的函数。
继续来讲,一旦我们分析了参数集,就会实例化这个类,并调用setup函数——一个用来给变量赋值并调用DoWork的函数。现在我们就开始写类咯(见下页右下方展示的代码)。
这(下页的右上方)是__init__函数的定义。在此我们设置了整个代码中我们所需要的变量。请记住在调用DoWork函数之前,我们将调用Setup函数。在这儿我们获取空变量并将正确的值赋给他们。注意,此处的意图不在于写文件而是为了便于调试。我们也可以只写策略或数据库结构,而不写数据。如果你在使用数据库并建立一个不带任何已有数据的新项目时,它将很有用。
我们开始打开SQL转储文件,接着建立一些内部变量,也定义一些字符串来保存等会儿我们要输入的字符串。然后,如果要向一个输出文件写东西,就要打开它接着就开始执行整个进程。我们将按行读取输入文件,对其进行处理,接着会将其写入到输出文件。在此,我们用一个强制while循环来辅助读取每一行数据,当输入文件读入结束后使用break命令跳出循环。用f.readline()来获得处理的行,并把它分配给变量“line”。对于某些行,我们可以放心地忽视掉。我们将简单地利用一个带有pass语句的if/elif来完成这个动作(如下图所示)。
接着我们就可以不再忽略它们而且要有所作为啦。如果有一个CreateTable声明,我们就将启动那个进程。请记住我们定义了CT来代表“Create Table”。这里(右上方)我们设置了一个等于1的“CreateTableMode”变量,以便我们知道自己正在做什么,因为每一个定义域都是独立成行的。然后读取一行,处理掉回车,并做好输出文件的准备,在需要的时候就输出它。
现在(中间靠右)我们需要开始处理创建表声明里的每行内容——使每一行都符合SQLite的格式。SQLite有很多事情都不会自己处理。让我们再看看MySQL的创建表声明吧。
一个SQLite绝对会出现的问题是在右括号后面最后的一整行。另一个在这之上的问题出现在主键那行。还有另一个就是第二行的无符号关键字。处理这些问题需要写一些代码(下图所示),但是别担心我们可以办到。
首先,(右边从上往下第三行)检查这一行是否包含“auto increment”。我们假设这是主键那行。虽然在98.6%的时间里是正确的,但也有出问题的时候。不过我们还是做的简单点儿吧。然后检查这行是否以“) ”开始。这标识着创建表区域的末行。如果是这样,我们在变量“newline”里就只设置一个字符串来正确地关闭声明——关闭CreateTableMode变量,同时,如果要写入到文件,调用写入操作就可以了。
现在(右下方示)我们用到在自增关键字发现的信息了。首先,去掉此行中所有多余的空格,接着检查其中“ int(“词组出现的位置(假设它存在)。我们将用“ INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL”词组来替换它。对于SQLite来说整型的长度是无关紧要的。同样,如果需要就写到输出文件中。
现在在这行寻找“PRIMARY KEY ”词组。请注意最后附加的空格——那是故意的。如果它出现了,我们就忽略这行。
elif line.strip().startswith(PK):
pass
现在(右上方所示)我们查找并替换“ unsigned ”(同样保留附加空格)为“ ”。
以上是创建表函数的最后部分。现在(如下所示)我们继续看插入数据声明。InsertStart变量是“INSERT INTO”词组。检查它是因为MySQL允许在单一命令行输入多个插入声明,但SQLite却不允许。我们需要为每个数据块创建单独的声明。在这儿设置一个叫“insertmode”的变量并赋值为1,将INSERT INTO {Table} {Fieldlist} VALUES (”赋入到一个可重用的变量中(我们称它为我们的序部),然后我们继续。
现在,查看我们是否只需要处理模式。如果是,就可以放心地忽略任何插入声明。如果不是,则需要处理它们。
elif self.SchemaOnly == 0:
if insertmode == 1:
查看那行是否有“');”或“'),”。如果有“');”,这将是输入声明集的最后一行。
posx = line.find("');")
pos1 = line.find("'),")
l1 = line[:pos1]
此行查找了单引号并替换它们。
line = line.replace("\\'","")
如果遇到结束声明(“);”),就说明我们到了插入集的尾部,同时我们可以通过在前面实际的值声明里加入这个来创建声明。如前一页右下角所示。
如果插入声明的最后一个值是个带引号的字符串,则这些(右上方示代码)都会被执行。然而,如果最后一个值是个数值,我们就得采用不同的方式来处理了。你终会理解在这儿我们都干了些什么的。
最后,我们关闭输入文件,并且,如果我们要写一个输出文件,也要关闭它。
f.close()
if self.WriteFile == 1:
OutFile.close()
一旦你获得了转换后的文件,就可以用SQLite数据库浏览器去填写数据库结构和数据了。
这些代码应该在90%以上的时间里都有效。当然也可能由于其他的问题而无法运行,这就是调试模式存在的原因。不过我已经在多个文件上进行了测试并确认没有问题了。
一如既往,代码已经贴在PasteBin上,网址是:http://pastebin.com/cPvzNT7T。
下期见。