PostgreSQL 中文参考文档
🌐 英文原版 官方 ↗

44.6. 数据库访问 #

44.6.1. 数据库访问函数
44.6.2. 捕获错误

PL/Python 语言模块会自动导入一个名为 plpy 的 Python 模块。这个模块中的函数和常量在 Python 代码中可以作为 plpy.foo 使用。

44.6.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 的列表以及各列的类型修饰符 列表。

对不产生结果集的命令(例如不带 RETURNING 的 UPDATE,或 DROP 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.2 中所述在 PostgreSQL 与 Python 数据类型之间转换。

用 PL/Python 模块准备计划时,它会被自动保存。 这意味着什么请阅读 SPI 文档(Chapter 45)。 要在函数调用之间有效地利用这一点,需要使用持久存储字典 SD 或 GD(见 Section 44.3)。例如:

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 plpython3u;

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 plpython3u;

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 plpython3u;

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 plpython3u;

游标会被自动释放。但如果想显式释放游标持有的所有资源,请使用 close 方法。一旦关闭,游标就不能再被 fetch 了。

Tip

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

44.6.2. 捕获错误 #

访问数据库的函数可能会遇到错误,这会使它们中止并抛出异常。 plpy.execute 和 plpy.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 plpython3u;

被抛出异常的实际类对应于导致错误的特定条件。 可能的条件列表参见 Table A.1。 模块 plpy.spiexceptions 为每个 PostgreSQL 条件定义了一个异常类,其名字 由条件名派生而来。例如, division_by_zero 变为 DivisionByZero, unique_violation 变为 UniqueViolation, fdw_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 plpython3u;

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

作为处理不同错误条件的另一种方式,您可以捕获 SPIError 异常,并在 except 块内通过查看异常对象的 sqlstate 属性来确定具体的错误条件。 该属性是包含 “SQLSTATE” 错误代码的字符串值。 这种方式提供大致相同的功能。