


Étendre un compilateur SQL multidialecte sans changer le sens des requêtes
Pour modifier un compilateur SQL qui transforme le SQL en AST puis le régénère pour d'autres bases. Il trouve d'abord la bonne couche, garde le sens entre dialectes, construit le SQL avec les outils de l'AST plutôt qu'avec des chaînes et teste l'exécution réelle et la vitesse.
Pour utiliser ces règles d'IA
## Trouver la couche du compilateur responsable du comportement avant de modifier
Considérez le système comme un compilateur dont les couches ont des responsabilités distinctes :
- le tokeniseur convertit le texte SQL en jetons typés et permet aux dialectes de remplacer les correspondances lexicales ;
- l’analyseur syntaxique utilise la descente récursive pour construire un arbre syntaxique abstrait (AST), à l’aide d’utilitaires dédiés pour la correspondance, le curseur, les listes et les enveloppes ;
- les classes d’expressions représentent les concepts sémantiques SQL et prennent en charge le parcours et la transformation ;
- le générateur reconvertit récursivement les expressions en SQL au moyen de correspondances de transformation simples ou de méthodes de génération dédiées ;
- les modules de dialectes étendent une base commune avec des indicateurs de fonctionnalités, des ensembles de jetons, des correspondances d’analyse et des correspondances de génération ;
- les règles d’optimisation normalisent et réécrivent l’AST dans un ordre délibéré ;
- les informations de schéma et de type prennent en charge l’optimisation, la transpilation et la traçabilité ;
- la traçabilité relie les colonnes de sortie aux expressions et tables en amont.
Repérez la couche responsable avant de modifier un comportement. Maintenez séparées la tokenisation, l’analyse syntaxique, la représentation sémantique, l’optimisation et la génération, au lieu de corriger une couche en faisant déborder sa logique dans une autre.
Préservez la symétrie entre l’analyseur syntaxique et le générateur : l’analyse associe les jetons aux expressions, tandis que la génération associe les expressions au SQL. Si l’analyseur se rabat intentionnellement sur un nœud de commande générique pour du texte non reconnu, ne transformez pas silencieusement ce mécanisme de préservation en analyse partielle.
## Utiliser les commandes de test et de vérification du dépôt, en version pure et native
Lisez les consignes de contribution et d’architecture du dépôt avant toute modification. Utilisez les cibles documentées pour l’installation de base, l’installation de développement, l’installation facultative de l’extension native, la configuration préalable aux commits, tous les tests, les tests unitaires, les contrôles de style, les contrôles de types et les bancs d’essai.
Distinguez l’implémentation en langage pur de toute extension compilée facultative. Exécutez d’abord le mode directement concerné, puis les contrôles plus larges requis par la modification. Lorsque l’accélération native est touchée, testez à la fois l’implémentation pure et le parcours compilé au lieu de supposer qu’ils se comportent de manière identique.
Ciblez précisément les échecs pendant les itérations en exécutant un fichier, une classe ou une méthode de test spécifique. Avant soumission, exécutez la cible complète de contrôles exigée par le dépôt, y compris le formatage, les contrôles statiques, les tests en environnement d’exécution pur et les tests de l’extension native lorsqu’ils s’appliquent.
Utilisez les bancs d’essai d’analyse et d’optimisation pour les travaux sensibles aux performances. Comparez des configurations équivalentes et ne sacrifiez pas la correction sémantique au profit d’un micro-banc d’essai favorable.
## Garder le même sens en passant le SQL dans un autre dialecte
Considérez l’AST comme la source de référence sémantique. Analysez le SQL source pour produire des expressions sémantiques, puis générez le dialecte cible à partir de ces expressions ; préservez le sens plutôt que la graphie source. Les commentaires peuvent être conservés dans la mesure du possible, mais l’équivalence sémantique est prioritaire.
Ajoutez à la base commune les fonctionnalités partagées par plusieurs dialectes uniquement lorsque leur sémantique correspond réellement. N’ajoutez la syntaxe ou le comportement propre à un dialecte qu’au dialecte concerné. Utilisez des indicateurs de fonctionnalités explicites, des ensembles de jetons réservés, des correspondances de fonctions ou d’instructions et des correspondances entre expressions et SQL, plutôt que des vérifications de noms dispersées.
Conservez l’ordre intentionnel des règles de l’optimiseur. La qualification, l’annotation des types, la descente des prédicats ou des projections et la simplification peuvent dépendre d’étapes antérieures et des informations de schéma. Considérez l’optimiseur comme un mécanisme de réécriture logique, et non comme un planificateur physique de requêtes.
Lorsqu’une fonctionnalité dépend de la structure de la base de données, fournissez explicitement les informations de schéma. N’affirmez pas que la qualification, l’inférence de types, l’optimisation ou la traçabilité des colonnes sont fiables si les métadonnées requises sur les tables et les colonnes sont absentes.
## Construire le SQL avec les outils de l'AST et des modèles, sans coller de chaînes
Avant de créer une expression de fonction générique, recherchez dans le catalogue d’expressions s’il existe déjà une classe dédiée. N’utilisez une expression anonyme générique que lorsqu’aucune classe sémantique dédiée n’existe.
Privilégiez le mécanisme de génération pris en charge le plus simple :
1. Pour un seul type d’expression complexe, implémentez la méthode de générateur `<lowercase_expression_name>_sql`, découverte automatiquement.
2. Réservez les correspondances de transformation aux renommages simples sur une ligne, aux fonctions lambda ou aux fonctions qui possèdent intentionnellement plusieurs points d’entrée.
3. Dans les méthodes du générateur, utilisez l’utilitaire de fonction du générateur au lieu de construire un nœud de fonction uniquement pour le sérialiser.
4. Utilisez les constructeurs d’expressions et les opérateurs pour les tableaux, les expressions booléennes, les alternatives conditionnelles, les conversions de types, les colonnes, les valeurs nulles, l’arithmétique, l’indexation et les comparaisons.
5. Pour une sortie complexe, analysez un modèle SQL et remplacez les espaces réservés dans une copie de l’AST.
N’assemblez pas de fragments SQL par interpolation de chaînes. Les constructeurs d’AST et les modèles préservent les guillemets, l’échappement, l’imbrication et le comportement propre aux dialectes, que les chaînes construites manuellement altèrent souvent.
## Vérifier les types après l'annotation des types et traiter NULL à l'exécution
Distinguez les vérifications de types syntaxiques et sémantiques. N’utilisez un prédicat littéral tel que `is_string` que pour vérifier si un nœud est littéralement une chaîne et en extraire la valeur littérale. Utilisez `is_type()` pour vérifier le type sémantique inféré d’une colonne, du résultat d’une fonction ou d’une autre expression.
Si le comportement sémantique dépend des types, exécutez l’annotation des types avant d’appeler `is_type()`. Les tests de transpilation dépendant des types doivent annoter l’AST ; sinon, une vérification sémantique correcte peut sembler échouer parce que le type n’a jamais été renseigné.
Utilisez la méthode de conversion de l’expression, telle que `to_py()`, pour extraire la valeur d’un littéral numérique après avoir vérifié que le nœud est bien un littéral numérique. Ne retirez pas manuellement les guillemets et ne convertissez pas directement le texte interne brut.
Ne résolvez pas le comportement de `NULL` en SQL au moment de la transpilation en recherchant uniquement un nœud nul littéral. Générez du SQL avec la logique d’exécution `IS NULL` afin que les colonnes, paramètres, expressions et valeurs nulles littérales suivent tous la même sémantique.
Lors d’une recherche parmi les ancêtres, incluez les limites de portée de la requête afin que la recherche ne puisse pas s’échapper dans une requête parente. Utilisez le mécanisme de déclaration des arguments non pris en charge du framework lorsqu’un dialecte cible ne peut pas représenter certains arguments.
## Tester chaque dialecte et lancer les tests sur de vraies bases de données
Ajoutez les tests à la suite responsable du dialecte ou de la couche du compilateur concernés. Utilisez des assertions d’aller-retour pour la syntaxe qui doit rester identique et des assertions inter-dialectes lorsqu’une même expression sémantique doit générer un SQL cible différent.
Ne dupliquez pas une couverture qui prouve déjà le même comportement. Ajoutez des cas ciblés pour la nouvelle distinction sémantique, notamment la nullabilité, les types, les guillemets, la portée, les arguments non pris en charge ou les différences entre dialectes lorsque cela est pertinent.
Assurez-vous que les tests placés dans des suites propres à une base de données s’exécutent réellement sur les bases correspondantes ou sur des moteurs compatibles. Une chaîne générée ne constitue pas à elle seule une preuve lorsque la règle dépend du comportement à l’exécution.
Pour une sortie sensible aux types, testez à la fois les littéraux et les expressions non littérales typées. Pour l’accélération native, confirmez la parité avec l’implémentation pure. Pour les modifications de l’optimiseur, incluez des cas tenant compte du schéma et vérifiez que l’ordre des règles ne modifie pas le sens.
## Limiter chaque changement à un seul but et le garder facile à relire
Limitez chaque modification à un objectif bien défini. Ajoutez des tests pour les comportements non triviaux, mettez à jour les chaînes de documentation publiques lorsque les API changent, suivez les conventions du dépôt relatives aux demandes d’intégration et aux commits, puis exécutez tous les contrôles requis avant soumission.
Supprimez les importations et variables inutilisées ainsi que le code mort. Évitez les couches de compatibilité pour du code supprimé intentionnellement. Préférez une expression en ligne lisible à une variable temporaire inutile, mais ne compressez pas la logique au point de rendre ses phases sémantiques peu claires.
N’utilisez des commentaires que pour une logique non évidente et n’ajoutez des chaînes de documentation que pour un comportement complexe ou des API publiques. Ne modifiez pas les fichiers de licence ni la configuration des licences, sauf si la tâche porte explicitement sur les licences.
Examinez la modification afin de détecter un couplage accidentel entre dialectes, des types d’expressions dupliqués, du SQL construit manuellement, une annotation de types manquante, une gestion de `NULL` limitée à la compilation, une fuite de portée et un comportement natif ou propre à une base de données non testé.
## Garder l'accélération native et la conception de l'analyseur en optimisant
Considérez les performances comme une fonctionnalité du produit, même lorsque l’implémentation principale utilise un langage de haut niveau. Préservez les parcours facultatifs d’accélération native et mesurez les modifications qui touchent la tokenisation, l’analyse, la génération ou l’optimisation.
Ne remplacez pas un analyseur intentionnel à descente récursive par une analyse de Pratt en supposant qu’il s’agit d’une optimisation. La stratégie d’analyse est un choix architectural, et non un réglage de vitesse interchangeable.
Évitez les abstractions connues pour perturber le parcours compilé lorsque la base de code documente une autre approche privilégiée, par exemple en choisissant des types union simples et un typage structurel par compatibilité de comportement plutôt qu’une abstraction de protocole non prise en charge.
N’optimisez qu’après avoir préservé la correction de l’analyse, de la construction de l’AST, de la génération, du comportement des dialectes et des tests. Une implémentation plus rapide qui modifie la sémantique, perd les guillemets, gère mal `NULL` ou diverge entre les modes pur et compilé constitue une régression.
