Article
· 6 hr il y a 5m de lecture

SQLAchemy-iris avec la dernière version du pilote Python

Après tant d'années d'attente, nous avons enfin un pilote officiel disponible sur Pypi

De plus, j'ai découvert que le pilote JDBC était enfin disponible sur Maven depuis déjà 3 mois,  et le pilote .Net driver - surNuget depuis plus d'un mois.

 La mise en œuvre de la DB-API et que les fonctions devraient au moins être définies par cette norme. La seule différence devrait se situer au niveau de SQL.

Et ce qui est intéressant dans l'utilisation de bibliothèques existantes, c'est qu'elles ont déjà mis en œuvre d'autres bases de données en utilisant le standard DB-API, et que ces bibliothèques s'attendent déjà à ce que le pilote fonctionne.

J'ai décidé de tester le pilote officiel d'InterSystems en mettant en œuvre son support dans la bibliothèque SQLAlchemy-iris.

executemany

Préparez une opération de base de données (requête ou commande) et exécutez-la en fonction de toutes les séquences de paramètres ou de mappages trouvées dans la séquence seq_of_parameters.

Cette fonction très utile permet d'insérer plusieurs lignes à la fois. Commençons par un exemple simple

import iris

host = "localhost"
port = 1972
namespace = "USER"
username = "_SYSTEM"
password = "SYS"
conn = iris.connect(
    host,
    port,
    namespace,
    username,
    password,
)

with conn.cursor() as cursor:
    cursor = conn.cursor()

    res = cursor.execute("DROP TABLE IF EXISTS test")
    res = cursor.execute(
        """
    CREATE TABLE test (
            id IDENTITY NOT NULL,
            value VARCHAR(50)
    ) WITH %CLASSPARAMETER ALLOWIDENTITYINSERT = 1
    """
    )

    cursor = conn.cursor()
    res = cursor.executemany(
        "INSERT INTO test (id, value) VALUES (?, ?)", [
            (1, 'val1'),
            (2, 'val2'),
            (3, 'val3'),
            (4, 'val4'),
        ]
    )

Cela fonctionne bien, mais que se passe-t-il s'il faut insérer une seule valeur par ligne.

    res = cursor.executemany(
        "INSERT INTO test (value) VALUES (?)", [
            ('val1', ),
            ('val2', ),
            ('val3', ),
            ('val4', ),
        ]
    )

Cela conduit malheureusement à une exception inattendue

RuntimeError: Cannot use list/tuple for single values (Impossible d'utiliser une liste/tuple pour des valeurs uniques)

Pour certaines raisons, une seule valeur par ligne est autorisée, et InterSystems demande d'utiliser une méthode différente

    res = cursor.executemany(
        "INSERT INTO test (value) VALUES (?)", [
            'val1',
            'val2',
            'val3',
            'val4',
        ]
    )

De cette façon, cela fonctionne bien

fetchone

Récupère la ligne suivante d'un ensemble de résultats de requête, en renvoyant une seule séquence, ou None lorsqu'il n'y a plus de données disponibles.

Un exemple simple sur sqlite

import sqlite3
con = sqlite3.connect(":memory:")

cur = con.cursor()
cur.execute("SELECT 1 one, 2 two")
onerow = cur.fetchone()
print('onerow', type(onerow), onerow)
cur.execute("SELECT 1 one, 2 two union all select '01' as one, '02' as two")
allrows = cur.fetchall()
print('allrows', type(allrows), allrows)

fournit

onerow <class 'tuple'> (1, 2)
allrows <class 'list'> [(1, 2), ('01', '02')]

Et avec le pilote InterSystems

import iris

con = iris.connect(
    hostname="localhost",
    port=1972,
    namespace="USER",
    username="_SYSTEM",
    password="SYS",
)

cur = con.cursor()
cur.execute("SELECT 1 one, 2 two")
onerow = cur.fetchone()
print("onerow", type(onerow), onerow)
cur.execute("SELECT 1 one, 2 two union all select '01' as one, '02' as two")
allrows = cur.fetchall()
print("allrows", type(allrows), allrows)

par certaines raisons fournit

onerow <class 'iris.dbapi.DataRow'> <iris.dbapi.DataRow object at 0x104ca4e10>
allrows <class 'tuple'> ((1, 2), ('01', '02'))

Qu'est-ce que DataRow, et pourquoi ne pas utiliser un tuple ou au moins une liste

Exceptions

SLa norme décrit une variété de classes d'exceptions que le pilote est censé utiliser, au cas où quelque chose ne fonctionnerait pas. Or, le pilote InterSystems ne les utilise pas du tout, se contentant de déclencher une erreur RunTime pour toute raison, ce qui, de toute façon, est contraire à la norme.

L'application peut s'appuyer sur le type d'exception qui se produit et se comporter en conséquence. Mais le pilote InterSystems ne fournit aucune différence. Par ailleurs, SQLCODE serait utile, mais il doit être extrait du message d'erreur

Conclusion

Au cours des tests, j'ai donc trouvé plusieurs bogues

  • Erreurs aléatoires survenant à tout moment <LIST ERROR> Format de liste incorrect, type non supporté pour IRISList; Détails : type détecté : 32
    • fonctionnent correctement, si vous réessayez juste après l'erreur
  • Des erreurs de segmentation ont été détectées, je ne sais même pas comment cela se produit
  • Résultat inattendu de la fonction fetchone
  • Fonctionnement inattendu de la fonction executemany, pour une seule ligne de valeur
  • Les exceptions ne sont pas du tout implémentées, des exceptions différentes devraient être générées en cas d'erreurs différentes, et les applications s'appuient sur ces exceptions
  • Python intégré peut être interrompu en cas d'installation à côté d'IRIS
    • en raison du même nom utilisé par Python intégré et ce pilote, il remplace ce qui est déjà installé avec IRIS et peut l'interrompre

 

SQLAlchemy-iris supporte maintenant le pilote officiel d'InterSystems, mais ceci en raison d'une incompatibilité avec Python intégré et de plusieurs bogues découverts lors des tests. Installation à l'aide de cette commande, avec l'option définie

pip install sqlalchemy-iris[intersystems]

Et pour une utilisation simple, l'URL devrait être iris+intersystems://

from sqlalchemy import Column, MetaData, Table
from sqlalchemy.sql.sqltypes import Integer, VARCHAR
from sqlalchemy import create_engine
from sqlalchemy.orm import DeclarativeBase


DATABASE_URL = "iris+intersystems://_SYSTEM:SYS@localhost:1972/USER"
engine = create_engine(DATABASE_URL, echo=True)

# Create a table metadata
metadata = MetaData()


class Base(DeclarativeBase):
    pass
def main():
    demo_table = Table(
        "demo_table",
        metadata,
        Column("id", Integer, primary_key=True, autoincrement=True),
        Column("value", VARCHAR(50)),
    )

    demo_table.drop(engine, checkfirst=True)
    demo_table.create(engine, checkfirst=True)
    with engine.connect() as conn:
        conn.execute(
            demo_table.insert(),
            [
                {"id": 1, "value": "Test"},
                {"id": 2, "value": "More"},
            ],
        )
        conn.commit()
        result = conn.execute(demo_table.select()).fetchall()
        print("result", result)


main()

En raison de bogues dans le pilote InterSystems, certaines fonctionnalités peuvent ne pas fonctionner comme prévu. J'espère que cela sera corrigé à l'avenir

Discussion (0)0
Connectez-vous ou inscrivez-vous pour continuer