受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: devel

44.7. 数据库访问 #

PL/Python 语言模块会自动导入一个名为plpy的 Python 模块。该模块中的函数和常量在 Python 代码中可以通过plpy.foo这样的形式访问。

44.7.1. 数据库访问函数 #

plpy模块提供若干函数来执行数据库命令:

plpy.execute(query [, limit])

使用查询字符串和可选的行数限制参数调用plpy.execute会执行该查询,并把结果作为结果对象返回。

如果指定了limit且其大于零,则plpy.execute最多检索limit行,就像查询中包含了LIMIT子句一样。省略limit或将其指定为零,则表示不限制行数。

结果对象的行为类似于列表或字典对象。可以通过行号和列名来访问结果对象。例如:

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 列表以及列的类型相关修饰符列表。

如果在来自不产生结果集的命令的结果对象上调用这些方法,就会引发异常,例如不带RETURNINGUPDATEDROP TABLE。但在包含零行的结果集上使用这些方法是没有问题的。

__str__()

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

结果对象是可修改的。

注意,调用plpy.execute会把整个结果集读入内存。只有在确信结果集相对较小时才应使用这个函数。获取大型结果时,如果不想承担过高的内存占用风险,应使用plpy.cursor而不是plpy.execute

plpy.prepare(query [, argtypes])
plpy.execute(plan [, arguments [, limit]])

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)

将计划对象作为第一个参数传递(而不是查询字符串),并将要代入查询的值列表作为第二个参数传递。如果查询不需要任何参数,第二个参数可以省略。和前面一样,第三个参数仍然是可选的行数限制。

另外,也可以在计划对象上调用execute方法:

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

查询参数和结果行字段会按照Section 44.3中所述,在 PostgreSQL 与 Python 数据类型之间进行转换。

当你使用 PL/Python 模块准备一个计划时,它会被自动保存。关于这意味着什么,请参阅 SPI 文档(Chapter 45)。为了在函数调用之间有效利用这一点,需要使用持久存储字典SDGD之一(见Section 44.4)。例如:

CREATE FUNCTION usesavedplan() RETURNS trigger AS $$
    if "plan" in SD:
        plan = SD["plan"]
    else:
        plan = plpy.prepare("SELECT 1")
        SD["plan"] = plan
    # rest of function
$$ LANGUAGE plpythonu;
plpy.cursor(query)
plpy.cursor(plan [, arguments])

plpy.cursor函数接受与plpy.execute相同的参数(只是没有行数限制),并返回一个游标对象,使你可以分块处理大型结果集。与plpy.execute一样,可以使用查询字符串,也可以使用带参数列表的计划对象;还可以把cursor函数作为计划对象的方法来调用。

游标对象提供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]))  # or: = list(plan.cursor([2]))

return len(rows)
$$ LANGUAGE plpythonu;

游标会被自动释放。但如果想显式释放游标持有的全部资源,可使用close方法。一旦关闭,就不能再从该游标中取数。

Tip

不要把plpy.cursor创建的对象与Python 数据库 API 规范定义的 DB-API 游标混淆。除了名字之外,它们毫无共同之处。

44.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;

实际抛出的异常类对应于引起错误的具体条件。可能的条件列表见Table 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 as e:
    return "other error, SQLSTATE %s" % e.sqlstate
else:
    return "fraction inserted"
$$ LANGUAGE plpythonu;

请注意,由于plpy.spiexceptions模块中的所有异常都继承自SPIError,处理它的except子句会捕获任何数据库访问错误。

作为处理不同错误情况的另一种方法,你可以捕获SPIError异常,并在except块中通过查看异常对象的sqlstate属性来判断具体的错误条件。该属性是一个包含SQLSTATE错误代码的字符串值。这种方法大体上提供了相同的功能。

提交更正

如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。