Regexp pour extraire le nom des tables d'une chaîne.

Eléphanteau du PHP | 12 Messages

21 oct. 2010, 15:01

Hello :)

Je précise avant de commencer: je ne pose pas du tout une question sur SQL ou MySQL. Ma question est à dissocier totallement des fonctionnalités MySQL.

Je dispose au moment de l'execution de mon programme d'une chaine de caractère contenant des condition SQL.
Je souhaite extraire la totalité des tables présentes dans cette chaine.

Quelques exemples de ces chaines: "Tatas.b=1"
ou encore: "Totos.a IN (1) AND Titis.c IN (1)"
Ou même: "COUNT(Tatas.d) > 3 AND Trotros.e LIKE '%Ha%'"
Je précise pour cette dernière chaine que je ne parle donc pas que de conditions "WHERE", mais aussi "HAVING"...

Pour l'instant, je pense utiliser preg_split avec une condition établie sur la base de:
- N'est pas entre guillemets simples ou doubles.
- Commence par une majuscule.
- Finit par un "s" puis un point.

Est-ce que quelqu'un aurait une solution établie déja faite, ou dois-je me lancer dans l'élaboration du Regexp au risque d'oublier les exceptions?

Merci :)
Pierrot.

ViPHP
ViPHP | 5462 Messages

21 oct. 2010, 15:05

suivant ta description ca donnerai : (?<!')[A-Z]\w+s\.

Eléphanteau du PHP | 12 Messages

21 oct. 2010, 15:17

suivant ta description ca donnerai : (?<!')[A-Z]\w+s\.
Génial. Merci beaucoup! Petit test:
$r		= "Malls.uid IN (1) AND COUNT(Stores.toto)>3 && Deals.value<=Accounts.credit";
preg_match_all("/(?<!')[A-Z]\w+s/", $r, $m);
print_r($m);
Done:
Array
(
    [0] => Array
        (
            [0] => Malls
            [1] => Stores
            [2] => Deals
            [3] => Accounts
        )

)
J'ai juste modifié le regexp, mes tables "peuvent" ne pas se terminer par "S".
Petite question: Le résultat est un tableau de tableau contenant le résultat... Pourquoi? Ca ne me dérange pas d'y accéder en $m[0][0], mais je ne saisis pas si j'ai fait une bourde ou quoi?

Dernière question: j'ai modifié ton regexp pour qu'il marche avec mon truc, j'ai dû enlever le \. de la fin pour exclure le point de mes résultats... Du coup la condition "fini par un point" n'existe pas. Comment faire pour lui dire, mais "en dehors" du delimiter, pour qu'il le vérifie mais ne l'inclut pas dans les résultat (Donc "Totos" et non "Totos." dans le résultat)?

J'ai essayé /(?<!')([A-Z]\w+s)\./ pour capturer ce qui est entre les parenthèses (Et donc pas le point, malgré son interet pour le test)... Mais ça me donne un double tableau de résultats: l'un sans les points, l'autre avec les points...

ViPHP
ViPHP | 5462 Messages

21 oct. 2010, 15:22

c'est le preg_match_all qui fait ca, c'est parce que y'a pas de groupe de capture que ca te perturbe

si tu fais :
$r = "Malls.uid IN (1) AND COUNT(Stores.toto)>3 && Deals.value<=Accounts.credit";
preg_match_all("/(?<!')([A-Z])(\w+)/", $r, $m);
print_r($m);
tu va avoir 3 groupes

le 1er étant toujours la chaine qui correspond au pattern
le 2eme le 1er goupe
le 3eme le 2 groupe

:wink:

Eléphanteau du PHP | 12 Messages

21 oct. 2010, 15:38

c'est le preg_match_all qui fait ca, c'est parce que y'a pas de groupe de capture que ca te perturbe
Yes je viens de capter :D Merci.

Alors j'ai fait quelques modifs... Pour tenter une exception:
$r		= "Table1.a IN (1) AND COUNT(Table2.b)>3 OR Table3.pupu=4839.32 AND 'Faux1.haha'='Faux2.hoho'";
preg_match_all('/(?<!\'!")([A-Z]\w+)\./', $r, $m);
echo '<pre>';
print_r($m);
echo '</pre>';
Cela donne:
Array
(
    [0] => Array
        (
            [0] => Table1.
            [1] => Table2.
            [2] => Table3.
            [3] => Faux1.
            [4] => Faux2.
        )
    [1] => Array
        (
            [0] => Table1
            [1] => Table2
            [2] => Table3
            [3] => Faux1
            [4] => Faux2
        )
)
Comment puis-je exclude les guillemets simples ou doubles? Je pensais qu'en ajoutant !\'!" ça fonctionnerait, mais non...

ViPHP
ViPHP | 5462 Messages

21 oct. 2010, 15:49

comme ca
preg_match_all('/(?<![\'"])([A-Z]\w+)\./', $r, $m);

Eléphanteau du PHP | 12 Messages

21 oct. 2010, 15:50

comme ca
preg_match_all('/(?<![\'"])([A-Z]\w+)\./', $r, $m);
J'ai dû trouver à peu près en même temps que tu as posté que j'avais oublié le OU dans mon regexp XD
Du coup j'ai corrigé en:
preg_match_all('/(?<!\'|!")([A-Z]\w+)\.\w/', $r, $m);

Mais ta version est plus claire... Merci beaucoup man :)

Eléphanteau du PHP | 12 Messages

21 oct. 2010, 15:56

comme ca
preg_match_all('/(?<![\'"])([A-Z]\w+)\./', $r, $m);
Haha... Je viens de trouver une autre exception:
$r = "Table1.a IN (1) AND COUNT(Table2.b)>3 OR Table3.pupu=4839.32 AND Table4.haha LIKE 'hmm... Faux1.bah hmm.'";
Faux1 est reconnu comme juste, puisque les guillemets ne sont pas collés à la fausse table name...
Une idée STP chèr(e) master du regexp? :)

[EDIT]Jserais presque tenté d'aller regarder les sources du parser SQL de MySQL... Il y aurait de grandes chances qu'il fasse pareil que ce que je cherche au moment du preparsing des requettes...[/EDIT]

ViPHP
ViPHP | 5462 Messages

21 oct. 2010, 16:05

la ca deviens chaud, c'est pour faire quoi a la base ?

Eléphanteau du PHP | 12 Messages

21 oct. 2010, 16:09

la ca deviens chaud, c'est pour faire quoi a la base ?
Ben je bosse sur une couche d'abstraction de base de donnée assez complexe. Avant de générer des requêtes, je souhaite parser les conditions WHERE / HAVING, afin que le constructeur de requête puisse déterminer quelles tables sont utilisées dans la requête, pour ensuite être en mesure de n'inclure que les jointures automatiques nécéssaires et pas toutes.

[EDIT]Je m'exprime très mal. En gros:
- Le constructeur de requête connaît toutes les relations entre tables,
- Il peut donc construire une requête à ralonge, qui syntaxiquement parlant sera juste et sans erreur, mais avec un overhead de malade pour le serveur SQL puisque certains objets peuvent avoir jusque 17 jointures...
- Je souhaite donc déterminer quelles jointures inclure (ou non) en fonction des conditions (Donc tables) présentes dans les clauses WHERE et HAVING :)

Est-ce que je suis réellement claîr? :D[/EDIT]

ViPHP
ViPHP | 5462 Messages

21 oct. 2010, 16:14

normalement les requêtes tu dois mettre un ` autour des champs, pour le reste si tu regarde du coté de Doctrine tout l'ORM se base sur le DQL, il va juste chercher les champs dans la fonction "where"

Eléphanteau du PHP | 12 Messages

21 oct. 2010, 16:20

normalement les requêtes tu dois mettre un ` autour des champs,
"Je" met toujours un ` autours de mes champs. Les développeurs qui utilisent mon framework, non :)
pour le reste si tu regarde du coté de Doctrine tout l'ORM se base sur le DQL, il va juste chercher les champs dans la fonction "where"
Oui j'ai pas mal bossé avec... Mais dans le projet dont je parle certaines contraintes techniques n'étaient pas satisfaisantes avec Doctrine ou PhpActiveRecord...

Pour en revenir au shmilblick, je vais essayer de décortiquer le parser MySQL à la recherche de leur routine d'éclatement de conditions...

Dans tous les cas, je te remercie grandement pour ton aide très utile (Je vais choisir ta dernière réponse comme étant la bonne, histoire quand même que tu n'aies pas perdu ton temps =D)

Pierrot.

ViPHP
ViPHP | 5462 Messages

21 oct. 2010, 16:25

faut voire aussi a quelle moment peux suivre un nom de table,
doit y 'avoir FROM, JOIN, INTO, HAVING, UPDATE, TABLE
si t'en vois d'autre ...

Eléphanteau du PHP | 12 Messages

21 oct. 2010, 16:30

faut voire aussi a quelle moment peux suivre un nom de table,
doit y 'avoir FROM, JOIN, INTO, HAVING, UPDATE, TABLE
si t'en vois d'autre ...
Uniquement WHERE et HAVING (Puisque les conditions sont fournies au framework par le developeur et uniquement les conditions).
Les FROM, JOIN, INTO, UPDATE... Sont générés par le framework en fonction des conditions.

ViPHP
ViPHP | 5462 Messages

21 oct. 2010, 16:37

donc il est obligé de mettre le nom des tables dans les where ?

EDIT : et si tu supprime d'hab tout ce qui est entre guillemet ?

EDIT 2 : comme ca
$r = "Table1.a IN (1) AND COUNT(Table2.b)>3 OR Table3.pupu=4839.32 AND Table4.haha LIKE 'hmm... Faux1.bah hmm.'";
$r = preg_replace('/([\'"])[^\1]+\1/', '', $r);
preg_match_all('/\x60?([A-Z]\w+)\x60?\./', $r, $m);
print_r($m);