IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

43.7. Accès à la base de données

Le module du langage PL/Python importe automatiquement un module Python appelé plpy. Les fonctions et constantes de ce module vous sont accessibles dans le code Python via plpy.foo.

43.7.1. Fonctions d'accès à la base de données

Le module plpy fournit plusieurs fonctions pour exécuter des commandes sur la base de données :

plpy.execute(query [, max-rows])

L'appel à plpy.execute avec une chaîne pour la requête et une limite de ligne optionnelle permet d'exécuter la requête et de retourner le résultat dans un objet résultant.

L'objet resultant émule une liste ou un objet dictionnaire. L'objet résultant peut être accédé par le numéro de ligne et le nom de colonne. Par exemple :

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

retourne jusqu'à 5 lignes de my_table. Si my_table possède une colonne my_column, elle pourra être accédée ainsi :

 foo = rv[i]["my_column"]
 

Le nombre de lignes retournées peut être obtenu en utilisant la fonction intégrée len.

L'objet résultant contient ces méthodes additionnelles :

nrows()

Retourne le nombre de lignes traitées par cette commande. Notez que cela n'est pas nécessairement identique au nombre de lignes retournées. Par exemple, une commande UPDATE fixera cette valeur mais ne retournera aucune ligne (sauf si RETURNING est utilisé).

status()

La valeur retournée par SPI_execute().

colnames(), coltypes(), coltypmods()

Retourne respectivement une liste de noms de colonne, une liste de type OID de colonne et une liste de type de modifieurs spécifiques à un type pour les colonnes.

Ces méthodes lèvent une exception quand elles sont appelées sur un objet résultant d'une commande n'ayant pas produit d'ensemble de résultat, par ex, UPDATE sans RETURNING, ou DROP TABLE. Il est cependant normal d'utiliser ces méthodes sur un ensemble de résultat ne contenant aucune ligne.

__str__()

La méthode standard __str__ est définie pour qu'il soit possible de débugger les résultats de l'exécution d'une requête en utilisant plpy.debug(rv).

L'objet résultant peut être modifié.

Notez que l'appel à plpy.execute provoquera la lecture de tout l'ensemble de résultat en mémoire. N'utilisez cette fonction que lorsque vous êtes surs que l'ensemble de résultat sera relativement petit. Si vous ne voulez pas risquer une utilisation excessive de mémoire pour récupérer de gros ensembles, préférez plpy.cursor à plpy.execute.

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

plpy.prepare prépare le plan d'exécution pour une requête. Il faut l'appeler avec une chaîne contenant la requête et une liste de types de paramètres, si vous avez des références à des paramètres dans cette requête. Par exemple :

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

text est le type de variable qui sera passé à $1. Le second paramètre est optionel si vous ne voulez pas fournir de paramètre à la requête.

Après avoir préparé une requête, il faut utiliser une variante de la fonction plpy.execute pour l'exécuter :

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

Il faut fournir le plan comme premier argument (à la place de la chaîne), et une liste de valeurs à substituer dans la requête comme second argument. Le deuxième argument est optionnel si la requête n'attend pas de paramètre. Le troisième argument est la limite de ligne optionnelle comme auparavant.

Les paramètres de requête ainsi que les champs des lignes de résultat sont converties entre les types de données de PostgreSQL et de Python comme décrit dans Section 43.3, « Valeur des données avec PL/Python ». Il y a une exception pour les types composites qui ne sont pour le moment pas supportés : ils seront rejetés comme paramètre de requête et convertis en chaînes de caractères s'ils apparaissent dans le résultat d'une requête. Pour contourner le dernier problème, la requête peut parfois être réécrite pour que le type composite résultant apparaisse comme une ligne plutôt qu'un champ dans la ligne résultante. Sinon, la chaîne résultante peut être analysée séparément à la main, mais cette approche n'est pas recommendée car elle n'est pas à l'abri d'un changement ultérieur.

Quand un plan est préparé en utilisant le module PL/Python, il est automatiquement sauvegardé. Voir la documentation de SPI (Chapitre 44, Interface de programmation serveur) pour une description de ce que cela signifie. Afin d'utiliser efficacement ces appels de fonction, il faut utiliser un des dictionnaires de stockage persistant SD ou GD (voir Section 43.4, « Sharing Data »). Par exemple :

 CREATE FUNCTION usesavedplan() RETURNS trigger AS $$
    plan = SD.setdefault("plan", plpy.prepare("SELECT 1"))
     # reste de la fonction
 $$ LANGUAGE plpythonu;
 
plpy.cursor(query), plpy.cursor(plan [, arguments])

La fonction plpy.cursor accepte les mêmes arguments que plpy.execute (à l'exception de la limite de lignes) et retourne un objet curseur, qui permet de traiter de gros ensembles de résultats en plus petits morceaux. Comme avec plpy.execute, une chaîne de caractère ou un objet plan accompagné d'une liste d'arguments peuvent être utilisés.

L'objet curseur fournit une méthode fetch qui requiert en entrée un paramètre entier et retourne un objet résultat. À chaque appel de fetch, l'objet retourné contiendra la prochaine série de lignes, mais jamais plus que la valeur passée en paramètre. Une fois que toutes les lignes ont été épuisées, fetch se met à retourner des objets résultat vides. Les objets curseurs fournissent également une interface d'itérateur, fournissant les lignes une par une jusqu'à épuisement. Les données récupérées de cette façon ne sont pas retournées dans des objets résultat, mais plutôt dans des dictionnaires, chacun correspondant à une unique ligne de résultat.

Un exemple montrant deux façons de traiter des données dans une large table est:

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]))

return len(rows)
$$ LANGUAGE plpythonu;

Les curseurs sont automatiquement libérés. Mais si vous voulez libérer explicitement toutes les ressources retenues par un curseur, il faut utiliser la méthode close. Une fois fermé, un curseur ne peut plus être utilisé pour retourner des lignes.

[Astuce]

Astuce

Il ne faut pas confondre les objets créés par plpy.cursor avec les curseurs DB-API comme définis par la spécification Python Database API. Ils n'ont rien en commun si ce n'est le nom.

43.7.2. Récupérer les erreurs

Les fonctions accédant à la base de données peuvent rencontrer des erreurs, qui forceront leur annulation et lèveront une exception. plpy.execute et plpy.prepare peuvent lancer une instance d'une sous-classe de plpy.SPIError, qui terminera par défaut la fonction. Cette erreur peut être gérée comme toutes les autres exceptions Python, en utilisant la construction try/except. Par exemple :

CREATE FUNCTION essaie_ajout_joe() RETURNS text AS $$
    try:
        plpy.execute("INSERT INTO utilisateurs(nom) VALUES ('joe')")
    except plpy.SPIError:
        return "quelque chose de mal est arrivé"
    else:
        return "Joe ajouté"
$$ LANGUAGE plpythonu;

La classe réelle de l'exception levée correspond à la condition spécifique qui a causé l'erreur. Référez-vous à Tableau A.1, « Codes d'erreur de PostgreSQL™ » pour une liste des conditions possibles. Le module plpy.spiexceptions définit une classe d'exception pour chaque condition PostgreSQL™, dérivant leur noms du nom de la condition. Par exemple, division_by_zero devient DivisionByZero, unique_violation devient UniqueViolation, fdw_error devient FdwError, et ainsi de suite. Chacune de ces classes d'exception hérite de SPIError. Cette séparation rend plus simple la gestion des erreurs spécifiques. Par exemple :

CREATE FUNCTION insere_fraction(numerateur int, denominateur int) RETURNS text AS $$
from plpy import spiexceptions
try:
    plan = plpy.prepare("INSERT INTO fractions (frac) VALUES ($1 / $2)", ["int", "int"])
    plpy.execute(plan, [numerateur, denominateur])
except spiexceptions.DivisionByZero:
    return "denominateur doit être différent de zéro"
except spiexceptions.UniqueViolation:
    return "a déjà cette fraction"
except plpy.SPIError, e:
    return "autre erreur, SQLSTATE %s" % e.sqlstate
else:
    return "fraction insérée"
$$ LANGUAGE plpythonu;

Notez que, comme toutes les exceptions du module plpy.spiexceptions héritent de SPIError, une clause except la gérant récupèrera toutes les erreurs d'accès aux bases.

Comme alternative à la gestion des différentes conditions d'erreur, vous pouvez récupérer l'exception SPIError et déterminer la condition d'erreur spécifique dans le bloc except en recherchant l'attribut sqlstate de l'objet exception. Cet attribut est une chaîne contenant le code d'erreur « SQLSTATE ». Cette approche fournit approximativement la même fonctionnalité.