3
votes

Accélérer l'expression régulière

Ceci est une expression régulière pour extraire le nom de la table d'une instruction SQL:

(?:\sFROM\s|\sINTO\s|\sNEXTVAL[\s\W]*|^UPDATE\s|\sJOIN\s)[\s`'"]*([\w\.-_]+)

Il correspond à un jeton, éventuellement inclus dans [`'"] , précédé par FROM etc. entouré d'espaces, à l'exception de UPDATE qui n'a pas d'espace au début.

Nous exécutons de nombreuses expressions régulières, et c'est la plus lente, et je ne sais pas pourquoi. Les chaînes SQL peuvent aller jusqu'à 4k de taille, et le temps d'exécution est au pire de 0,35ms sur un MBP i7 à 2,2 GHz.

Ceci est un exemple d'entrée lente: https://pastebin.com/DnamKDPf

Pouvons-nous faire mieux? Le fractionner en plusieurs expressions régulières serait une option, ainsi que si l'alternance est un problème.


6 commentaires

Pouvez-vous fournir une entrée / sortie attendue?


J'ai ajouté un exemple.


Certaines chaînes SQL sont tronquées car elles sont trop longues, puis elles n'ont pas du tout de FROM table . Mais il est très probable qu'ils soient proches de la fin de la chaîne s'ils sont particulièrement longs, en raison d'une énorme liste de définitions de colonnes générées automatiquement au début.


Je suggère de réduire le modèle en (?: \ S (? :( ?: FROM | INTO | JOIN) \ s | NEXTVAL) | ^ UPDATE \ s) \ W * ([\ w ._-] + ‌) , voir regex101.com/r/CC2oDa/1 ( 12471 contre 22976 étapes)


Ne devrait pas affecter la vitesse, mais [\ s \ W] est identique à \ W , et j'espère que vous connaissez [\ w \.-_] comprend toutes les lettres, tous les chiffres et ./:;<=>?@[\\ ^_ .


Ok, personne ne le mentionne, mais la règle d'optimisation des regex consiste à éviter que les modèles ultérieurs quantifiés indéfiniment correspondent aux mêmes caractères . En ce qui concerne l'alternance, * assurez-vous que les alternatives ne correspondent pas au même texte au même endroit (c'est-à-dire évitez (\ sLl \ s | \ sWw \ s) , utilisez \ s ( Ll | Ww) \ s )


3 Réponses :


1
votes

L'optimisation

Regex est un sujet très complexe et doit être réalisée à l'aide de certains outils. Par exemple, j'aime Regex101 qui calcule pour nous le nombre d'étapes que le moteur Regex a dû faire pour faire correspondre le motif à la charge utile . Pour votre modèle et l'exemple donné, il imprime:

1 match, 15891 steps (~13ms)

La première chose que vous pouvez toujours faire est de regrouper des parties similaires dans un groupe. Par exemple, FROM , INTO et JOIN se ressemblent, nous pouvons donc écrire regex comme ci-dessous:

(?:\s(?:FROM|INTO|JOIN)\s|\sNEXTVAL[\s\W]*|^UPDATE\s)[\s`'"]*([\w\.-_]+)


2 commentaires

Je l'ai couru contre un gros corpus d'instructions SQL, le regex est parfaitement équivalent mais malheureusement un peu plus lent en moyenne (0,177 vs 0,179).


Cette partie de regex ne compte probablement pas autant lorsqu'elle est testée avec un grand ensemble d'instructions SQL. Néanmoins ma réponse n'est pas une solution définitive mais plutôt un processus pour trouver la meilleure solution.



1
votes

Étant donné que les correspondances sont souvent proches de la fin, une possibilité serait de commencer essentiellement à la fin et de revenir en arrière, plutôt que de commencer par le début et la poursuite, quelque chose du genre

^(?:UPDATE\s|.*(?:\s(?:(?:FROM|INTO|JOIN)\s|NEXTVAL[\s\W]*)))[\s`'\"]*([\w\.-_]+)

https://regex101.com/r/SO7M87/1/ (154 étapes)

Bien que cela puisse être beaucoup plus rapide lorsqu'une correspondance existe, ce n'est qu'une amélioration modérée lorsqu'il n'y a pas de correspondance, car le modèle doit revenir en arrière jusqu'au début ( ~ 9000 étapes de ~ 23 000 étapes)


4 commentaires

Je ne connais pas la syntaxe SQL moi-même, donc je ne connais pas les pièges ou autres possibilités qu'un tel modèle doit tenir compte, mais il peut bien y avoir un joli modèle impliquant (?: \ S + AS \ S +,) * < / code> ou quelque chose comme ça, pour consommer les énormes colonnes au début


C'est déjà plus rapide, mais ce n'est pas tout à fait équivalent car l'ordre des correspondances a changé. Un exemple le comparant à l'ancienne regex, où il correspondait à JOIN au lieu de FROM: SELECT * FROM order_refactor o LEFT JOIN order_data od ON o.id = od.order_id WHERE o.id =: id Attendu: "order_refactor "mais: était" order_data "


J'ai mis au banc "consommer les énormes colonnes", mais c'était un peu plus lent.


Êtes-vous certain de la performance sur les différentes piqûres d'entrée?



1
votes

Il existe une règle de base :

Ne laissez pas le moteur tenter de faire correspondre chaque caractère s'il y a des limites.

Essayez l'expression régulière suivante (~ 2500 étapes sur la chaîne d'entrée donnée):

(?!(?:FROM|INTO|NEXTVAL|UPDATE|JOIN)\b)\S*\s*|\b(?:NEXTVAL\W*|\w+\s[\s`'"]*)([\[\]\w\.-]+)

Démo en direct

Remarque : ce dont vous avez besoin est dans le premier groupe de capture.

La regex finale selon les commentaires (qui est un peu plus lente que la précédente clean):

(?!FROM|INTO|NEXTVAL|UPDATE|JOIN)\S*\s*|\w+\W*(\w[\w\.-]*)


20 commentaires

Celui-ci est très rapide, mais ... ne fonctionne pas? Il y a plus de 200 groupes de match, le premier contient juste "select".


Veuillez noter la dernière phrase Ce dont vous avez besoin est dans le premier groupe de capture . Vous devez parcourir les valeurs du groupe de capture 1.


Je vois que vous avez changé l'expression régulière de (?! FROM | INTO | NEXTVAL | UPDATE | JOIN) \ S * \ s * | \ w + \ W * (\ w [\ w \.-_] *) à (?: \ s (? :( ?: FROM | INTO | JOIN) \ s | NEXTVAL) | ^ UPDATE \ s) \ W * ([\ w ._-] + ‌) < / code>. Je vais essayer.


Ok, c'est environ deux fois plus rapide en moyenne - bien! Cependant, ne gère pas bien les sous-sélections. Exemple: select * from (select distinct materialwa0_.id as col_0_0_ from PDMBOM2.materialways materialwa0_ where materialwa0_.name =? And materialwa0_.revision =?) Where rownum <=? Attendu: "PDMBOM2.materialways" mais: était "sélectionné"


@ MichaelBöckling J'ai fait un changement sur le côté droit du tuyau. Essayez plutôt ceci regex101.com/r/MxQRPu/2


Celui-là encore ne fonctionne pas. Je vois cette expression régulière: (?! FROM | INTO | NEXTVAL | UPDATE | JOIN) \ S * \ s * | \ w + \ s [\ s '"] * (\ w [\ w \ .‌ -_] *) `


@ MichaelBöckling Voyez-vous ces chaînes en vert? Cela confirme qu'il correspond aux bons morceaux.


Peut-être que c'est une différence entre Java et PCRE? Parce que matcher.find () ne renvoie pas true.


@ MichaelBöckling J'ai fait une démo Java en direct. Veuillez le voir ici ideone.com/BnJKHD


Merci beaucoup! Je vérifierai.


Cela semble plutôt bien. Deux régressions que je n'arrive pas à comprendre: SELECT mis à jour FROM ticket_Ticket WHERE fk_customer =? ORDER BY a mis à jour DESC LIMIT 1 Attendu: "ticket_Ticket" mais: était "FROM" et SELECT nextval ('wsid_sequence') comme next_value Attendu: "wsid_sequence" mais: était nul


@ MichaelBöckling Oui, je m'y attendais. Selon votre regex, cela n'aurait plus besoin de patch. Veuillez vérifier la mise à jour ici regex101.com/r/MxQRPu/4


Cela a fonctionné, merci! Maintenant, il ne reste plus que deux cas. J'ai essayé de résoudre le problème, mais je n'ai pas pu le résoudre: pastebin.com/T93Pehfw


@ MichaelBöckling Vous ajoutez à vos exigences. Si vous continuez à le faire, ce sera sans fin.


@ MichaelBöckling Dans la première requête, vous devez vous attendre à ce que category_balance et category ne soient pas category_balance seuls et que l'expression régulière renvoie à la fois regex101.com/r/MxQRPu/5


@ MichaelBöckling J'ai inclus des crochets pour la classe de caractères. Voici le regex101.com/r/MxQRPu/6


Je suis désolé si j'en ai trop demandé, j'apprécie votre aide!


@ MichaelBöckling Je suis heureux de répondre aux questions, mais connaître les exigences aussi clairement que possible est fondamentalement une chose importante. J'ai mis à jour la réponse pour refléter les changements.


J'ai couru l'ancienne regex et la nouvelle regex contre quelques milliers de SQL et comparé leurs correspondances - la "spécification" est essentiellement l'ancienne regex, mais je comprends qu'il est très difficile de construire un équivalent plus rapide avec le même comportement. Merci beaucoup!


@ MichaelBöckling Vouliez-vous vraiment ce [\ w \.-_] ? Je soupçonne que ce devait être [\ w \. \ -_] qui est égal à [\ w \ .-] .