Sous-sections

Python et MaxDB


Matériel et méthode

Récupérer les binaires récents ici

Personnellement j'ai décompressé l'archive dans un dossier créé pour l'occasion, sous /usr/local/sapdb/interfaces/python et, par conséquent réglé mon PYTHONPATH ainsi:
export PYTHONPATH=".:/usr/lib/python2.2:$SAPDB/interfaces/python"

J'ai récupéré une base d'adresses courriel qui tourne sous BerkeleyDB et j'ai chargé une table sous MaxDB avec, et j'utilise l'API Python pour m'y connecter et effectuer différentes opérations dessus. La table s'appelle ADRESSES, les deux champs "nom" et "adresse". J'ai choisi d'utiliser l'API car on peut considérer que c'est plus "portable" d'un SGBD à un autre.

On trouve les spécifications des méthodes de l'API dans le fichier sapdb/dbapi.py du dossier principal cité ci-dessus. Car c'est l'API Python que j'utilise, mais MaxDB propose un module sapdb.sql qui a l'air sympa aussi bien documenté dans la doc téléchargeable (voir 1 pour les liens)

Les spécifications de la DB API Python peuvent se lire ici

Ce ne sera pas précisé dans les lignes qui suivent mais il faut les imports suivants:

import sapdb.dbapi  # api python 2.0
import sapdb.sql    # pour les erreurs

Connexion

Une ligne suffit:
session = sapdb.dbapi.connect (user, password, database, host)

Gaffe à la casse sur le nom de l'utilisateur. L'utilisation de string.upper () fait des miracles...

On peu passer d'autres paramètres de connexion au constructeur:
session = sapdb.dbapi.connect (user, password, database, host,isolation='15')

Jeter un oeil à la documentation fournie avec MaxDB (Interfaces, Python module, Method connect)

On ferme la session en appelant sa méthode close ():
session.close ()

Exécution d'instructions

On doit d'abord ouvrir un curseur:
curseur = session.cursor ()

Puis envoyer l'ordre à exécuter. Il est possible d'envoyer une seule instruction, ou une série.

On fermera le curseur avec sa méthode close ().

Envoi d'une instruction

Voici deux manières possibles pour envoyer une instruction simple:

Envoi d'une série d'instructions

curseur = self.cursor ()
try:
    curseur.executemany ("CALL pc_insert(?, ?)", loadList)
except sapdb.sql.SQLError, e:
    print 'Erreur SQL (position %s): %s' %  (e.errorPos, e.message)
self.commit ()
curseur.close ()
D'une pierre, deux coups, on voit là qu'on peut demander l'exécution d'une procédure (voir cette procédure à 5.5) avec la méthode execute (). La variable loadList que je passe en paramètre à executemany () est une séquence (liste) de tuples (paires nom, adresse) qui sont envoyés successivement à la procédure stockée.

Appel de procédure

On appelle la méthode callproc () du curseur:
curseur.callproc ('pc_insert', paireArgs)
paireArgs est une paire de variables (nom, adresse).

Récupérer un résultat

Il y a trois méthodes pour la récupération d'un résultat:

Les noms indiquent véritablement ce que font les méthodes donc on pourra récupérer un résultat (on supposera un curseur ouvert) avec:

fp = file (fichier, "w")
resultat = curseur.fetchall ()
for tuple in resultat:
   fp.write (tuple[1])
   fp.write ('\t')
   fp.write (tuple[0])
   fp.write ('\n')
Ce qui écrira dans le fichier ouvert avec file () dont on aura obtenu fp.

Ou bien:

fp = file (fichier, "w")
while 1:
   row = curseur.fetchone ()
   if row == None:
       break
   fp.write (row[1])
   fp.write ('\t')
   fp.write (row[0])
   fp.write ('\n')

fetchone () va chercher plusieurs enregistrements par passe, donc les performances devraient être équivalentes à fetchmany ().

Mise à jour avec curseur

Pas vraiment dbapi comme méthode puisque on va attaquer la couche inférieure sur laquelle s'appuie le module dbapi. Mais c'est intéressant à voir:
curs1 = session.cursor ()
curs1.execute ("SELECT * FROM PRODUCTEUR FOR UPDATE OF NOMPRODUCTEUR,TAILLEPRODUCTEUR")
originel = curs1._Cursor__cursor
originel.setFetchSize (1)
nomCurseur = originel.cursorName ()
originel.first ()
curs2 = session.cursor ()
curs2.execute ("UPDATE PRODUCTEUR \
                SET NOMPRODUCTEUR='%s', TAILLEPRODUCTEUR=%.3f \
                WHERE CURRENT OF %s" % \
                (nom,taille,nomCurseur))
Alors, comment ça marche ? Le premier curseur (curs1) est un objet de classe Cursor. A l'exécution de sa méthode execute(), l'implémentation de la dbapi génère un objet de classe SapDB_ResultSet, qui est l'attribut __cursor du curseur. Étant un attribut privé au sens de python, on l'adresse avec une syntaxe _Cursor__cursor. En fait les méthodes setFetchSize() et cursorName() ne sont pas (encore ?) documentées dans la doc MaxDB mais peuvent être atteintes par:
dir(orig)
orig.cursorName.__doc__
Il faut obligatoirement fixer la taille du déplacement du curseur originel à 1 par setFetchSize() ou l'attribut fetchSize du curseur de résultat.
Ensuite il s'agit de positionner le curseur avant de faire la mise à jour. Dans l'exemple on ne le déplace que d'un seul enregistrement, avec la méthode first() d'un objet SapDB_ResultSet.
On doit utiliser un nouveau curseur pour faire la mise à jour, sous peine d'écraser les précédents curseurs.

Conversions

Le module dbapi permet de faire des conversions à la volée à l'aide d'un dictionnaire, mais cette possibilité ne semble pas documentée dans la Python Database API Specification.

Voilà comment on peut procéder, pour traduire une date en format français (le format de date original de SapDB est INTERNAL, c'est à dire AAAAMMJJ).

# on implémente une fonction traductrice
def dateUS2FR (sapdbdate):
    return "%s-%s-%s" % (str (sapdbDate[-2:]), str (sapdbDate[4:6]), str (sapdbDate[0:4]))

# on définit un dictionnaire traducteur pour le type sql DATE
traducteur = {'Date':dateUS2FR}

# on initialise le traducteur, par une méthode de l'objet Connection
session.setTypeTranslation (traducteur)

Et puis c'est tout. Les dates seront formatées selon la fonction de conversion. On peut définir plusieurs fonctions dans le dictionnaire. Voir le code du module pour ça.

Mais bon, on peut implémenter une fonction sous MaxDB (version supérieure à 7.5 donc) pour le faire: voir 5.6

Remarques

jean-michel OLTRA 2004-07-06