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
.png)
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(<span class="hljs-string">"DROP TABLE IF EXISTS test"</span>)
res = cursor.execute(
<span class="hljs-string">"""
CREATE TABLE test (
id IDENTITY NOT NULL,
value VARCHAR(50)
) WITH %CLASSPARAMETER ALLOWIDENTITYINSERT = 1
"""</span>
)
cursor = conn.cursor()
res = cursor.executemany(
<span class="hljs-string">"INSERT INTO test (id, value) VALUES (?, ?)"</span>, [
(<span class="hljs-number">1</span>, <span class="hljs-string">'val1'</span>),
(<span class="hljs-number">2</span>, <span class="hljs-string">'val2'</span>),
(<span class="hljs-number">3</span>, <span class="hljs-string">'val3'</span>),
(<span class="hljs-number">4</span>, <span class="hljs-string">'val4'</span>),
]
)</code></pre>
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
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()
classBase(DeclarativeBase):passdefmain():
demo_table = Table(
"demo_table",
metadata,
Column("id", Integer, primary_key=True, autoincrement=True),
Column("value", VARCHAR(50)),
)
demo_table.drop(engine, checkfirst=<span class="hljs-keyword">True</span>)
demo_table.create(engine, checkfirst=<span class="hljs-keyword">True</span>)
<span class="hljs-keyword">with</span> engine.connect() <span class="hljs-keyword">as</span> conn:
conn.execute(
demo_table.insert(),
[
{<span class="hljs-string">"id"</span>: <span class="hljs-number">1</span>, <span class="hljs-string">"value"</span>: <span class="hljs-string">"Test"</span>},
{<span class="hljs-string">"id"</span>: <span class="hljs-number">2</span>, <span class="hljs-string">"value"</span>: <span class="hljs-string">"More"</span>},
],
)
conn.commit()
result = conn.execute(demo_table.select()).fetchall()
print(<span class="hljs-string">"result"</span>, 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