80
CC-BY Programming with Python and PostgreSQL Peter Eisentraut [email protected] F-Secure Corporation PostgreSQL Conference East 2011

Programming with Python and PostgreSQL

Embed Size (px)

DESCRIPTION

presentation from PgEast 2011

Citation preview

Page 1: Programming with Python and PostgreSQL

CC-BY

Programming with Pythonand PostgreSQL

Peter [email protected]

F-Secure Corporation

PostgreSQL Conference East 2011

Page 2: Programming with Python and PostgreSQL

Partitioning

• Part I: Client programming (60 min)• Part II: PL/Python (30 min)

Page 3: Programming with Python and PostgreSQL

Why Python?

Pros:• widely used• easy• strong typing• scripting, interactive use• good PostgreSQL support• client and server (PL) interfaces• open source, community-based

Pros:• no static syntax checks, must rely on test coverage• Python community has varying interest in RDBMS

Page 4: Programming with Python and PostgreSQL

Why Python?

Pros:• widely used• easy• strong typing• scripting, interactive use• good PostgreSQL support• client and server (PL) interfaces• open source, community-based

Pros:• no static syntax checks, must rely on test coverage• Python community has varying interest in RDBMS

Page 5: Programming with Python and PostgreSQL

Why Python?

Pros:• widely used• easy• strong typing• scripting, interactive use• good PostgreSQL support• client and server (PL) interfaces• open source, community-based

Pros:• no static syntax checks, must rely on test coverage• Python community has varying interest in RDBMS

Page 6: Programming with Python and PostgreSQL

Part I

Client Programming

Page 7: Programming with Python and PostgreSQL

Example

import psycopg2

dbconn = psycopg2.connect('dbname=dellstore2')cursor = dbconn.cursor()cursor.execute("""SELECT firstname, lastnameFROM customersORDER BY 1, 2LIMIT 10""")for row in cursor.fetchall():

print "Name: %s %s" % (row[0], row[1])cursor.close()db.close()

Page 8: Programming with Python and PostgreSQL

Drivers

Name License Platforms Py VersionsPsycopg LGPL Unix, Win 2.4–3.2PyGreSQL BSD Unix, Win 2.3–2.6ocpgdb BSD Unix 2.3–2.6py-postgresql BSD pure Python 3.0+bpgsql (alpha) LGPL pure Python 2.3–2.6pg8000 BSD pure Python 2.5–3.0+

More details

• http://wiki.postgresql.org/wiki/Python

• http://wiki.python.org/moin/PostgreSQL

Page 9: Programming with Python and PostgreSQL

Drivers

Name License Platforms Py VersionsPsycopg LGPL Unix, Win 2.4–3.2PyGreSQL BSD Unix, Win 2.3–2.6ocpgdb BSD Unix 2.3–2.6py-postgresql BSD pure Python 3.0+bpgsql (alpha) LGPL pure Python 2.3–2.6pg8000 BSD pure Python 2.5–3.0+

More details

• http://wiki.postgresql.org/wiki/Python

• http://wiki.python.org/moin/PostgreSQL

Page 10: Programming with Python and PostgreSQL

DB-API 2.0

• the standard Python database API• all mentioned drivers support it• defined in PEP 249• discussions: [email protected]• very elementary (from a PostgreSQL perspective)• outdated relative to Python language development• lots of extensions and incompatibilities possible

Page 11: Programming with Python and PostgreSQL

Higher-Level Interfaces

• Zope• SQLAlchemy• Django

Page 12: Programming with Python and PostgreSQL

Psycopg Facts

• Main authors: Federico Di Gregorio, Daniele Varrazzo• License: LGPLv3+• Web site: http://initd.org/psycopg/

• Documentation: http://initd.org/psycopg/docs/• Git, Gitweb

• Mailing list: [email protected]• Twitter: @psycopg• Latest version: 2.4 (February 27, 2011)

Page 13: Programming with Python and PostgreSQL

Using the Driver

import psycopg2

dbconn = psycopg2.connect(...)...

Page 14: Programming with Python and PostgreSQL

Driver Independence?

import psycopg2

dbconn = psycopg2.connect(...) # hardcodes driver name

Page 15: Programming with Python and PostgreSQL

Driver Independence?

import psycopg2 as dbdriver

dbconn = dbdriver.connect(...)

Page 16: Programming with Python and PostgreSQL

Driver Independence?

dbtype = 'psycopg2' # e.g. from config filedbdriver = __import__(dbtype,

globals(), locals(),[], -1)

dbconn = dbdriver.connect(...)

Page 17: Programming with Python and PostgreSQL

Connecting

# libpq-like connection stringdbconn = psycopg2.connect('dbname=dellstore2

host=localhost port=5432')

# samedbconn = psycopg2.connect(dsn='dbname=dellstore2

host=localhost port=5432')

# keyword arguments# (not all possible libpq options supported)dbconn = psycopg2.connect(database='dellstore2',

host='localhost',port='5432')

DB-API 2.0 says: arguments database dependent

Page 18: Programming with Python and PostgreSQL

“Cursors”

cursor = dbconn.cursor()

• not a real database cursor, only an API abstraction• think “statement handle”

Page 19: Programming with Python and PostgreSQL

Server-Side Cursors

cursor = dbconn.cursor(name='mycursor')

• a real database cursor• use for large result sets

Page 20: Programming with Python and PostgreSQL

Executing

# queriescursor.execute("""SELECT firstname, lastnameFROM customersORDER BY 1, 2LIMIT 10""")

# updatescursor.execute("UPDATE customers SET password = NULL")print "%d rows updated" % cursor.rowcount

# or anything elsecursor.execute("ANALYZE customers")

Page 21: Programming with Python and PostgreSQL

Fetching Query Results

cursor.execute("SELECT firstname, lastname FROM ...")cursor.fetchall()

[('AABBKO', 'DUTOFRPLOK'),('AABTSI', 'ZFCKMPRVVJ'),('AACOHS', 'EECCQPVTIW'),('AACVVO', 'CLSXSGZYKS'),('AADVMN', 'MEMQEWYFYE'),('AADXQD', 'GLEKVVLZFV'),('AAEBUG', 'YUOIINRJGE')]

Page 22: Programming with Python and PostgreSQL

Fetching Query Results

cursor.execute("SELECT firstname, lastname FROM ...")for row in cursor.fetchall():

print "Name: %s %s" % (row[0], row[1])

Note: field access only by number

Page 23: Programming with Python and PostgreSQL

Fetching Query Results

cursor.execute("SELECT firstname, lastname FROM ...")for row in cursor.fetchall():

print "Name: %s %s" % (row[0], row[1])

Note: field access only by number

Page 24: Programming with Python and PostgreSQL

Fetching Query Results

cursor.execute("SELECT firstname, lastname FROM ...")row = cursor.fetchone()if row is not None:

print "Name: %s %s" % (row[0], row[1])

Page 25: Programming with Python and PostgreSQL

Fetching Query Results

cursor.execute("SELECT firstname, lastname FROM ...")for row in cursor:

print "Name: %s %s" % (row[0], row[1])

Page 26: Programming with Python and PostgreSQL

Fetching Query Results in Batches

cursor = dbconn.cursor(name='mycursor')cursor.arraysize = 500 # default: 1cursor.execute("SELECT firstname, lastname FROM ...")while True:

batch = cursor.fetchmany()break if not batchfor row in batch:

print "Name: %s %s" % (row[0], row[1])

Page 27: Programming with Python and PostgreSQL

Fetching Query Results in Batches

cursor = dbconn.cursor(name='mycursor')cursor.execute("SELECT firstname, lastname FROM ...")cursor.itersize = 2000 # defaultfor row in cursor:

print "Name: %s %s" % (row[0], row[1])

Page 28: Programming with Python and PostgreSQL

Getting Query Metadata

cursor.execute("SELECT DISTINCT state, zip FROMcustomers")

print cursor.description[0].nameprint cursor.description[0].type_codeprint cursor.description[1].nameprint cursor.description[1].type_code

state1043 # == psycopg2.STRINGzip23 # == psycopg2.NUMBER

Page 29: Programming with Python and PostgreSQL

Passing Parameters

cursor.execute("""UPDATE customers

SET password = %sWHERE customerid = %s

""", ["sekret", 37])

Page 30: Programming with Python and PostgreSQL

Passing Parameters

Not to be confused with (totally evil):

cursor.execute("""UPDATE customers

SET password = '%s'WHERE customerid = %d

""" % ["sekret", 37])

Page 31: Programming with Python and PostgreSQL

Passing Parameters

cursor.execute("INSERT INTO foo VALUES (%s)","bar") # WRONG

cursor.execute("INSERT INTO foo VALUES (%s)",("bar")) # WRONG

cursor.execute("INSERT INTO foo VALUES (%s)",("bar",)) # correct

cursor.execute("INSERT INTO foo VALUES (%s)",["bar"]) # correct

(from Psycopg documentation)

Page 32: Programming with Python and PostgreSQL

Passing Parameters

cursor.execute("""UPDATE customers

SET password = %(pw)sWHERE customerid = %(id)s

""", {'id': 37, 'pw': "sekret"})

Page 33: Programming with Python and PostgreSQL

Passing Many Parameter Sets

cursor.executemany("""UPDATE customers

SET password = %sWHERE customerid = %s

""", [["ahTh4oip", 100],["Rexahho7", 101],["Ee1aetui", 102]])

Page 34: Programming with Python and PostgreSQL

Calling Procedures

cursor.callproc('pg_start_backup', 'label')

Page 35: Programming with Python and PostgreSQL

Data Types

from decimal import Decimalfrom psycopg2 import Date

cursor.execute("""INSERT INTO orders (orderdate, customerid,

netamount, tax, totalamount)VALUES (%s, %s, %s, %s, %s)""",[Date(2011, 03, 23), 12345,Decimal("899.95"), 8.875, Decimal("979.82")])

Page 36: Programming with Python and PostgreSQL

Mogrify

from decimal import Decimalfrom psycopg2 import Date

cursor.mogrify("""INSERT INTO orders (orderdate, customerid,

netamount, tax, totalamount)VALUES (%s, %s, %s, %s, %s)""",[Date(2011, 03, 23), 12345,Decimal("899.95"), 8.875, Decimal("979.82")])

Result:

"\nINSERT INTO orders (orderdate, customerid,\nnetamount, tax, totalamount)\nVALUES('2011-03-23'::date, 12345, 899.95, 8.875, 979.82)"

Page 37: Programming with Python and PostgreSQL

Data Types

cursor.execute("""SELECT * FROM orders WHERE customerid = 12345""")

Result:

(12002, datetime.date(2011, 3, 23), 12345,Decimal('899.95'), Decimal('8.88'),Decimal('979.82'))

Page 38: Programming with Python and PostgreSQL

Nulls

Input:

cursor.mogrify("SELECT %s", [None])

'SELECT NULL'

Output:

cursor.execute("SELECT NULL")cursor.fetchone()

(None,)

Page 39: Programming with Python and PostgreSQL

Booleans

cursor.mogrify("SELECT %s, %s", [True, False])

'SELECT true, false'

Page 40: Programming with Python and PostgreSQL

Binary DataStandard way:

from psycopg2 import Binarycursor.mogrify("SELECT %s", [Binary("foo")])

"SELECT E'\\\\x666f6f'::bytea"

Other ways:

cursor.mogrify("SELECT %s", [buffer("foo")])

"SELECT E'\\\\x666f6f'::bytea"

cursor.mogrify("SELECT %s",[bytearray.fromhex(u"deadbeef")])

"SELECT E'\\\\xdeadbeef'::bytea"

There are more. Check the documentation. Check the versions.

Page 41: Programming with Python and PostgreSQL

Binary DataStandard way:

from psycopg2 import Binarycursor.mogrify("SELECT %s", [Binary("foo")])

"SELECT E'\\\\x666f6f'::bytea"

Other ways:

cursor.mogrify("SELECT %s", [buffer("foo")])

"SELECT E'\\\\x666f6f'::bytea"

cursor.mogrify("SELECT %s",[bytearray.fromhex(u"deadbeef")])

"SELECT E'\\\\xdeadbeef'::bytea"

There are more. Check the documentation. Check the versions.

Page 42: Programming with Python and PostgreSQL

Date/Time

Standard ways:

from psycopg2 import Date, Time, Timestamp

cursor.mogrify("SELECT %s, %s, %s",[Date(2011, 3, 23),Time(9, 0, 0),Timestamp(2011, 3, 23, 9, 0, 0)])

"SELECT '2011-03-23'::date, '09:00:00'::time,'2011-03-23T09:00:00'::timestamp"

Page 43: Programming with Python and PostgreSQL

Date/Time

Other ways:

import datetime

cursor.mogrify("SELECT %s, %s, %s, %s",[datetime.date(2011, 3, 23),datetime.time(9, 0, 0),datetime.datetime(2011, 3, 23, 9, 0),datetime.timedelta(minutes=90)])

"SELECT '2011-03-23'::date, '09:00:00'::time,'2011-03-23T09:00:00'::timestamp, '0 days5400.000000 seconds'::interval"

mx.DateTime also supported

Page 44: Programming with Python and PostgreSQL

Arrays

foo = [1, 2, 3]bar = [datetime.time(9, 0), datetime.time(10, 30)]

cursor.mogrify("SELECT %s, %s",[foo, bar])

"SELECT ARRAY[1, 2, 3], ARRAY['09:00:00'::time,'10:30:00'::time]"

Page 45: Programming with Python and PostgreSQL

Tuples

foo = (1, 2, 3)

cursor.mogrify("SELECT * FROM customers WHEREcustomerid IN %s",

[foo])

'SELECT * FROM customers WHERE customerid IN (1, 2, 3)'

Page 46: Programming with Python and PostgreSQL

Hstore

import psycopg2.extras

psycopg2.extras.register_hstore(cursor)

x = {'a': 'foo', 'b': 'bar'}

cursor.mogrify("SELECT %s",[x])

"SELECT hstore(ARRAY[E'a', E'b'], ARRAY[E'foo',E'bar'])"

Page 47: Programming with Python and PostgreSQL

Unicode Support

Cause all result strings to be returned as Unicode strings:

psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)

psycopg2.extensions.register_type(psycopg2.extensions.UNICODEARRAY)

Page 48: Programming with Python and PostgreSQL

Transaction Control

Transaction blocks are used by default. Must use

dbconn.commit()

or

dbconn.rollback()

Page 49: Programming with Python and PostgreSQL

Transaction Control: Autocommit

import psycopg2.extensions

dbconn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)

cursor = dbconn.cursor()cursor.execute("VACUUM")

Page 50: Programming with Python and PostgreSQL

Transaction Control: Isolation Mode

import psycopg2.extensions

dbconn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE) # or other level

cursor = dbconn.cursor()cursor.execute(...)...dbconn.commit()

Page 51: Programming with Python and PostgreSQL

Exception Handling

StandardError|__ Warning|__ Error

|__ InterfaceError|__ DatabaseError

|__ DataError|__ OperationalError| |__ psycopg2.extensions.QueryCanceledError| |__ psycopg2.extensions.TransactionRollbackError|__ IntegrityError|__ InternalError|__ ProgrammingError|__ NotSupportedError

Page 52: Programming with Python and PostgreSQL

Error Messages

try:cursor.execute("boom")

except Exception, e:print e.pgerror

Page 53: Programming with Python and PostgreSQL

Error Codes

import psycopg2.errorcodes

while True:try:

cursor.execute("UPDATE something ...")cursor.execute("UPDATE otherthing ...")break

except Exception, e:if e.pgcode == \

psycopg2.errorcodes.SERIALIZATION_FAILURE:continue

else:raise

Page 54: Programming with Python and PostgreSQL

Connection and Cursor Factories

Want: accessing result columns by nameRecall:

dbconn = psycopg2.connect(dsn='...')cursor = dbconn.cursor()cursor.execute("""SELECT firstname, lastnameFROM customersORDER BY 1, 2LIMIT 10""")for row in cursor.fetchall():

print "Name: %s %s" % (row[0], row[1]) # stupid :(

Page 55: Programming with Python and PostgreSQL

Connection and Cursor FactoriesSolution 1: Using DictConnection:

import psycopg2.extras

dbconn = psycopg2.connect(dsn='...',connection_factory=psycopg2.extras.DictConnection)

cursor = dbconn.cursor()cursor.execute("""SELECT firstname, lastnameFROM customersORDER BY 1, 2LIMIT 10""")for row in cursor.fetchall():

print "Name: %s %s" % (row['firstname'], # or row[0]row['lastname']) # or row[1]

Page 56: Programming with Python and PostgreSQL

Connection and Cursor FactoriesSolution 2: Using RealDictConnection:

import psycopg2.extras

dbconn = psycopg2.connect(dsn='...',connection_factory=psycopg2.extras.RealDictConnection)

cursor = dbconn.cursor()cursor.execute("""SELECT firstname, lastnameFROM customersORDER BY 1, 2LIMIT 10""")for row in cursor.fetchall():

print "Name: %s %s" % (row['firstname'],row['lastname'])

Page 57: Programming with Python and PostgreSQL

Connection and Cursor FactoriesSolution 3: Using NamedTupleConnection:

import psycopg2.extras

dbconn = psycopg2.connect(dsn='...',connection_factory=psycopg2.extras.NamedTupleConnection)

cursor = dbconn.cursor()cursor.execute("""SELECT firstname, lastnameFROM customersORDER BY 1, 2LIMIT 10""")for row in cursor.fetchall():

print "Name: %s %s" % (row.firstname, # or row[0]row.lastname) # or row[1]

Page 58: Programming with Python and PostgreSQL

Connection and Cursor FactoriesAlternative: UsingDictCursor/RealDictCursor/NamedTupleCursor:

import psycopg2.extras

dbconn = psycopg2.connect(dsn='...')cursor = dbconn.cursor(cursor_factory=psycopg2.extras.

DictCursor/RealDictCursor/NameTupleCursor)cursor.execute("""SELECT firstname, lastnameFROM customersORDER BY 1, 2LIMIT 10""")for row in cursor.fetchall():

print "Name: %s %s" % (row['firstname'],row['lastname'])

# (resp. row.firstname, row.lastname)

Page 59: Programming with Python and PostgreSQL

Supporting New Data Types

Only a finite list of types is supported by default: Date, Binary,etc.

• map new PostgreSQL data types into Python• map new Python data types into PostgreSQL

Page 60: Programming with Python and PostgreSQL

Mapping New PostgreSQL Types IntoPython

import psycopg2import psycopg2.extensions

def cast_oidvector(value, _cursor):"""Convert oidvector to Python array"""if value is None:

return Nonereturn map(int, value.split(' '))

OIDVECTOR = psycopg2.extensions.new_type((30,),'OIDVECTOR', cast_oidvector)

psycopg2.extensions.register_type(OIDVECTOR)

Page 61: Programming with Python and PostgreSQL

Mapping New Python Types intoPostgreSQL

from psycopg2.extensions import adapt,register_adapter, AsIs

class Point(object):def __init__(self, x, y):

self.x = xself.y = y

def adapt_point(point):return AsIs("'(%s, %s)'" % (adapt(point.x),

adapt(point.y)))

register_adapter(Point, adapt_point)

cur.execute("INSERT INTO atable (apoint) VALUES (%s)",(Point(1.23, 4.56),))

(from Psycopg documentation)

Page 62: Programming with Python and PostgreSQL

Connection Pooling With Psycopg

for non-threaded applications:

from psycopg2.pool import SimpleConnectionPool

pool = SimpleConnectionPool(1, 20, dsn='...')dbconn = pool.getconn()...pool.putconn(dbconn)pool.closeall()

for non-threaded applications:

from psycopg2.pool import ThreadedConnectionPool

pool = ThreadedConnectionPool(1, 20, dsn='...')dbconn = pool.getconn()cursor = dbconn.cursor()...pool.putconn(dbconn)pool.closeall()

Page 63: Programming with Python and PostgreSQL

Connection Pooling With Psycopgfor non-threaded applications:

from psycopg2.pool import SimpleConnectionPool

pool = SimpleConnectionPool(1, 20, dsn='...')dbconn = pool.getconn()...pool.putconn(dbconn)pool.closeall()

for non-threaded applications:

from psycopg2.pool import ThreadedConnectionPool

pool = ThreadedConnectionPool(1, 20, dsn='...')dbconn = pool.getconn()cursor = dbconn.cursor()...pool.putconn(dbconn)pool.closeall()

Page 64: Programming with Python and PostgreSQL

Connection Pooling With DBUtils

import psycopg2from DBUtils.PersistentDB import PersistentDB

dbconn = PersistentDB(psycopg2, dsn='...')cursor = dbconn.cursor()...

see http://pypi.python.org/pypi/DBUtils/

Page 65: Programming with Python and PostgreSQL

The Other Stuff

• thread safety: can share connections, but not cursors• COPY support: cursor.copy_from(), cursor.copy_to()• large object support: connection.lobject()• 2PC: connection.xid(), connection.tpc_begin(), . . .• query cancel: dbconn.cancel()• notices: dbconn.notices• notifications: dbconn.notifies• asynchronous communication• coroutine support• logging cursor

Page 66: Programming with Python and PostgreSQL

Part II

PL/Python

Page 67: Programming with Python and PostgreSQL

Setup

• included with PostgreSQL• configure --with-python• apt-get/yum install postgresql-plpython

• CREATE LANGUAGE plpythonu;

• Python 3: CREATE LANGUAGE plpython3u;

• “untrusted”, superuser only

Page 68: Programming with Python and PostgreSQL

Basic ExamplesCREATE FUNCTION add(a int, b int) RETURNS intLANGUAGE plpythonuAS $$return a + b$$;

CREATE FUNCTION longest(a text, b text) RETURNS textLANGUAGE plpythonuAS $$if len(a) > len(b):

return aelif len(b) > len(a):

return belse:

return None$$;

Page 69: Programming with Python and PostgreSQL

Using Modules

CREATE FUNCTION json_to_array(j text) RETURNS text[]LANGUAGE plpythonuAS $$import json

return json.loads(j)$$;

Page 70: Programming with Python and PostgreSQL

Database Calls

CREATE FUNCTION clear_passwords() RETURNS intLANGUAGE plpythonuAS $$rv = plpy.execute("UPDATE customers SET password =

NULL")return rv.nrows$$;

Page 71: Programming with Python and PostgreSQL

Database Calls With Parameters

CREATE FUNCTION set_password(username text, passwordtext) RETURNS boolean

LANGUAGE plpythonuAS $$plan = plpy.prepare("UPDATE customers SET password = $1

WHERE username= $2", ['text', 'text'])rv = plpy.execute(plan, [username, password])return rv.nrows == 1$$;

Page 72: Programming with Python and PostgreSQL

Avoiding Prepared Statements

CREATE FUNCTION set_password(username text, passwordtext) RETURNS boolean

LANGUAGE plpythonuAS $$rv = plpy.execute("UPDATE customers SET password = %s

WHERE username= %s" %(plpy.quote_nullable(username),plpy.quote_literal(password)))

return rv.nrows == 1$$;

(available in 9.1-to-be)

Page 73: Programming with Python and PostgreSQL

Caching Plans

CREATE FUNCTION set_password2(username text, passwordtext) RETURNS boolean

LANGUAGE plpythonuAS $$if 'myplan' in SD:

plan = SD['myplan']else:

plan = plpy.prepare("UPDATE customers SET password= $1 WHERE username= $2", ['text', 'text'])

SD['myplan'] = planrv = plpy.execute(plan, [username, password])return rv.nrows == 1$$;

Page 74: Programming with Python and PostgreSQL

Processing Query Results

CREATE FUNCTION get_customer_name(username text)RETURNS boolean

LANGUAGE plpythonuAS $$plan = plpy.prepare("SELECT firstname || ' ' ||

lastname AS ""name"" FROM customers WHERE username =$1", ['text'])

rv = plpy.execute(plan, [username], 1)return rv[0]['name']$$;

Page 75: Programming with Python and PostgreSQL

Compare: PL/Python vs. DB-API

PL/Python:

plan = plpy.prepare("SELECT ...")for row in plpy.execute(plan, ...):

plpy.info(row["fieldname"])

DB-API:

dbconn = psycopg2.connect(...)cursor = dbconn.cursor()cursor.execute("SELECT ...")for row in cursor.fetchall() do:

print row[0]

Page 76: Programming with Python and PostgreSQL

Set-Returning and Table Functions

CREATE FUNCTION get_customers(id int) RETURNS SETOFcustomers

LANGUAGE plpythonuAS $$plan = plpy.prepare("SELECT * FROM customers WHERE

customerid = $1", ['int'])rv = plpy.execute(plan, [id])return rv$$;

Page 77: Programming with Python and PostgreSQL

Triggers

CREATE FUNCTION delete_notifier() RETURNS triggerLANGUAGE plpythonuAS $$if TD['event'] == 'DELETE':

plpy.notice("one row deleted from table %s" %TD['table_name'])

$$;

CREATE TRIGGER customers_delete_notifier AFTER DELETEON customers FOR EACH ROW EXECUTE PROCEDUREdelete_notifier();

Page 78: Programming with Python and PostgreSQL

Exceptions

CREATE FUNCTION test() RETURNS textLANGUAGE plpythonuAS $$try:

rv = plpy.execute("SELECT ...")except plpy.SPIError, e:

plpy.notice("something went wrong")

The transaction is still aborted in < 9.1.

Page 79: Programming with Python and PostgreSQL

New in PostgreSQL 9.1

• SPI calls wrapped in subtransactions• custom SPI exceptions: subclass per SQLSTATE,.sqlstate attribute

• plpy.subtransaction() context manager• support for OUT parameters• quoting functions• validator• lots of internal improvements

Page 80: Programming with Python and PostgreSQL

The End