将元组传递给Python中的MySQLdb的问题
我有一些SQL查询是通过Python的mysqldb库连接到我的MySQL数据库的,但我想让它们更不容易受到SQL注入攻击,这样小Bobby Tables就不会试图添加数据了。
比如说:
原始代码:
(这个可以运行,所以ListID等肯定是有效的)
sql="SELECT NAME FROM ListsTable WHERE ID=%s"%(ListID)
c.execute(sql)
尝试使用元组:
sql="SELECT NAME FROM ListsTable WHERE ID=%s"
c.execute(sql,(ListID,))
可以运行:
sql="SELECT NAME FROM ListsTable WHERE ID=%s"
c.execute(sql, ListID)
我不知道为什么第二次尝试用元组的方式不行,但作为单个参数却可以,不过无论如何,对于另一个语句我需要传递多个参数,所以这样做不适用:
原始代码:
sql="SELECT * FROM ListsTable ORDER BY ID DESC LIMIT %s,%s"%(Page, (Page*20)+20)
c.execute(sql)
这个可以运行,但如果我试着把参数作为元组发送,就不行了:
sql="SELECT * FROM ListsTable ORDER BY ID DESC LIMIT %s,%s"
var1=Page
var2=(Page*20)+20
params=(var1,var2)
c.execute(sql,params)
甚至只是
sql="SELECT * FROM ListsTable ORDER BY ID DESC LIMIT %s,%s"
c.execute(sql,(Page,(Page*20)+20))
我最近在我的网页服务器日志中看到这个错误,不过请注意,这可能是个误导,因为我尝试了很多不同的东西,以前没有出过错:(这个错误是指上面尝试传递“params”变量的情况)
File "process.py", line 98, in main
c.execute(sql,params)
File "/var/www/cgi-bin/MySQLdb/cursors.py", line 159, in execute
query = query % db.literal(args)
TypeError: not enough arguments for format string
编辑:如果有帮助的话,我使用的是mysqldb版本1.2.3,可能在那个版本不支持元组,但别让我开始说mysqldb文档有多糟糕……
4 个回答
我无法用MySQLdb重现这个问题。我现在用的是1.2.2版本。也许可以试试简单的调试,确保问题确实出在你说的地方:
In [13]: cur.execute('select %s + %s', (1,2))
Out[13]: 1L
In [14]: cur.fetchall()
Out[14]: ((3L,),)
更新1:我安装了1.2.3版本,这是我的记录:
In [1]: import MySQLdb
In [2]: MySQLdb.version_info
Out[2]: (1, 2, 3, 'final', 0)
In [3]: con = MySQLdb.connect(user='root', db='inventory')
In [4]: cur = con.cursor()
In [5]: cur.execute('select %s + %s', (1,2))
Out[5]: 1L
In [6]: cur.fetchall()
Out[6]: ((3L,),)
如果我能重现你的问题,也许我就能提供解决方案了!?那我们的环境之间还有什么不同呢?
$ mysql --version
mysql Ver 14.14 Distrib 5.1.41, for debian-linux-gnu (x86_64) using readline 6.1
更新2:你可以把问题拆解得更细一些。注意以下这段来自MySQLdb-1.2.3的代码:
139 def execute(self, query, args=None):
...
158 if args is not None:
159 query = query % db.literal(args)
160 try:
161 r = self._query(query)
162 except TypeError, m:
163 if m.args[0] in ("not enough arguments for format string",
164 "not all arguments converted"):
165 self.messages.append((ProgrammingError, m.args[0]))
第159行是你的参数被转换/转义并插入到查询字符串中的地方。所以你可以试试这样做:
In [24]: con.literal((1,2,))
Out[24]: ('1', '2')
看看在和你的查询字符串合并之前,驱动是如何处理这些参数的。
In [26]: "select %s + %s" % con.literal((1,2,))
Out[26]: 'select 1 + 2'
在MySQLdb 1.2.3中,查询是通过字符串替换来完成的。第一个参数是模型字符串,第二个参数可以是一个参数列表或者单个参数,这些参数是通过con.literal传递的。con.literal会把你提供的内容转换成MySQL能正确解析的字符串。它会给字符串加上引号,而数字则不会加引号。例如,如果你给它一个包含撇号的字符串,它会对撇号进行处理,加上引号。
在你的具体情况下,当你传递一个只有一个元素的元组时,MySQLdb似乎总是会出错(con是任何一个打开的连接):
>>> con.literal((10,))
('10',)
>>>
这是Python期望能够创建的一个只有一个元素的元组,但这会导致MySQL出错(向右滚动可以看到出错的情况):
mysql> select distinct sites.SiteCode, sites.SiteName, Latitude, Longitude, County, SourceID from sites, seriescatalog where sites.SiteID = seriescatalog.SiteID and sites.SiteID in (82,);
ERROR 1064 (42000): 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 ')' at line 1
mysql>
这是我用来避免这个问题的解决办法:
siteinfoquery = """select distinct sites.SiteCode, sites.SiteName, Latitude, Longitude, County, SourceID from sites, seriescatalog where sites.SiteID = seriescatalog.SiteID and sites.SiteID in (%s);"""
cur.execute(siteinfoquery % ",".join([str(int(i)) for i in SiteID]))
顺便说一下,下面的做法不奏效,因为con.literal(在MySQLdb的execute()函数内部调用)会给结果字符串加上引号,这样就把数字列表变成了一个字符串,最终变成了一个单一的值,而这个值并不匹配任何SiteID:
cur.execute(siteinfoquery , ",".join([str(int(i)) for i in SiteID]))
>>> "(%s)" % con.literal(",".join([str(float(i)) for i in SiteID]))
"('10.0,20.0')"
>>>
我还没有去查这个问题是否在后来的版本中修复了,或者这是当前的问题,还是我自己的错误,或者是他们不知道的问题。
你的mysqldb版本可能有问题。
这个地方被改成了:
1.51 - if args is not None:
1.52 - query = query % self.connection.literal(args)
1.53 try:
1.54 + if args is not None:
1.55 + query = query % tuple(map(self.connection.literal, args))
然后又改成了:
1.144 - query = query % tuple(map(self.connection.literal, args))
1.145 + query = query % tuple(( get_codec(a, self.encoders)(db, a) for a in args ))