django + south + python: 使用作为函数参数的文本字符串时出现奇怪行为
这是我第一次提问。
我正在尝试在 Django 中执行一个 SQL 查询(使用 south 迁移):
from django.db import connection
# ...
class Migration(SchemaMigration):
# ...
def transform_id_to_pk(self, table):
try:
db.delete_primary_key(table)
except:
pass
finally:
cursor = connection.cursor()
# This does not work
cursor.execute('SELECT MAX("id") FROM "%s"', [table])
# I don't know if this works.
try:
minvalue = cursor.fetchone()[0]
except:
minvalue = 1
seq_name = table + '_id_seq'
db.execute('CREATE SEQUENCE "%s" START WITH %s OWNED BY "%s"."id"', [seq_name, minvalue, table])
db.execute('ALTER TABLE "%s" ALTER COLUMN id SET DEFAULT nextval("%s")', [table, seq_name + '::regclass'])
db.create_primary_key(table, ['id'])
# ...
我这样使用这个函数:
self.transform_id_to_pk('my_table_name')
所以它应该:
- 找到最大的现有 ID,或者是 0(它崩溃了)
- 创建一个序列名称
- 创建这个序列
- 更新 ID 字段以使用这个序列
- 把 ID 更新为主键
但是它崩溃了,错误信息是:
File "../apps/accounting/migrations/0003_setup_tables.py", line 45, in forwards
self.delegation_table_setup(orm)
File "../apps/accounting/migrations/0003_setup_tables.py", line 478, in delegation_table_setup
self.transform_id_to_pk('accounting_delegation')
File "../apps/accounting/migrations/0003_setup_tables.py", line 20, in transform_id_to_pk
cursor.execute(u'SELECT MAX("id") FROM "%s"', [table.encode('utf-8')])
File "/Library/Python/2.6/site-packages/django/db/backends/util.py", line 19, in execute
return self.cursor.execute(sql, params)
psycopg2.ProgrammingError: relation "E'accounting_delegation'" does not exist
LINE 1: SELECT MAX("id") FROM "E'accounting_delegation'"
^
我为了方便缩短了文件路径。
那句 "E'accounting_delegation'" 是什么意思?我该怎么解决这个问题?
谢谢!
卡洛斯。
1 个回答
4
问题在于,你在处理一些不是SQL数据的内容时,使用了DB-API的参数化方式。当你做类似这样的事情:
cursor.execute('INSERT INTO table_foo VALUES (%s, %s)', (col1, col2))
DB-API模块(在这里是django为你使用的数据库提供的接口)会知道如何正确处理'col1'和'col2'的内容,并把它们替换到%s的位置。注意,这里的%s周围没有引号。但这只适用于SQL的数据,而不适用于SQL的元数据,比如表名和序列名,因为它们需要用不同的方式加引号(或者根本不需要)。当你这样做:
cursor.execute('INSERT INTO "%s" VALUES (%s, %s)', (tablename, col1, col2))
表名会被加上引号,就像你想把它当作字符串数据插入一样,结果你会得到,比如说,"'table_foo'"。你需要把SQL的元数据(这是查询的一部分)和SQL的数据(这不是查询的一部分)分开,像这样:
sql = 'INSERT INTO TABLE "%s" VALUES (%%s, %%s)' % (tablename,)
cursor.execute(sql, (col1, col2))
注意,因为django的DB-API前端的参数样式是'pyformat'(它使用%s作为占位符),所以在进行字符串格式化以创建你想执行的SQL时,你需要对这些进行转义。另外要注意,如果你从不安全的来源获取表名而没有进行验证,这样的做法是无法防止SQL注入攻击的。