Module PHP pour travailler avec des données hiérarchiques dans InterSystems IRIS

image Depuis le début de son époque, PHP est réputé (et critiqué) pour soutenir l'intégration avec une masse de bibliothèques, ainsi qu'avec presque tous les SGBD du marché. Cependant, pour des raisons étranges, il ne prend pas en charge les bases de données hiérarchiques sur les globaux.

Les globaux sont des structures pour stocker des informations hiérarchiques. Ils rappellent quelque peu la base de données «clé -> valeur», à la seule différence que la clé peut être à plusieurs niveaux:

Set ^inn("1234567890", "city") = "Moscow" Set ^inn("1234567890", "city", "street") = "Req Square" Set ^inn("1234567890", "city", "street", "house") = 1 Set ^inn("1234567890", "year") = 1970 Set ^inn("1234567890", "name", "first") = "Vladimir" Set ^inn("1234567890", "name", "last") = "Ivanov" 

Dans cet exemple, le langage ObjectScript intégré dans le global ^ inn , qui est stocké sur le disque dur (indiqué par l'icône ^ devant le nom global), stocke des informations à plusieurs niveaux.

Naturellement, pour travailler avec des globaux de PHP, nous aurons besoin de nouvelles fonctions qui seront ajoutées par le module PHP, qui sera discuté ci-dessous.

Les globaux prennent en charge de nombreuses fonctions pour travailler avec la hiérarchie: contourner les clés à chaque niveau séparément, supprimer, copier et coller des arbres entiers et des nœuds individuels. Eh bien, ainsi que toute bonne base de données de transactions ACID. Tout cela se produit extrêmement rapidement (environ 10 5 -10 6 opérations d'insertion par seconde sur du matériel ordinaire), pour deux raisons:

  1. Les globaux sont une abstraction de niveau inférieur à SQL,
  2. Les bases mondiales sont en production depuis des décennies et pendant ce temps, elles ont réussi à lécher leur code et à l'optimiser en profondeur.

Plus d'informations sur les globaux dans la série d'articles «Globals - Swords-Masons for Data Storage»:

Les arbres. Partie 1
Les arbres. 2e partie
Tableaux clairsemés. 3e partie

Dans ce monde, les mondiaux ont trouvé leur principal lieu d'application dans les systèmes de stockage d'informations peu structurées et clairsemées telles que: médicales, données personnelles, bancaires, etc.

J'adore PHP (et je développe dessus) et je voulais jouer avec les mondiaux. Il n'y avait aucun module fini. J'ai écrit à InterSystems pour me demander de le créer. L'attente n'a abouti à rien et à la fin, nous (avec mon étudiant diplômé) avons fait le module nous-mêmes. InterSystems a parrainé le développement dans le cadre d'une subvention à l'éducation.

De manière générale, InterSystems IRIS est un SGBD multimodèle, donc à partir de PHP, vous pouvez travailler avec via ODBC en utilisant SQL, mais je m'intéressais aux globaux, et il n'y avait pas un tel connecteur.

Ainsi, le module est disponible pour PHP 7.x (testé sous 7.0-7.2). Actuellement, il ne peut fonctionner qu'avec InterSystems IRIS et Caché installés sur le même hôte.

Page du module sur OpenExchange (un catalogue de projets et d'extensions pour les développeurs sur InterSystems IRIS et Caché).

Il y a une section DISCUSS utile dans laquelle les gens partagent leurs expériences d'utilisation.

Téléchargez ici:
https://github.com/intersystems-community/php_ext_iris
Téléchargez le référentiel depuis la ligne de commande:

 git clone https://github.com/intersystems-community/php_ext_iris 

Instructions pour l'installation du module en anglais et en russe.

Fonctions du module:
Fonction PhpLa description
Travailler avec des données
iris_set ($ node, value)
Site d'installation.
  1. iris_set ($ global, $ subscript1, ..., $ subscriptN, $ value);
    iris_set ($ global, $ value);

    Renvoie: vrai ou faux (en cas d'erreur).
    Tous les paramètres de fonction sont des chaînes ou des nombres. Le premier est le nom du global, puis les indices, le dernier paramètre est la valeur.

     iris_set('^time',1); iris_set('^time', 'tree', 1, 1, 'value'); 

    Analogique sur ObjectScript

     Set ^time = 1 Set ^time("tree", 1, 1) = "value" 
  2. iris_set ($ arrayGlobal, $ value);
    Seulement 2 paramètres: le premier est un tableau dans lequel le nom du global et tous ses indices sont stockés; le second est le sens.

     $node = ['^time', 'tree', 1, 1]; iris_set($node,'value'); 

iris_get ($ node)
Lire le nœud.
Renvoie: valeur (nombre ou chaîne), NULL (valeur non définie) ou FALSE (en cas d'erreur).

  1. iris_get ($ global, $ subscript1, ..., $ subscriptN);
    iris_get ($ global);
    Tous les paramètres de fonction sont des chaînes ou des nombres. Le premier est le nom du global, puis les indices. Un global peut ne pas avoir d'index.

     $res = iris_get('^time'); $res1 = iris_get('^time', 'tree', 1, 1); 
  2. iris_get ($ arrayGlobal);
    Le seul paramètre est le tableau dans lequel le nom du global et tous ses index sont stockés.

     $node = ['^time', 'tree', 1, 1]; $res = iris_get($node); 

iris_zkill ($ node)
Suppression d'une valeur de nœud.
Renvoie: VRAI ou FAUX - en cas d'erreur.

Il est important de noter que cette fonction supprime uniquement la valeur dans le nœud et ne touche pas les branches sous-jacentes.

  1. iris_zkill ($ global, $ subscript1, ..., $ subscriptN);
    iris_zkill ($ global);
    Tous les paramètres de fonction sont des chaînes ou des nombres. Le premier est le nom du global, puis les indices. Un global peut ne pas avoir d'index.

     $res = iris_zkill('^time'); //    . $res1 = iris_zkill('^time', 'tree', 1, 1); 
  2. iris_zkill ($ arrayGlobal);
    Le seul paramètre est le tableau dans lequel le nom du global et tous ses index sont stockés.

     $a = ['^time', 'tree', 1, 1]; $res = iris_zkill($a); 

iris_kill ($ node)
Supprimez un nœud et toutes les branches descendantes.
Renvoie: VRAI ou FAUX - en cas d'erreur.

  1. iris_kill ($ global, $ subscript1, ..., $ subscriptN);
    iris_kill ($ global);
    Tous les paramètres de fonction sont des chaînes ou des nombres. Le premier est le nom du global, puis les indices. Un global peut ne pas avoir d'index, auquel cas il est supprimé complètement.

     $res1 = iris_kill('^example', 'subscript1', 'subscript2'); $res = iris_kill('^time'); //   . 
  2. iris_kill ($ arrayGlobal);
    Le seul paramètre est le tableau dans lequel le nom du global et tous ses index sont stockés.

     $a = ['^time', 'tree', 1, 1]; $res = iris_kill($a); 

iris_order ($ node)
Contourner les branches mondiales à un niveau donné
Renvoie: un tableau contenant le nom complet du prochain nœud global ou FALSE (en cas d'erreur).

  1. iris_order ($ global, $ subscript1, ..., $ subscriptN);
    Tous les paramètres de fonction sont des chaînes ou des nombres. Le premier est le nom du global, puis les indices.

    Formulaire d'utilisation PHP et équivalent ObjectScript:

     iris_order('^ccc','new2','res2'); // $Order(^ccc("new2", "res2")) 
  2. iris_order ($ arrayGlobal);
    Le seul paramètre est le tableau dans lequel le nom global et les index du nœud de départ sont stockés.

     $node = ['^inn', '1234567890', 'city']; for (; $node !== NULL; $node = iris_order($node)) { echo join(', ', $node).'='.iris_get($node)."\n"; } 

    Nous donnera une conclusion:

     ^inn, 1234567890, city=Moscow ^inn, 1234567890, year=1970 

iris_order_rev ($ node)
Contourner les branches mondiales à un niveau donné dans l'ordre inverse
Renvoie: un tableau contenant le nom complet du nœud global précédent au même niveau ou FAUX (si une erreur se produit).

  1. iris_order_rev ($ global, $ subscript1, ..., $ subscriptN);

    Tous les paramètres de fonction sont des chaînes ou des nombres. Le premier est le nom du global, puis les indices.

    Formulaire d'utilisation PHP et équivalent ObjectScript:

     iris_order_rev('^ccc','new2','res2'); // $Order(^ccc("new2", "res2"), -1) 
  2. iris_order_rev ($ arrayGlobal);

    Le seul paramètre est le tableau dans lequel le nom global et les index du nœud de départ sont stockés.

     $node = ['^inn', '1234567890', 'name', 'last']; for (; $node !== NULL; $node = iris_order_rev($node)) { echo join(', ', $node).'='.iris_get($node)."\n"; } 

    Nous donnera une conclusion:

     ^inn, 1234567890, name, last=Ivanov ^inn, 1234567890, name, first=Vladimir 

iris_query ($ CmdLine)
Contournement des succursales mondiales avec entrée à des niveaux inférieurs
Renvoie: un tableau contenant le nom complet du nœud sous-jacent (le cas échéant) ou le nœud global suivant (s'il n'y a pas de nœud imbriqué).

  1. iris_query ($ global, $ subscript1, ..., $ subscriptN);
    Tous les paramètres de fonction sont des chaînes ou des nombres. Le premier est le nom du global, puis les indices.

    Formulaire d'utilisation PHP et équivalent ObjectScript:

     iris_query('^ccc', 'new2', 'res2'); // $Query(^ccc("new2", "res2")) 
  2. iris_query ($ arrayGlobal);
    Le seul paramètre est le tableau dans lequel le nom global et les index du nœud de départ sont stockés.

     $node = ['^inn', 'city']; for (; $node !== NULL; $node = iris_query($node)) { echo join(', ', $node).'='.iris_get($node)."\n"; } 

    Nous donnera une conclusion:

     ^inn, 1234567890, city=Moscow ^inn, 1234567890, city, street=Req Square ^inn, 1234567890, city, street, house=1 ^inn, 1234567890, name, first=Vladimir ^inn, 1234567890, name, last=Ivanov ^inn, 1234567890, year=1970 

L'ordre diffère de l'ordre dans lequel nous avons défini, car dans le global automatiquement lorsque vous insérez tout est trié par ordre croissant.
Fonctions de service
iris_set_dir ($ FullPath)
Définition d'un répertoire avec une base de données
Renvoie: VRAI ou FAUX - en cas d'erreur.

 iris_set_dir('/InterSystems/Cache/mgr'); 

Il doit être complété avant de se connecter à la base de données.
iris_exec ($ CmdLine)
Exécuter la commande DB
Renvoie: VRAI ou FAUX - en cas d'erreur.

 iris_exec('kill ^global(6)'); //   ObjectScript    

iris_connect ($ login, $ pass)Se connecter à DB
iris_quit ()Coupez la connexion à la base de données
iris_errno ()Obtenir le code d'erreur
iris_error ()Obtenez une description textuelle de l'erreur

Si vous voulez jouer avec le module vous-même, alors:

Surtout pour les utilisateurs Habr, un Dockerfile a été créé pour construire l'image.
 git clone https://github.com/intersystems-community/php_ext_iris cd php_ext_iris/iris docker-compose build docker-compose up -d 

Test de la page de démonstration sur localhost: 52080 dans le navigateur.

Les fichiers PHP qui peuvent être modifiés et lus avec eux se trouvent dans le dossier php / demo et seront montés à l'intérieur du conteneur.

Pour tester IRIS, utilisez le nom d'utilisateur admin , le mot de passe est SYS .

Pour entrer les paramètres IRIS, utilisez l'URL suivante:
http: // localhost: 52773 / csp / sys / UtilHome.csp

Pour entrer dans la console IRIS de ce conteneur, utilisez la commande:
 docker exec -it iris_iris_1 iris session IRIS 


Surtout pour les utilisateurs de Habr et InterSystems Caché, la virtualka avec php-module a été levée.
Page de démonstration en russe.
Page de démonstration en anglais.
Connexion: habr_test
Mot de passe: burmur # @ 8765

Pour l'auto-installation du module sous InterSystems Caché
  1. Ayez Linux. J'ai testé sous Ubuntu, sous Windows le module devrait également être construit, mais je ne l'ai pas testé.
  2. Téléchargez la version gratuite:
  3. Installez le module cach.so en PHP selon les instructions .

Dans le conteneur docker de mon ordinateur personnel (AMD FX-9370 @ 4700Mhz 32GB, LVM, SATA SSD), pour le plaisir, j'ai fait deux tests primitifs sur la vitesse d'insertion de nouvelles valeurs dans la base de données.

  • L'insertion d'un million de nouveaux éléments dans le monde a pris 1,81 seconde ou 552 000 insertions par seconde.
  • La mise à jour d'une valeur dans le même million de fois global a pris 1,98 seconde ou 505 000 mises à jour par seconde. Un fait intéressant est que l'insertion se produit que la mise à jour. Apparemment, c'est une conséquence de l'optimisation initiale de la base de données pour une insertion rapide.

Il est clair que ces tests ne peuvent prétendre être précis et utiles, car ils sont primitifs, réalisés dans un récipient. Sur un matériel plus puissant avec un système de disque SSD PCIe, des dizaines de millions d'insertions par seconde peuvent être réalisées.

Ce qui peut être complété et l'état actuel


  1. Vous pouvez ajouter des fonctions utiles pour travailler avec des transactions (vous pouvez toujours les utiliser via iris_exec).
  2. La fonction de retour de la structure entière du global n'est pas implémentée afin que PHP ne contourne pas le global.
  3. La fonction de sauvegarde du tableau PHP en tant que sous-arbre n'est pas implémentée.
  4. L'accès aux variables de base de données locales n'est pas implémenté. Uniquement via iris_exec, bien qu'il soit préférable via iris_set.
  5. Non implémenté en contournant le global en profondeur dans la direction opposée.
  6. L'accès à la base de données via l'objet à l'aide de méthodes (similaires aux fonctions actuelles) n'est pas implémenté.

Le module actuel n'est probablement pas encore prêt pour la production: il n'y a eu aucun test pour les charges élevées et les fuites de mémoire. Cependant, si quelqu'un en a besoin, contactez-moi (Sergey Kamenev updates@mail.ru).

Conclusion


Pendant longtemps, les mondes de PHP et des bases hiérarchiques sur les globaux ne se sont pratiquement pas intersectés, bien que les globaux fournissent des fonctionnalités solides et rapides sur des types de données spécifiques (médicaux, personnels).

J'espère que ce module servira d'impulsion aux expériences des programmeurs PHP avec des programmeurs globaux et ObjectScript pour le développement simple d'interfaces web en PHP.

PS Merci de votre attention!

Source: https://habr.com/ru/post/fr443440/


All Articles