You are on page 1of 7

WikiPrint - from Polar Technologies

ndice Funciones ("Procedimientos almacenados") 1. Ejemplo simple 2. Recibir tipos compuestos 3. Devolver tipos compuestos 4. Devolver mltiples tipos escalares o compuestos (set-of) 2. Disparadores (Triggers) Acceso a la base de datos 1. Generar mensajes y lanzar errores 2. Preparar y ejecutar consultas Ejemplos Prcticos 1. Analizador de direcciones Temas varios 1. Error con generadores y plpy.execute 2. Ejemplo de Agregados 3. Ejemplo de Disparador

Pl/Python: Python dentro de PostgreSQL


El lenguaje procedural plpython permite escribir funciones python para la base de datos relacional PostgreSQL. Python es un lenguaje simple, moderno y flexible, fcil de aprender y usar, que posibilita el diseo rpido de todo tipo de aplicaciones multiplataforma, ya sea en sistemas de gestin comercial como juegos o aplicaciones cientficas Para informacin sobre como instalarlo, ver PreguntasFrecuentes Como se puede acceder a todas las funciones de python, no debe usarse para usuarios no confiados, por ello la u en plpythonu (u=untrusted).

Funciones ("Procedimientos almacenados")


El cuerpo de una funcion plpythonu es simplemente un script de Python. Cuando la funcin es llamada, sus argumentos son pasados como elementos de una lista args; los argumentos por nombre son pasados como variables ordinarias. El resultado es devuelto de la manera usual, con un return o un yield (en el caso que devuelvan un conjunto de resultados) Los valores NULL de PostgreSQL equivalen a None en Python. Est disponible el diccionario SD para almacenar datos entre cada llamada a funcin, y el diccionario globar GD para usar desde todas las funciones. Nota: PostgreSQL 8.1 no soporta argumentos por nombre, recibir valores compuestos, devolver listas/tuplas o usar generadores.

Ejemplo simple
Calcular el valor mximo entre dos enteros, descartando valores nulos: 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; -- invoco la funcin: SELECT pymax(2, 3); -- devuelve 3

WikiPrint - from Polar Technologies

Recibir tipos compuestos


Las funciones plpython pueden recibir tipos compuestos (ej.registros de tablas) como diccionarios: CREATE TABLE empleado ( nombre TEXT, salario INTEGER, edad INTEGER ); CREATE FUNCTION sueldo_alto (e empleado) RETURNS boolean AS $$ if e["salario"] > 200000: return True if (e["edad"] < 30) and (e["salario"] > 100000): return True return False $$ LANGUAGE plpythonu;

Devolver tipos compuestos


Los tipos compuestos pueden ser devueltos como secuencias (tuplas o listas), diccionarios u objetos. En este ejemplo se devuelve un tipo compuesto representando una persona: CREATE TYPE persona AS ( nombre TEXT, apellido TEXT ); CREATE FUNCTION crear_persona (nombre TEXT, apellido TEXT) RETURNS persona AS $$ return [ nombre, apellido ] # o como tupla: return ( nombre, apellido ) # o como diccionario: return { "nombre": nombre, "apellido": apellido } $$ LANGUAGE plpythonu; CREATE FUNCTION crear_persona (nombre TEXT, persona TEXT) RETURNS persona AS $$ class Persona: def __init__ (self, n, a): self.nombre = n self.apellido = a return Persona(nombre, apellido) $$ LANGUAGE plpythonu;

Devolver mltiples tipos escalares o compuestos (set-of)


Se puede devolver mltiples valores (usando listas/tuplas, iteradores o generadores). En este ejemplo se devuelven varios saludos: CREATE TYPE saludo AS ( mensaje TEXT, -- hola a_quien TEXT -- mundo );

WikiPrint - from Polar Technologies

CREATE FUNCTION saludar (mensaje TEXT) RETURNS SETOF saludo AS $$ # devolver una tupla conteniendo lista de tipos compuestos # todas las otras combinaciones son posibles return ( [ mensaje, "Mundo" ], [ mensaje, "PostgreSQL" ], [ mensaje, "PL/Python" ] ) $$ LANGUAGE plpythonu; CREATE FUNCTION saludar_generador (mensaje TEXT) RETURNS SETOF saludo AS $$ for a_quien in [ "Mundo", "PostgreSQL", "PL/Python" ]: yield ( mensaje, a_quien ) $$ LANGUAGE plpythonu;

Disparadores (Triggers)
Cuando una funcin plpython es usada en un disparador, el diccionario TD contiene: TD["new"]: valores nuevos de la fila afectada (diccionario) TD["old"]: valores viejos de la fila afectada (diccionario) TD["event"]: tipo de evento "INSERT", "UPDATE", "DELETE", o "UNKNOWN" TD["when"]: momento en que se ejecut: "BEFORE" (antes del commit), "AFTER" (despues del commit), o "UNKNOWN" TD["level"]: nivel al que se ejecut: "ROW" (por fila), "STATEMENT" (por sentencia), o "UNKNOWN" TD["name"]: nombre del disparador TD["table_name"]: nombre de la tabla en que se dispar TD["table_schema"]: esquema en el que se dispar TD["relid"]: OID de la tabla que dispar Si el comando CREATE TRIGGER incluy argumentos, estos estarn disponibles en la lista TD["args"]

Si TD["when"] es BEFORE, se puede devolver None or "OK" para indicar que la fila no se modific, "SKIP" para abortar el evento, o "MODIFY" para indicar que hemos modificado la fila.

Acceso a la base de datos


Automaticamente se importa un mdulo llamado plpy.

Generar mensajes y lanzar errores


Este mdulo incluye funciones de plpy.debug(msg), plpy.log(msg), plpy.info(msg), plpy.notice(msg), plpy.warning(msg), plpy.error(msg), y plpy.fatal(msg) plpy.error y plpy.fatal en realidad disparan una excepcin python, si no se controla, se propaga y causa que la transaccin se aborte. Equivalente a llamar raise plpy.ERROR(msg) y raise plpy.FATAL(msg), respectivamente Las otras funciones solo generan mensajes en los distintos niveles de prioridad.

Preparar y ejecutar consultas


Adicionalmente, el mdulo plpy provee dos funciones: execute y prepare. Llamar a plpy.execute(query, limit) con una consulta (query: string) y un lmite de registros opcional (limit), permite ejecutar la consulta y devuelve los resultados en un objeto que emula una lista de diccionarios, pudiendo acceder por nmero de fila y nombre de columna. Tiene tres mtodos adicionales: nrows que devuelve el nmero de filas, y status. Ejemplo: rv = plpy.execute("SELECT * FROM mi_tabla", 5) for fila in rv: print fila['columna']

WikiPrint - from Polar Technologies

La funcin plpy.prepare(query,[parameter_types]), prepara el plan de ejecucin para una consulta, se le pasa la consulta como string y la lista de tipos de parmetros: plan = plpy.prepare("SELECT apellido FROM usuario WHERE nombre = $1 AND casado = $2 ", [ "text", "boolean" ]) text y boolean son los tipos de la variables que se pasara como parmetros ($1 y $2). Despues de preparar la sentencia, usar la funcin plpy.execute para ejecutarla: rv = plpy.execute(plan, [ "Mariano", True ], 5) Se pasa el plan como primer argumento, los parmetros como segundo (en este caso, busca nombre="Mariano" y si esta casado). El lmite (tercer argumento) es opcional. Al preparar un plan, este se almacena para usarlo posteriormente. Para usarlo eficazmente entre llamada y llamada, se debe usar un diccionario de almacenamiento persistente (SD o GD) para guardarlo: CREATE FUNCTION usar_plan_guardado() RETURNS trigger AS $$ if SD.has_key("plan"): plan = SD["plan"] # est el plan, lo reutilizo else: # no esta el plan, lo creo y almaceno en el diccionario persistente plan = plpy.prepare("SELECT 1") SD["plan"] = plan # continua la funcin... $$ LANGUAGE plpythonu;

Ejemplos Prcticos
Analizador de direcciones
En este ejemplo completo, se utiliza una funcin para analizar y desglozar una direccin ingresada como un texto (string), en tres campos: calle1 (principal), calle2 (transversal) y altura (n en la calle principal). Para ello creamos el tipo direccion para contener estos campos desglozados que devuelve nuestra funcin: CREATE TYPE direccion AS ( calle1 TEXT, calle2 TEXT, altura INTEGER ); Luego creamos la funcion analizar_dir con el cdigo python propiamente dicho: CREATE OR REPLACE FUNCTION analizar_dir(dir text) RETURNS direccion AS $BODY$ def is_numeric(x): return not [y for y in x if not '0'<=x<='9'] nombres = ["","",""] altura = "" ss = dir.strip() + " " calle = 0 # comienzo a procesar por la primer calle; n = p = "" for c in ss: if c in (' ', '.', ',', '/', '-'): # procesar separador: if n: if nombres[calle].strip().lower() in ('ruta', 'r.p.', 'rp', 'r.n.', 'rn'): # tomar el numero de ruta (nacional o provincial)

WikiPrint - from Polar Technologies

nombres[calle] += n + ' ' n = '' else: # tomar el numero como altura altura = n n = "" calle+=1 elif p.lower() in ('y', 'e', 'entre', 'u') or c in ('/', '-') or p[0:3]=='esq': # la palabra es separador de calles if nombres[calle]: # pasar a la siguiente calle calle+=1 elif p.lower() not in ('calle', 'avenida','al','n?','nro') and c not in ('.',) and (p or is_numeric(p)): # agregar la palabra como parte de esta calle nombres[calle] += p + " " p = "" elif '0' <= c <= '9': if calle==0 and nombres[calle]: # agregar el caracter como parte del numero n += c else: # agregar el caracter como parte de la palabra actual p += c else: # agrego el caracter a la palabra actual cc = c.lower() if ('a'<=cc<='z' or '0'<=cc<='9'): p += c; else: p += "?" # comodin de una letra por las dudas (si no es un caracter numerico o alfa) if calle>2: break # devolver las dos primeras calles (si hay) y la altura return nombres[0], nombres[1], altura and int(altura[-6:]) or None $BODY$ LANGUAGE 'plpythonu' IMMUTABLE; Para ejecutar la funcin, podemos simplemente llamarla desde la clausula from ya que devuelve un tipo de datos compuesto: select calle1, calle2, altura from analizar_dir('balcarce 50 esquina rivadavia') Lo que nos devolver: calle1 text balcarce calle2 text rivadavia altura integer 50

Tambin se la puede llamar desde una consulta analizando datos de una tabla (ej. campo lugar de novedades): SELECT (d.dir).calle1, (d.dir).calle2, (d.dir).altura FROM (SELECT analizar_dir(lugar) AS dir FROM novedades) d

Temas varios
Error con generadores y plpy.execute
Si se usa un generador (yield) para devolver filas (setof) y al mismo tiempo se usa plpy.execute: CREATE OR REPLACE FUNCTION mi_funcion2() RETURNS SETOF mi_tabla AS $BODY$ plpy.execute("select 1")

WikiPrint - from Polar Technologies

yield [None, None, None] $BODY$ LANGUAGE 'plpythonu' VOLATILE; SELECT * FROM mi_funcion2() Se producir un error: ********** Error ********** ERROR: error fetching next item from iterator SQL state: 22000 Utilizar return en lugar de yield

Ejemplo de Agregados
CREATE OR REPLACE FUNCTION py_disp() RETURNS trigger AS $$ plpy.notice("Campo 1 = %s" % TD["new"]["campo1"]) if TD["new"]["campo1"] == "": raise RuntimeError("El campo no puede ser vacio") $$ language plpythonu; create table tabla_prueba (campo1 text) CREATE TRIGGER disp_prueba BEFORE INSERT OR UPDATE OR DELETE ON tabla_prueba FOR EACH ROW EXECUTE PROCEDURE py_disp(); INSERT INTO tabla_prueba VALUES ('')

CREATE OR REPLACE FUNCTION first_agg(text, text) RETURNS text AS $BODY$ if not args[0]: return args[1] else: return args[0] $BODY$ LANGUAGE 'plpythonu' VOLATILE COST 100; CREATE select select UPDATE AGGREGATE first (text) ( sfunc = first_agg, stype = text ); * from part; price, first(pname) , sum(stock), count(pno) from part GROUP BY PRICE; part set price=15 where pno in (2, 4)

Ejemplo de Disparador
CREATE OR REPLACE FUNCTION py_disp() RETURNS trigger AS $$ plpy.notice("Campo 1 = %s" % TD["new"]["campo1"]) if TD["new"]["campo1"] == "": raise RuntimeError("El campo no puede ser vacio") $$ language plpythonu; create table tabla_prueba (campo1 text)

WikiPrint - from Polar Technologies

CREATE TRIGGER disp_prueba BEFORE INSERT OR UPDATE OR DELETE ON tabla_prueba FOR EACH ROW EXECUTE PROCEDURE py_disp(); INSERT INTO tabla_prueba VALUES ('hola'); INSERT INTO tabla_prueba VALUES ('');

You might also like