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語法聲明:

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空值。

例如,返回兩個整數中較大者的函數可以定義爲:

CREATE FUNCTION pymax (a integer, b integer)
  RETURNS integer
AS $$
  if a > b:
    return a
  return b
$$ LANGUAGE plpythonu;

作爲函數定義主體給出的Python代碼被轉換爲Python函數。例如,上面的結果是:

def __plpython_procedure_pymax_23456():
  if a > b:
    return a
  return b

假設23456是由PostgreSQL分配給函數的OID。

參數被設置爲全局變量。由於Python的範圍規則,這具有微妙的後果,即變量不能在函數內重新分配給涉及變量名本身的表達式的值,除非該變量在塊中被重新聲明爲全局變量。例如,以下內容不起作用:

CREATE FUNCTION pystrip(x text)
  RETURNS text
AS $$
  x = x.strip()  # error
  return x
$$ LANGUAGE plpythonu;

因爲賦值給x使x成爲整個塊的局部變量,所以賦值右側的x引用尚未賦值的局部變量x,而不是PL/Python函數參數。使用全局聲明,可以使其工作:

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結果。或者,我們可以檢查函數體中的空輸入:

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列表:

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數組時,每個級別的內部列表必須都是相同的大小。例如:

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程序員很熟悉:

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映射。映射的元素名稱是複合類型的屬性名稱。如果傳遞行的屬性具有空值,那麼它在映射中沒有任何值。這是一個例子:

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函數中返回行或複合類型有多種方法。下面的例子假設我們有:

CREATE TYPE named_value AS (
  name   text,
  value  integer
);

複合結果可以返回爲:

序列類型(一個元組或列表,但不是一個集合,因爲它不是可索引的)

返回的序列對象必須具有相同數量的項,因爲複合結果類型具有字段。索引0的項被分配給複合類型的第一個字段,1的第二個字段,等等。例如:

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列表表示一個複合類型,或者另一個數組維度。

映射(字典)
從映射中檢索每個結果類型列的值,並使用列名稱作爲鍵。例子:

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

任何額外的字典鍵/值對都被忽略。丟失的鍵被視爲錯誤。若要返回任何列的SQL空值,請插入None用相應的列名作爲鍵。

對象(任何提供方法__getattr__的對象)
這和映射是一樣的。例子:

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參數的函數。例如:

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

SELECT * FROM multiout_simple();

設置返回函數

PL/Python函數還可以返回標量或複合類型的集合。有幾種方法可以實現這一點,因爲返回的對象內部變成了迭代器。下面的例子假設我們有複合類型:

CREATE TYPE greeting AS (
  how text,
  who text
);

可以從A返回一個集合結果:

序列類型(元組、列表、集合)

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方法的對象)

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)

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

還支持帶OUT參數的集返回函數(使用返回的記錄集)。例如:

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語句調用的匿名代碼塊:

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模塊提供了幾個執行數據庫命令的函數:

plpy.execute(query [, max-rows])
調用plpy.execute使用一個查詢字符串和一個可選的行限制參數執行查詢,結果將返回到結果對象中。

結果對象模擬一個列表或dictionary對象。結果對象可以通過行號和列名訪問。例如:

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

從my_table返回最多5行。如果my_table有一個列my_column,它將被訪問如下:

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

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)

將該計劃作爲第一個參數(而不是查詢字符串)傳遞,並將一個值列表作爲第二個參數替換爲查詢。如果查詢不期望任何參數,則第二個參數是可選的。第三個參數是和以前一樣的可選行限制。

或者,您可以調用plan對象上的execute方法:

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

查詢參數和結果行字段在PostgreSQL和Python數據類型之間轉換。

當您使用PL/Python模塊準備一個計劃時,它會自動保存。請閱讀SPI文檔()來描述這意味着什麼。爲了在函數調用中有效地使用這個功能,需要使用一個持久存儲字典SD或GD。例如:

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,可以使用查詢字符串或計劃對象,也可以使用參數列表,或者可以將遊標函數稱爲計劃對象的方法。

遊標對象提供一個獲取方法,該方法接受整數參數並返回結果對象。每次調用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方法。一旦關閉,一個遊標就不能再被提取了。

提示:
不要混淆由plpy創建的對象。使用Python數據庫API規範定義的DB-API遊標遊標。除了名字以外,他們沒有任何共同之處。

捕獲錯誤

訪問數據庫的函數可能會遇到錯誤,這將導致它們中止並引發異常。plpy.executeplpy.prepare都可以引發plpy.SPIError子類的實例,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;

所提出的異常的實際類對應於導致錯誤的特定條件。參考表PostgreSQL Error Codes列出可能的條件。模塊plpy.spiexception爲每個PostgreSQL條件定義了一個異常類,從條件名稱派生它們的名稱。例如,division_by_zero變成了DivisionByZero, unique_違背變成了獨特的效果,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, e:
    return "other error, SQLSTATE %s" % e.sqlstate
else:
    return "fraction inserted"
$$ LANGUAGE plpythonu;

請注意,因爲所有的異常都來自於plpy.spiexception模塊繼承自SPIError,除子句處理外,它將捕獲任何數據庫訪問錯誤。

作爲處理不同錯誤條件的另一種方法,可以通過查看異常對象的sqlstate屬性來捕獲SPIError異常並確定除塊內的特定錯誤條件。該屬性是包含“SQLSTATE”錯誤代碼的字符串值。這種方法提供了大致相同的功能。

顯式子事務

從上節所述的數據庫訪問引起的錯誤中恢復,可能導致一些操作在其中一個操作失敗之前成功,然後從錯誤中恢復後,數據將處於不一致的狀態。PL/Python以顯式子事務的形式提供了這個問題的解決方案。

子事務上下文管理器

考慮一個實現兩個帳戶之間轉移的函數:

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()函數創建的顯式子事務。由這個函數創建的對象實現了上下文管理器接口。使用顯式子事務,我們可以將函數重寫爲:

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__函數。轉移資金的示例函數可以寫成:

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

作爲關鍵字參數傳遞的對象的字符串表示形式用於豐富向客戶機報告的消息。例如:

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:

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解釋器。)

注:原文地址

發佈了53 篇原創文章 · 獲贊 29 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章