43.7. 数据库访问

PL/Python语言模块自动输入一个叫plpy的Python模块。 在这个模块里的函数和常量可以在Python代码里以 plpy.foo的名字获得。

43.7.1. 数据库访问函数

plpy模块提供几个函数执行数据库命令:

plpy.execute(query [, max-rows])

拿一个查询字符串和一个可选的行限制参数调用plpy.execute可以运行该查询, 并且结果返回到一个结果对象里。

结果对象仿真一个列表或者是字典对象。结果对象通过行号和字段名来访问。例如:

rv = plpy.execute("SELECT * FROM my_table", 5)

返回来自my_table的最多5行。如果my_table 有一个my_column字段,那么你可以用下面的方法访问它:

foo = rv[i]["my_column"]

返回的行数可以使用内建len函数获得。

结果对象有下面这些额外的方法:

nrows()

返回该命令处理的行数。请注意,这与返回的行数不一定相同。例如,UPDATE 命令将设置这个值,但是不会返回任何行(除非使用了RETURNING)。

status()

SPI_execute()的返回值。

colnames()
coltypes()
coltypmods()

分别返回一个字段名的列表、字段类型OID的列表和该字段的特定于类型的类型修饰符的列表。

这些方式在从一个不产生结果集命令的结果对象上调用时产生一个意外,比如, 不带有RETURNINGUPDATE,或DROP TABLE。 但是在一个包含零行的结果集上使用这些方法是可以的。

__str__()

定义了标准的__str__方法,所以有可能,比如, 使用plpy.debug(rv)调试查询执行结果。

结果对象可以被修改。

请注意,调用plpy.execute将导致读取整个结果集到内存中。 只有在你确定结果集将相对较小的情况下使用这个函数。 如果你不希望在抓取大的结果集时承担过度使用内存的风险, 那么使用plpy.cursor而不是plpy.execute

plpy.prepare(query [, argtypes])
plpy.execute(plan [, arguments [, max-rows]])

plpy.prepare为查询准备执行规划。 它用一个查询字符串和一个参数类型列表调用,如果在查询中你有参数引用。比如:

plan = plpy.prepare("SELECT last_name FROM my_users WHERE first_name = $1", ["text"])

text是将作为$1传递的变量类型。 如果你不想传递任何参数到查询,那么第二个参数是可选的。

在准备一个语句之后,你用函数plpy.execute的一个变量运行它:

rv = plpy.execute(plan, ["name"], 5)

传递的规划作为第一个参数(而不是查询字符串),一个值的列表代入查询中作为第二个参数。 如果查询不希望有任何参数则第二个参数是可选的。第三个参数和以前一样,是可选的,行数限制。

查询参数和结果行字段在PostgreSQL和第 43.3 节描述的Python数据类型之间进行转换。 例外是目前不支持复合类型:当在查询结果中出现时,将拒绝他们作为查询参数,并且转换成字符串。 对于后者问题的解决方法,查询有时可以被改写,这样复合类型的结果作为一个结果行出现而不是一个结果行字段。 可选的,结果字符串可以手动的单独解析,但是不建议使用这个方法,因为它会过时。

在你用PL/Python模块准备一个计划的时候,该计划是自动保存的。请阅读 SPI 文档(第 44 章) 获取这句话的含义。为了能有效的使用这个跨函数调用,需要使用永久存储字典 SDGD之一(参阅第 43.4 节)。比如:

CREATE FUNCTION usesavedplan() RETURNS trigger AS $$
    plan = SD.setdefault("plan", plpy.prepare("SELECT 1"))
    # rest of function
$$ LANGUAGE plpythonu;

plpy.cursor(query)
plpy.cursor(plan [, arguments])

plpy.cursor函数接受和plpy.execute一样的参数 (除了行限制),并返回一个游标对象,它允许你在一个小的语块中处理大的结果集。 和plpy.execute一样,可以使用一个查询字符串或者一个带有参数列表的规划对象。

该游标对象提供一个fetch方法,接受一个整型参数并返回一个结果对象。 每次你调用fetch,返回的对象将包含下一批行,从不大于参数值。 一旦所有行都获得了,fetch开始返回一个空的结果对象。 游标对象也提供一个迭代器接口, 一次生成一行直到所有行都获得。这种方式获取的数据不作为结果对象返回,而是作为字典, 每个字典对应于一个结果行。

两种方式处理大表中数据的例子是:

CREATE FUNCTION count_odd_iterator() RETURNS integer AS $$
odd = 0
for row in plpy.cursor("select num from largetable"):
    if row['num'] % 2:
         odd += 1
return odd
$$ LANGUAGE plpythonu;

CREATE FUNCTION count_odd_fetch(batch_size integer) RETURNS integer AS $$
odd = 0
cursor = plpy.cursor("select num from largetable")
while True:
    rows = cursor.fetch(batch_size)
    if not rows:
        break
    for row in rows:
        if row['num'] % 2:
            odd += 1
return odd
$$ LANGUAGE plpythonu;

CREATE FUNCTION count_odd_prepared() RETURNS integer AS $$
odd = 0
plan = plpy.prepare("select num from largetable where num % $1 <> 0", ["integer"])
rows = list(plpy.cursor(plan, [2]))

return len(rows)
$$ LANGUAGE plpythonu;

游标会自动处理。但是如果你希望明确的释放游标持有的所有资源,使用close方法。 一旦关闭了,游标不再能够获取。

提示: 不要混淆plpy.cursor创建的对象和 Python数据库API说明定义的DB-API游标。 它们除了名字相同外没有什么相同的。

43.7.2. 捕获错误

访问数据库的函数可能会遇到错误,导致它们退出并引发异常。plpy.executeplpy.prepare都会一起一个plpy.SPIError 的子类的实例,缺省将终止函数。这个错误可以像任何其他Python异常那样处理, 通过使用try/except构造。例如:

CREATE FUNCTION try_adding_joe() RETURNS text AS $$
    try:
        plpy.execute("INSERT INTO users(username) VALUES ('joe')")
    except plpy.SPIError:
        return "something went wrong"
    else:
        return "Joe added"
$$ LANGUAGE plpythonu;

引发的异常的实际类对应于导致该错误的特定条件。查阅表 A-1获取可能的条件的列表。 模块plpy.spiexceptions为每个PostgreSQL 条件定义一个异常类,从条件名中派生它们的名字。例如,division_by_zero 成为DivisionByZerounique_violation成为 UniqueViolationfdw_error成为FdwError等等。 每个异常类都从SPIError中继承。这个分离使得它更容易处理特定的错误,例如:

CREATE FUNCTION insert_fraction(numerator int, denominator int) RETURNS text AS $$
from plpy import spiexceptions
try:
    plan = plpy.prepare("INSERT INTO fractions (frac) VALUES ($1 / $2)", ["int", "int"])
    plpy.execute(plan, [numerator, denominator])
except spiexceptions.DivisionByZero:
    return "denominator cannot equal zero"
except spiexceptions.UniqueViolation:
    return "already have that fraction"
except plpy.SPIError, e:
    return "other error, SQLSTATE %s" % e.sqlstate
else:
    return "fraction inserted"
$$ LANGUAGE plpythonu;

请注意,因为所有来自plpy.spiexceptions模块的异常都从 SPIError继承,所以except子句处理它将捕获任何数据库访问错误。

作为处理不同错误条件的一个替换方式,你可以捕获SPIError异常。 然后在except块中通过查看异常对象的sqlstate 属性确定特定的错误条件。这个属性是一个包含"SQLSTATE"错误代码的字符串值。 这个方法提供了差不多相同的功能。