QoreDB LogoQoreDB
Retour au blog
ProduitJournal technique

Le multi-statement execution dans QoreDB : exécuter un script SQL complet

Un développeur ouvre un fichier .sql qui contient cinq instructions séparées par un point-virgule. Il colle le contenu dans l'éditeur et lance l'exécution. Ce cas d'usage paraît anodin, mais il expose plusieurs choix techniques qui divergent d'un client à l'autre : envoyer le…

Raphaël – Creator of QoreDBRaphaël – Creator of QoreDB
5 min de lecture
Le multi-statement execution dans QoreDB : exécuter un script SQL complet

Un développeur ouvre un fichier .sql qui contient cinq instructions séparées par un point-virgule. Il colle le contenu dans l'éditeur et lance l'exécution. Ce cas d'usage paraît anodin, mais il expose plusieurs choix techniques qui divergent d'un client à l'autre : envoyer le script tel quel au moteur, le découper côté client, préserver ou reformater le texte, restituer un ou plusieurs résultats. QoreDB a tranché ces questions de manière assumée, en cherchant à préserver la sémantique de chaque moteur sans imposer un modèle intermédiaire.

Découper côté client plutôt qu'envoyer un buffer

Envoyer un buffer multi-instructions directement à un driver serait la solution la plus simple, mais elle a un coût. Les protocoles réagissent différemment : PostgreSQL en mode simple query accepte plusieurs instructions et renvoie plusieurs résultats, mais le mode extended query utilisé par la plupart des pilotes ne l'autorise pas. MySQL exige que la connexion soit ouverte avec le flag CLIENT_MULTI_STATEMENTS. SQL Server les accepte, mais l'attribution des rowsets aux instructions dépend du pilote. Restituer proprement chaque résultat à l'interface impose alors de connaître le nombre exact d'instructions envoyées.

QoreDB adopte une position intermédiaire : découper le script côté client avec sqlparser, puis exécuter chaque instruction séparément dans la même session. La fonction split_sql_statements du crate qore-sql parse le buffer selon le dialecte du driver et renvoie un vecteur de chaînes. Cette approche garantit qu'un point-virgule dans une chaîne littérale ou dans un commentaire ne sera jamais confondu avec un séparateur d'instructions.

Le rôle du parser et son cache

Le découpage repose sur le crate sqlparser, instancié avec le dialecte correspondant au driver actif. Chaque instruction est ensuite reconstituée via to_string, ce qui reformate légèrement le texte. C'est un compromis assumé pour le chemin usuel : la précision du découpage prime sur la préservation exacte des caractères. Les commentaires isolés hors instructions disparaissent, l'indentation peut varier, mais l'exécution reste fidèle.

Parser un buffer à chaque exécution serait coûteux pour un éditeur interactif où l'utilisateur re-soumet fréquemment la même requête. Le crate maintient donc un cache LRU indexé par (driver_id, sql). Les résultats du parsing sont mémorisés, ce qui rend le split quasi gratuit après le premier appel.

Le cas ClickHouse et le chemin migration

ClickHouse ne passe pas par sqlparser. Son SQL contient des constructions spécifiques (moteurs de table, expressions de partitionnement, syntaxes de type) que le parser ne round-tripe pas fidèlement. QoreDB utilise à la place split_ch_statements, un walker de caractères qui coupe sur les points-virgules de plus haut niveau tout en respectant les chaînes simples et doubles, les commentaires inline et les blocs. C'est plus léger et plus sûr que d'imposer un parser inadapté au dialecte.

Il existe un second chemin de découpage, distinct, dédié aux migrations. Le crate qore-sql expose migration_split, qui renvoie des slices empruntés au buffer d'origine plutôt que des chaînes reconstituées. Une migration doit atteindre la base exactement telle qu'elle a été écrite : commentaires, casse, indentation, tout est conservé. Ce splitter rejette les constructions qu'il ne peut pas découper avec certitude (dollar-quoting non terminé, DELIMITER MySQL, blocs procéduraux ambigus) plutôt que de deviner. Les deux chemins coexistent parce que leurs contrats sont incompatibles : l'un privilégie la précision du parsing, l'autre la fidélité byte-à-byte.

Exécution séquentielle et propagation d'erreur

Une fois le buffer découpé, la couche qore-service itère sur le vecteur d'instructions et les exécute une par une dans la même session, via execute_in_namespace. Chaque appel produit un QueryResult indépendant, accumulé dans une liste. Le fait de rester dans une session unique préserve les variables locales, les transactions ouvertes et le search_path.

La propagation d'erreur est explicite. Si l'instruction numéro 3 échoue après que 2 aient réussi, le service remonte un message qui indique clairement le rang de l'instruction fautive et le nombre de succès qui la précèdent. Le développeur sait quelles opérations ont été validées et peut décider de continuer manuellement ou de reprendre le script après correction. QoreDB n'ouvre pas de transaction implicite autour du script : c'est à l'utilisateur d'écrire BEGIN et COMMIT s'il souhaite l'atomicité.

Restitution des résultats côté interface

La commande Tauri renvoie un objet dont le premier résultat est result et les suivants sont regroupés dans extra_results. Le composant QueryPanel crée un onglet par jeu de résultats. Le libellé de chaque onglet reprend les premiers caractères de l'instruction correspondante quand le nombre d'onglets colle avec le split effectué côté client, sinon le texte complet est utilisé. Les métriques (durée, nombre de lignes) sont calculées par onglet.

Un bandeau apparaît en haut du panneau de résultats quand plusieurs instructions sont détectées, pour signaler que l'onglet actif par défaut reste le dernier. Ce choix correspond au comportement attendu pour un script (le SELECT final) mais les jeux de résultats intermédiaires sont bien conservés et accessibles via les onglets.

Ce que ce choix donne à l'usage

Concrètement, un développeur peut coller un script de seed, un ensemble d'ALTER TABLE ou une série de SELECT diagnostiques et obtenir un résultat exploitable pour chacun. La cohérence des règles de sécurité est préservée : chaque instruction passe individuellement par le pipeline d'interception, les mutations sont détectées statement par statement, l'environnement Prod applique ses garde-fous à chaque appel. Le rate limiter voit N exécutions et non une seule, ce qui reflète le coût réel envoyé au moteur.

Le mécanisme est conçu pour un usage desktop mono-utilisateur : un script humain, tapé ou collé, qui compte de quelques instructions à quelques dizaines. Pour les migrations versionnées, le chemin dédié préserve le texte exact et remonte des erreurs de découpage précises. Ces deux chemins couvrent la quasi-totalité des besoins réels d'un client de base de données, sans rien émuler et sans fabriquer une couche d'abstraction qui masquerait ce que le moteur reçoit.

Newsletter

Restez informé des nouveautés

Rejoignez notre newsletter pour recevoir les mises à jour majeures, les nouveaux drivers et nos coulisses techniques.

🎁 Bonus : Recevez notre fiche mémo d'optimisation SQL — 9 pages, PostgreSQL / MySQL / SQLite (PDF) !
Partager
Le multi-statement execution dans QoreDB : exécuter un script SQL complet - Blog - QoreDB