PostgreSQL下的Python

PostgreSQL下的Python

PL/Python过程语言允许PostgreSQL函数用Python语言编写。

要在特定数据库中安装PL/Python,请使用CREATE EXTENSION plpythonu

提示:如果语言安装到template1中,则随后创建的所有数据库都将自动安装该语言。

PL/Python仅作为”不可信”语言提供,这意味着它不提供限制用户可以在其中执行的任何方式,因此命名为plpythonu。如果在Python中开发安全执行机制,将来可能会有可信变量plpython。在不可信的PL / Python中函数的作者必须注意,该函数不能用于做任何不想要的事情,因为它可以完成登录为数据库管理员的用户所能做的任何事情。 只有超级用户可以使用不受信任的语言(如plpythonu)创建函数。

Python 2 和 Python 3

PL/Python支持Python 2和Python 3语言变量。由于Python 2和Python 3语言变量在某些重要方面不兼容,因此PL / Python使用以下命名和转换方案来避免混淆它们:

  • 命名为plpython2u的PostgreSQL语言实现了基于Python 2语言版本的PL/Python。
  • 命名为plpython3u的PostgreSQL语言实现了基于Python 3语言版本的PL/Python。
  • 命名为plpythonu的语言基于默认的Python语言变体(目前是Python 2)实现了PL/Python。(这个默认值与任何本地Python安装可能认为是它们的”默认”无关,例如,可能是usr/bin/python。)根据在Python社区中迁移到Python 3的进度,在PostgreSQL的一个遥远的未来版本中,默认情况下可能会更改为Python 3。

这个方案类似于PEP 394关于python命令的命名和转换的建议。

它取决于构建配置或安装的包是否可用于Python 2或Python 3的PL/Python。

这会导致以下使用和迁移策略:

  • 目前还存在很多对Python 3不感兴趣的用使用语言名称plpythonu,并且在可预见的将来不必改变任何内容。建议通过迁移到Python 2.6/2.7逐步”面向未来”的代码,以简化最终迁移到Python 3。

    在实践中,许多PL/Python函数将迁移到Python 3而几乎不做任何更改。

  • 知道他们拥有严重Python 2相关代码并且不打算改变它的用户可以使用plpython2u语言名称。这将继续运行到很遥远的将来,直到Python 2的支持可能会被PostgreSQL完全抛弃。
  • 想要深入Python 3的用户可以使用plpython3u语言名称,该名称将按照今天的标准继续工作。在遥远的将来,当Python 3可能成为默认版本时,他们可能会因美学原因而想要删除”3”。
  • Daredevils希望构建一个仅限于Python 3的操作系统环境,它可以更改pg_pltemplate的内容,使plpythonu等同于plpython3u,同时要记住,这会使得它们的安装与世界上大多数其他地方不兼容。

有关移植到Python 3的更多信息,另请参阅Python 3.0中的新增功能

在同一个会话中,不允许使用基于Python 2的PL/Python和基于Python 3的PL/Python,因为动态模块中的符号会发生冲突,这可能会导致PostgreSQL服务器进程崩溃。有一个检查可以防止在会话中混合Python主要版本,如果检测到不匹配,这将会中止会话。但是,可以在单独的会话中使用同一数据库中的PL/Python变量。

PL/Python函数

PL/Python中的函数通过标准的CREATE FUNCTION语法声明:

1
2
3
4
5
CREATE FUNCTION funcname (argument-list)
RETURNS return-type
AS $$
# PL/Python function body
$$ LANGUAGE plpythonu;

函数的主体只是一个Python脚本。当函数被调用时,它的参数作为列表参数的元素传递; 命名参数也作为普通变量传递给Python脚本。命名参数的使用通常更具可读性。结果以通常的方式从Python代码返回,返回或yield(在结果集语句的情况下)。如果你没有提供返回值,Python将返回默认的None。PL/Python将Python的None转换为SQL空值。

例如,返回两个整数中较大者的函数可以定义为:

1
2
3
4
5
6
7
CREATE FUNCTION pymax (a integer, b integer)
RETURNS integer
AS $$
if a > b:
return a
return b
$$ LANGUAGE plpythonu;

作为函数定义主体给出的Python代码被转换为Python函数。例如,上面的结果是:

1
2
3
4
def __plpython_procedure_pymax_23456():
if a > b:
return a
return b

假设23456是由PostgreSQL分配给函数的OID。

参数被设置为全局变量。由于Python的范围规则,这具有微妙的后果,即变量不能在函数内重新分配给涉及变量名本身的表达式的值,除非该变量在块中被重新声明为全局变量。例如,以下内容不起作用:

1
2
3
4
5
6
CREATE FUNCTION pystrip(x text)
RETURNS text
AS $$
x = x.strip() # error
return x
$$ LANGUAGE plpythonu;

因为赋值给x使x成为整个块的局部变量,所以赋值右侧的x引用尚未赋值的局部变量x,而不是PL/Python函数参数。使用全局声明,可以使其工作:

1
2
3
4
5
6
7
CREATE FUNCTION pystrip(x text)
RETURNS text
AS $$
global x
x = x.strip() # ok now
return x
$$ LANGUAGE plpythonu;

但最好不要依赖PL/Python的这个实现细节。最好将函数参数视为只读。

数据值

一般来说,PL/Python的目标是提供PostgreSQL和Python世界之间的”自然”映射。 这通知了下面描述的数据映射规则。

数据类型映射

当调用PL/Python函数时,其参数将从其PostgreSQL数据类型转换为相应的Python类型:

  • PostgreSQL boolean 转换为Python bool。
  • PostgreSQL smallint 和 int 转换为Python int. PostgreSQL bigint 和 oid 转换为 Python 2的long 和Python 3的int。
  • PostgreSQL numeric转换为Python Decimal。如果可用,则从cdecimal包中导入此类型。否则,将使用标准库中的decimal.Decimal。cdecimal显着快于decimal。然而,在Python 3.3及更高版本中,cdecimal已经被集成到decimal标准库中,所以不再有任何区别。
  • PostgreSQL bytea被转换为Python 2中的Python str和Python 3中的bytes。在Python 2中,该字符串应该被视为没有任何字符编码的字节序列。
  • 所有其他数据类型(包括PostgreSQL字符串类型)都被转换为Python str。在Python 2中,这个字符串将在PostgreSQL服务器编码中;在Python 3中,它将是一个像所有字符串一样的Unicode字符串。
  • 对于非标量数据类型,请参见下文。

当PL/Python函数返回时,它的返回值被转换为函数声明的PostgreSQL返回数据类型,如下所示:

  • 当PostgreSQL返回类型是boolean时,根据Python规则将返回值的真值。也就是说,0和空字符串都是假的,但值得注意的是’f’是真的。
  • 当PostgreSQL返回类型是bytea时,返回值将被转换为一个字符串(Python 2)或bytes(Python 3),使用各自的Python内置函数,并将结果转换为bytea。
  • 对于所有其他PostgreSQL返回类型,使用Python内置str将返回值转换为字符串,并将结果传递给PostgreSQL数据类型的输入函数。(如果Python值是float,则使用内置的repr而不是str来转换,以避免精度损失。)

    Python 2中的字符串在传递给PostgreSQL时需要使用PostgreSQL服务器编码。在当前服务器编码中无效的字符串会引发错误,但并不是所有的编码不匹配都可以被检测到,所以如果没有正确完成,垃圾数据仍然会产生。 Unicode字符串会自动转换为正确的编码,因此可以更安全,更方便地使用这些字符串。在Python 3中,所有字符串都是Unicode字符串。

  • 对于非标量数据类型,请参见下文。

NULL,None

如果将一个SQL空值传递给一个函数,那么参数值将在Python中显示为None。例如,第45.2节所示的pymax函数定义将返回null输入的错误答案。我们可以对函数定义添加STRUCT,以使PostgreSQL做一些更合理的事情:如果null值被传递,函数将不会被调用,而是会自动返回一个null结果。或者,我们可以检查函数体中的空输入:

1
2
3
4
5
6
7
8
9
CREATE FUNCTION pymax (a integer, b integer)
RETURNS integer
AS $$
if (a is None) or (b is None):
return None
if a > b:
return a
return b
$$ LANGUAGE plpythonu;

如上所示,要从PL/Python函数返回一个SQL空值,返回None值。无论函数是否严格,都可以这样做。

数组和列表

SQL数组值作为Python列表传递到PL/Python中。要从PL/Python函数返回SQL数组值,返回Python列表:

1
2
3
4
5
6
7
8
9
10
11
CREATE FUNCTION return_arr()
RETURNS int[]
AS $$
return [1, 2, 3, 4, 5]
$$ LANGUAGE plpythonu;

SELECT return_arr();
return_arr
-------------
{1,2,3,4,5}
(1 row)

多维数组作为嵌套的Python列表传递到PL/Python中。例如,二维数组是列表的列表。当从PL/Python函数返回多维SQL数组时,每个级别的内部列表必须都是相同的大小。例如:

1
2
3
4
5
6
7
8
9
10
11
CREATE FUNCTION test_type_conversion_array_int4(x int4[]) RETURNS int4[] AS $$
plpy.info(x, type(x))
return x
$$ LANGUAGE plpythonu;

SELECT * FROM test_type_conversion_array_int4(ARRAY[[1,2,3],[4,5,6]]);
INFO: ([[1, 2, 3], [4, 5, 6]], <type 'list'>)
test_type_conversion_array_int4
---------------------------------
{{1,2,3},{4,5,6}}
(1 row)

其他Python序列,如元组,也被接受为向后兼容PostgreSQL版本9.6和以下,当多维数组不受支持时。但是,它们总是被当作一维数组来处理,因为它们与复合类型是不明确的。出于同样的原因,当一个复合类型在多维数组中使用时,它必须由一个元组来表示,而不是一个列表。

请注意,在Python中,字符串是序列,它可能对Python程序员很熟悉:

1
2
3
4
5
6
7
8
9
10
11
CREATE FUNCTION return_str_arr()
RETURNS varchar[]
AS $$
return "hello"
$$ LANGUAGE plpythonu;

SELECT return_str_arr();
return_str_arr
----------------
{h,e,l,l,o}
(1 row)

复合类型

复合类型的参数被传递给函数作为Python映射。映射的元素名称是复合类型的属性名称。如果传递行的属性具有空值,那么它在映射中没有任何值。这是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CREATE TABLE employee (
name text,
salary integer,
age integer
);

CREATE FUNCTION overpaid (e employee)
RETURNS boolean
AS $$
if e["salary"] > 200000:
return True
if (e["age"] < 30) and (e["salary"] > 100000):
return True
return False
$$ LANGUAGE plpythonu;

从Python函数中返回行或复合类型有多种方法。下面的例子假设我们有:

1
2
3
4
CREATE TYPE named_value AS (
name text,
value integer
);

复合结果可以返回为:

序列类型(一个元组或列表,但不是一个集合,因为它不是可索引的)

返回的序列对象必须具有相同数量的项,因为复合结果类型具有字段。索引0的项被分配给复合类型的第一个字段,1的第二个字段,等等。例如:

1
2
3
4
5
6
CREATE FUNCTION make_pair (name text, value integer)
RETURNS named_value
AS $$
return ( name, value )
# or alternatively, as tuple: return [ name, value ]
$$ LANGUAGE plpythonu;

若要返回任何列的SQL null,请在相应位置插入None

当返回一个复合类型数组时,它不能作为一个列表返回,因为它是不明确的,是否Python列表表示一个复合类型,或者另一个数组维度。

映射(字典)
从映射中检索每个结果类型列的值,并使用列名称作为键。例子:

1
2
3
4
5
CREATE FUNCTION make_pair (name text, value integer)
RETURNS named_value
AS $$
return { "name": name, "value": value }
$$ LANGUAGE plpythonu;

任何额外的字典键/值对都被忽略。丢失的键被视为错误。若要返回任何列的SQL空值,请插入None用相应的列名作为键。

对象(任何提供方法getattr的对象)
这和映射是一样的。例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CREATE FUNCTION make_pair (name text, value integer)
RETURNS named_value
AS $$
class named_value:
def __init__ (self, n, v):
self.name = n
self.value = v
return named_value(name, value)

# or simply
class nv: pass
nv.name = name
nv.value = value
return nv
$$ LANGUAGE plpythonu;

还支持带有OUT参数的函数。例如:

1
2
3
4
5
CREATE FUNCTION multiout_simple(OUT i integer, OUT j integer) AS $$
return (1, 2)
$$ LANGUAGE plpythonu;

SELECT * FROM multiout_simple();

设置返回函数

PL/Python函数还可以返回标量或复合类型的集合。有几种方法可以实现这一点,因为返回的对象内部变成了迭代器。下面的例子假设我们有复合类型:

1
2
3
4
CREATE TYPE greeting AS (
how text,
who text
);

可以从A返回一个集合结果:

序列类型(元组、列表、集合)

1
2
3
4
5
6
7
CREATE FUNCTION greet (how text)
RETURNS SETOF greeting
AS $$
# return tuple containing lists as composite types
# all other combinations work also
return ( [ how, "World" ], [ how, "PostgreSQL" ], [ how, "PL/Python" ] )
$$ LANGUAGE plpythonu;

迭代器(任何提供iter和next方法的对象)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
CREATE FUNCTION greet (how text)
RETURNS SETOF greeting
AS $$
class producer:
def __init__ (self, how, who):
self.how = how
self.who = who
self.ndx = -1

def __iter__ (self):
return self

def next (self):
self.ndx += 1
if self.ndx == len(self.who):
raise StopIteration
return ( self.how, self.who[self.ndx] )

return producer(how, [ "World", "PostgreSQL", "PL/Python" ])
$$ LANGUAGE plpythonu;

Generator (yield)

1
2
3
4
5
6
CREATE FUNCTION greet (how text)
RETURNS SETOF greeting
AS $$
for who in [ "World", "PostgreSQL", "PL/Python" ]:
yield ( how, who )
$$ LANGUAGE plpythonu;

还支持带OUT参数的集返回函数(使用返回的记录集)。例如:

1
2
3
4
5
CREATE FUNCTION multiout_simple_setof(n integer, OUT integer, OUT integer) RETURNS SETOF record AS $$
return [(1, 2)] * n
$$ LANGUAGE plpythonu;

SELECT * FROM multiout_simple_setof(3);

分享数据

全局字典SD可以在函数调用之间存储数据。该变量是私有静态数据。全局字典GD是公共数据,可以在会话中的所有Python函数中使用。小心使用。

每个函数在Python解释器中都有自己的执行环境,因此myfunc中的全局数据和函数参数不能用于myfunc2。例外是GD字典中的数据,如上所述。

匿名代码块

PL/Python还支持使用DO语句调用的匿名代码块:

1
2
3
DO $$
# PL/Python code
$$ LANGUAGE plpythonu;

一个匿名代码块不会接收任何参数,它返回的任何值都会被丢弃。否则它就像一个函数。

触发函数

当一个函数被用作触发器时,字典TD包含了触发相关的值:

TD(“event”)

将事件作为字符串包含:插入、更新、删除或截断。

TD(“when”)

包含一个之前,之后,或者不是。

TD(“level”)

包含行或声明。

TD(“new”)

TD(“old”)

对于行级触发器,根据触发器事件,其中一个或两个字段包含相应的触发器行。

TD(“name”)

包含触发器的名称。

TD(“table_name”)

包含触发器所发生的表的名称。

TD(“table_schema”)

包含触发器所发生的表的模式。

TD(“relid”)
包含触发器所发生的表的OID。

TD(“args”)
如果CREATE TRIGGER命令包含了参数,那么它们可以在TD[“args”][0]到TD[“args”][n-1]中得到。

如果TD[“when”]是在之前或者不是,而TD[“level”]是行,那么您可以从Python函数中返回None或”OK”,表示行未修改,”跳过”取消事件,或者如果TD[“event”]是插入或更新,您可以返回”MODIFY”来表示您已经修改了新行。否则将忽略返回值。

数据库访问

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

数据库访问函数

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

  1. plpy.execute(query [, max-rows])
    调用`plpy.execute使用一个查询字符串和一个可选的行限制参数执行查询,结果将返回到结果对象中。

结果对象模拟一个列表或dictionary对象。结果对象可以通过行号和列名访问。例如:

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

从my_table返回最多5行。如果my_table有一个列my_column,它将被访问如下:

1
foo = rv[i]["my_column"]

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

结果对象还有这些额外的方法:
nrows()
返回由命令处理的行数。请注意,这并不一定与返回的行数相同。例如,UPDATE命令将设置此值,但不会返回任何行(除非使用返回)。

status()
SPI_execute()返回值。

colnames()
coltypes()
coltypmods()
返回列表的列名称,列出列类型oid,以及列出类型特定和修饰符的列表。

这些方法在从没有生成结果集的命令中调用result对象时引发异常,例如,不返回或删除表的更新。但是在包含0行的结果集上使用这些方法是可以的。

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

结果对象可以被修改。

注意,调用plpy.execute将导致将整个结果集读入内存。只有在确定结果集相对较小的情况下才使用该函数。如果您不想在获取大的结果时冒过多的内存使用风险,请使用plpy.cursor而不是plpy.execute

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

plpy.prepare准备一个查询的执行计划。如果查询中有参数引用,则使用查询字符串和参数类型列表进行调用。例如:

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

text是变量的类型,您将传递$1。如果您不想将任何参数传递给查询,则第二个参数是可选的。

在准备语句之后,您将使用函数plpy.execute来运行:

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

将该计划作为第一个参数(而不是查询字符串)传递,并将一个值列表作为第二个参数替换为查询。如果查询不期望任何参数,则第二个参数是可选的。第三个参数是和以前一样的可选行限制。

或者,您可以调用plan对象上的execute方法:

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

查询参数和结果行字段在PostgreSQL和Python数据类型之间转换。

当您使用PL/Python模块准备一个计划时,它会自动保存。请阅读SPI文档(第46章)来描述这意味着什么。为了在函数调用中有效地使用这个功能,需要使用一个持久存储字典SD或GD(参见第45.4节)。例如:

1
2
3
4
5
6
7
8
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;

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

plpy.cursor函数接受与plpy.execute相同的参数(除了行限制)并返回一个游标对象,它允许以较小的块处理大的结果集。与plpy.execute,可以使用查询字符串或计划对象,也可以使用参数列表,或者可以将游标函数称为计划对象的方法。

游标对象提供一个获取方法,该方法接受整数参数并返回结果对象。每次调用fetch时,返回的对象将包含下一行,它们不会大于参数值。一旦所有行耗尽,fetch开始返回一个空结果对象。游标对象还提供一个迭代器接口,每次产生一行,直到所有行都耗尽为止。获取的数据不是作为结果对象返回的,而是作为字典,每个字典对应一个结果行。

从大型表中处理数据的两种方法的示例是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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方法。一旦关闭,一个游标就不能再被提取了。

提示:
不要混淆由plpy创建的对象。使用Python数据库API规范定义的DB-API游标游标。除了名字以外,他们没有任何共同之处。

捕获错误

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

1
2
3
4
5
6
7
8
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;

所提出的异常的实际类对应于导致错误的特定条件。参考表PostgreSQL Error Codes列出可能的条件。模块plpy.spiexception为每个PostgreSQL条件定义了一个异常类,从条件名称派生它们的名称。例如,division_by_zero变成了DivisionByZero, unique_违背变成了独特的效果,fdw_error变成了FdwError,等等。这些异常类都继承自SPIError。这种分离使得处理特定错误变得更容易,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.spiexception模块继承自SPIError,除子句处理外,它将捕获任何数据库访问错误。

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

显式子事务

从上节所述的数据库访问引起的错误中恢复,可能导致一些操作在其中一个操作失败之前成功,然后从错误中恢复后,数据将处于不一致的状态。PL/Python以显式子事务的形式提供了这个问题的解决方案。

子事务上下文管理器

考虑一个实现两个帐户之间转移的函数:

1
2
3
4
5
6
7
8
9
10
11
CREATE FUNCTION transfer_funds() RETURNS void AS $$
try:
plpy.execute("UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'")
plpy.execute("UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'")
except plpy.SPIError, e:
result = "error transferring funds: %s" % e.args
else:
result = "funds transferred correctly"
plan = plpy.prepare("INSERT INTO operations (result) VALUES ($1)", ["text"])
plpy.execute(plan, [result])
$$ LANGUAGE plpythonu;

如果第二个UPDATE语句导致一个异常被抛出,这个函数将报告错误,但是第一次更新的结果将会被提交。换句话说,这些资金将从乔的账户中撤出,但不会转移到玛丽的账户上。

为了避免这样的问题,你可以包装你的plpy.execute在显式子事务中的调用。plpy模块提供了一个helper对象,用于管理通过plpy.subtransaction()函数创建的显式子事务。由这个函数创建的对象实现了上下文管理器接口。使用显式子事务,我们可以将函数重写为:

1
2
3
4
5
6
7
8
9
10
11
12
CREATE FUNCTION transfer_funds2() RETURNS void AS $$
try:
with plpy.subtransaction():
plpy.execute("UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'")
plpy.execute("UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'")
except plpy.SPIError, e:
result = "error transferring funds: %s" % e.args
else:
result = "funds transferred correctly"
plan = plpy.prepare("INSERT INTO operations (result) VALUES ($1)", ["text"])
plpy.execute(plan, [result])
$$ LANGUAGE plpythonu;

注意,仍然需要使用try/catch,否则,异常将传到Python堆栈的顶部,并导致整个函数以PostgreSQL错误中止,这样操作表就不会插入任何行。子事务上下文管理器不会捕获错误,它只确保在其范围内执行的所有数据库操作将被自动地提交或回滚。子事务块的回滚发生在任何类型的异常退出上,而不仅仅是由数据库访问引起的错误引起的。在显式子事务块中引发的常规Python异常也会导致将子事务回滚。

旧的Python版本

在Python 2.6中默认情况下,使用with关键字的上下文管理器语法是可用的。如果使用较老的Python版本的PL/Python,仍然可以使用显式子事务,尽管不透明。可以使用enterexit方便别名调用subtransaction manager的__enter____exit__函数。转移资金的示例函数可以写成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CREATE FUNCTION transfer_funds_old() RETURNS void AS $$
try:
subxact = plpy.subtransaction()
subxact.enter()
try:
plpy.execute("UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'")
plpy.execute("UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'")
except:
import sys
subxact.exit(*sys.exc_info())
raise
else:
subxact.exit(None, None, None)
except plpy.SPIError, e:
result = "error transferring funds: %s" % e.args
else:
result = "funds transferred correctly"

plan = plpy.prepare("INSERT INTO operations (result) VALUES ($1)", ["text"])
plpy.execute(plan, [result])
$$ LANGUAGE plpythonu;

实用功能

plpy 模块也提供了如下功能:

  • plpy.debug(msg, **kwargs)
  • plpy.log(msg, **kwargs)
  • plpy.info(msg, **kwargs)
  • plpy.notice(msg, **kwargs)
  • plpy.warning(msg, **kwargs)
  • plpy.error(msg, **kwargs)
  • plpy.fatal(msg, **kwargs)

plpy.errorplpy.fatal
实际上会引发Python异常,如果未捕获,则传到调用查询,导致当前事务或子事务被终止。提高plpy.Error(msg)和提高plpy.Fatal(msg)等同于调用plpy.Error(msg)plpy.fatal(msg),但是raise不允许传递关键字参数。其他函数只生成不同优先级的消息。是否将特定优先级的消息报告给客户机,写入服务器日志,或两者都由log_min_messagesclient_min_messages配置变量控制。

msg参数作为一个位置参数给出。对于向后兼容性,可以给出多个位置参数。在这种情况下,位置参数元组的字符串表示形式成为向客户机报告的消息。

以下关键字参数被接受:

  • detail
  • hint
  • sqlstate
  • schema_name
  • table_name
  • column_name
  • datatype_name
  • constraint_name

作为关键字参数传递的对象的字符串表示形式用于丰富向客户机报告的消息。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CREATE FUNCTION raise_custom_exception() RETURNS void AS $$
plpy.error("custom exception message",
detail="some info about exception",
hint="hint for users")
$$ LANGUAGE plpythonu;

=# SELECT raise_custom_exception();
ERROR: plpy.Error: custom exception message
DETAIL: some info about exception
HINT: hint for users
CONTEXT: Traceback (most recent call last):
PL/Python function "raise_custom_exception", line 4, in <module>
hint="hint for users")
PL/Python function "raise_custom_exception"

另一组实用函数是plpy.quote_literal(string)plpy.quote_nullable(string)plpy.quote_ident(string)。它们相当于内置引用函数。它们在构造特别查询时非常有用。一个PL/Python的动态SQL:

1
2
3
4
plpy.execute("UPDATE tbl SET %s = %s WHERE key = %s" % (
plpy.quote_ident(colname),
plpy.quote_nullable(newvalue),
plpy.quote_literal(keyvalue)))

环境变量

Python解释器接受的一些环境变量也可以用来影响PL/Python行为。它们需要设置在主PostgreSQL服务器进程的环境中,例如在启动脚本中。可用的环境变量取决于Python的版本;有关详细信息,请参阅Python文档。在编写本文时,以下环境变量对PL/Python有影响,假设有足够的Python版本:

  • PYTHONHOME
  • PYTHONPATH
  • PYTHONY2K
  • PYTHONOPTIMIZE
  • PYTHONDEBUG
  • PYTHONVERBOSE
  • PYTHONCASEOK
  • PYTHONDONTWRITEBYTECODE
  • PYTHONIOENCODING
  • PYTHONUSERBASE
  • PYTHONHASHSEED

(它似乎是一个Python实现的细节,超出了PL/Python的控制,在Python手册页上列出的一些环境变量只在命令行解释器中有效,而不是嵌入的Python解释器。)