Redrock Postgres 搜索 英文
版本: 9.3 / 9.4 / 9.5 / 9.6 / 10 / 11 / 12 / 13 / 14 / 15 / 16 / 17

44.6. 数据库访问 #

44.6.1. 数据库访问函数
44.6.2. 处理错误

PL/Python 语言模块会自动导入名为 plpy 的 Python 模块。此模块中的函数和常量以 plpy.foo 的形式在 Python 代码中对您可用。

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 列表和一个针对列的特定类型的类型修改器列表。

针对未生成结果集的命令(例如,没有 RETURNINGUPDATE,或 DROP TABLE)的结果对象调用这些方法时,这些方法会引发异常。但在包含零行的结果集上使用这些方法是可以的。

__str__()

定义标准 __str__ 方法以便例如使用 plpy.debug(rv) 调试查询执行结果。

结果对象可以进行修改。

请注意,调用 plpy.execute 将导致将整个结果集读入内存。仅当您确定结果集会相对较小时才使用该函数。如果您不希望在获取大型结果时冒过度使用内存的风险,请使用 plpy.cursor 而不是 plpy.execute

plpy.prepare(查询 [, argtypes])
plpy.execute(plan [, 参数 [, 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)

查询参数和结果行字段在 PostgreSQL 和 Python 数据类型之间转换,如 44.2 节 中所述。

当您使用 PL/Python 模块准备计划时,它会自动保存。阅读 SPI 文档(第 45 章)以了解其含义的描述。要跨函数调用有效使用此功能,需要使用一个持久存储字典 SDGD(请参见 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(查询)
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 方法。关闭后,不能再从游标中获取数据。

提示

不要将 plpy.cursor 创建的对象与 Python 数据库 API 规范 所定义的 DB-API 游标混淆。除了名称外,它们没有任何共同点。

44.6.2. 消除错误 #

访问数据库的函数可能会遇到错误,这将导致它们中止并引发异常。plpy.executeplpy.prepare 均可以引发 plpy.SPIError 的子类的实例,默认情况下,该实例会终止函数。可以通过使用 try/except 结构,像任何其他 Python 异常一样来处理此错误。例如

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;

正在引发的异常的实际类别与导致错误的特定情况相对应。请参阅 表 A.1 以获取可能的状况列表。

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 异常,并通过查看异常对象的 sqlstate 属性来确定 except 块内的具体错误情况。此属性是一个字符串值,包含 SQLSTATE 错误代码。这种方法提供了近似相同的功能。