Módulo PHP para trabajar con datos jerárquicos en InterSystems IRIS

imagen Desde el comienzo de su tiempo, PHP ha sido famoso (y criticado) por apoyar la integración con una gran cantidad de bibliotecas, así como con casi todos los DBMS en el mercado. Sin embargo, debido a algunas razones extrañas, no admitía bases de datos jerárquicas en globales.

Los globales son estructuras para almacenar información jerárquica. De alguna manera recuerdan la base de datos "clave -> valor", con la única diferencia de que la clave puede ser multinivel:

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" 

En este ejemplo, el lenguaje ObjectScript incorporado en el ^ inn global, que se almacena en el disco duro (indicado por el icono ^ delante del nombre global), almacena información de varios niveles.

Naturalmente, para trabajar con globales de PHP necesitaremos nuevas funciones que serán agregadas por el módulo PHP, que se discutirán a continuación.

Globals admite muchas funciones para trabajar con la jerarquía: omitir las claves en cada nivel por separado, eliminar, copiar y pegar árboles completos y nodos individuales. Bueno, así como cualquier buena base de datos de transacciones ACID. Todo esto sucede extremadamente rápido (aproximadamente 10 5 -10 6 operaciones de inserción por segundo en hardware normal), por dos razones:

  1. Los globales son una abstracción de nivel más bajo que SQL,
  2. Las bases globales han estado en producción durante décadas y durante este tiempo lograron lamer su código y optimizarlo a fondo.

Más información sobre los globales en la serie de artículos "Globales - Espadas-Masones para el almacenamiento de datos":

Los arboles. Parte 1
Los arboles. Parte 2
Matrices dispersas. Parte 3

En este mundo, los globales han encontrado su lugar principal de aplicación en sistemas de almacenamiento de información escasa y poco estructurada, como: datos médicos, personales, bancarios, etc.

Me encanta PHP (y desarrollarlo) y quería jugar con los globales. No había un módulo terminado. Escribí a InterSystems pidiéndome que lo creara. La espera no condujo a nada, y al final nosotros (yo, junto con mi estudiante graduado) hicimos el módulo nosotros mismos. InterSystems patrocinó el desarrollo como parte de una subvención educativa.

En términos generales, InterSystems IRIS es un DBMS multimodelo, por lo que desde PHP puede trabajar con él a través de ODBC utilizando SQL, pero estaba interesado en los globales y no existía ese conector.

Entonces, el módulo está disponible para PHP 7.x (probado bajo 7.0-7.2). Actualmente, solo puede funcionar con InterSystems IRIS y Caché instalados en el mismo host.

Página del módulo en OpenExchange (un catálogo de proyectos y complementos para desarrolladores en InterSystems IRIS y Caché).

Hay una sección útil DISCUSS en la que las personas comparten sus experiencias de uso.

Descargue aquí:
https://github.com/intersystems-community/php_ext_iris
Descargue el repositorio desde la línea de comandos:

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

Instrucciones para instalar el módulo en inglés y ruso.

Funciones del módulo:
Función phpDescripción
Trabajar con datos
iris_set ($ nodo, valor)
Sitio de instalación.
  1. iris_set ($ global, $ subscript1, ..., $ subscriptN, $ value);
    iris_set ($ global, $ valor);

    Devuelve: verdadero o falso (en caso de error).
    Todos los parámetros de la función son cadenas o números. El primero es el nombre del global, luego los índices, el último parámetro es el valor.

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

    Analógico en ObjectScript

     Set ^time = 1 Set ^time("tree", 1, 1) = "value" 
  2. iris_set ($ arrayGlobal, $ value);
    Solo 2 parámetros: el primero es una matriz en la que se almacena el nombre del global y todos sus índices; El segundo es el significado.

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

iris_get ($ nodo)
Leer nodo.
Devuelve: valor (número o cadena), NULL (valor no definido) o FALSE (en caso de error).

  1. iris_get ($ global, $ subscript1, ..., $ subscriptN);
    iris_get ($ global);
    Todos los parámetros de la función son cadenas o números. El primero es el nombre de lo global, luego los índices. Un global puede no tener índices.

     $res = iris_get('^time'); $res1 = iris_get('^time', 'tree', 1, 1); 
  2. iris_get ($ arrayGlobal);
    El único parámetro es la matriz en la que se almacena el nombre del global y todos sus índices.

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

iris_zkill ($ nodo)
Eliminar un valor de nodo.
Devuelve: VERDADERO o FALSO - en caso de error.

Es importante tener en cuenta que esta función solo elimina el valor en el nodo y no toca las ramas subyacentes.

  1. iris_zkill ($ global, $ subscript1, ..., $ subscriptN);
    iris_zkill ($ global);
    Todos los parámetros de la función son cadenas o números. El primero es el nombre de lo global, luego los índices. Un global puede no tener índices.

     $res = iris_zkill('^time'); //    . $res1 = iris_zkill('^time', 'tree', 1, 1); 
  2. iris_zkill ($ arrayGlobal);
    El único parámetro es la matriz en la que se almacena el nombre del global y todos sus índices.

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

iris_kill ($ nodo)
Eliminar un nodo y todas las ramas descendientes.
Devuelve: VERDADERO o FALSO - en caso de error.

  1. iris_kill ($ global, $ subscript1, ..., $ subscriptN);
    iris_kill ($ global);
    Todos los parámetros de la función son cadenas o números. El primero es el nombre de lo global, luego los índices. Un global puede no tener índices, en cuyo caso se elimina por completo.

     $res1 = iris_kill('^example', 'subscript1', 'subscript2'); $res = iris_kill('^time'); //   . 
  2. iris_kill ($ arrayGlobal);
    El único parámetro es la matriz en la que se almacena el nombre del global y todos sus índices.

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

iris_order ($ nodo)
Omitir sucursales globales en un nivel dado
Devuelve: una matriz que contiene el nombre completo del siguiente nodo global o FALSO (en caso de error).

  1. iris_order ($ global, $ subscript1, ..., $ subscriptN);
    Todos los parámetros de la función son cadenas o números. El primero es el nombre de lo global, luego los índices.

    Formulario de uso de PHP y equivalente de ObjectScript:

     iris_order('^ccc','new2','res2'); // $Order(^ccc("new2", "res2")) 
  2. iris_order ($ arrayGlobal);
    El único parámetro es la matriz en la que se almacenan el nombre global y los índices del nodo inicial.

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

    Nos dará una conclusión:

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

iris_order_rev ($ nodo)
Omitir sucursales globales en un nivel dado en orden inverso
Devuelve: una matriz que contiene el nombre completo del nodo global anterior en el mismo nivel o FALSO (si ocurre un error).

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

    Todos los parámetros de la función son cadenas o números. El primero es el nombre de lo global, luego los índices.

    Formulario de uso de PHP y equivalente de ObjectScript:

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

    El único parámetro es la matriz en la que se almacenan el nombre global y los índices del nodo inicial.

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

    Nos dará una conclusión:

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

iris_query ($ CmdLine)
Omitir sucursales globales con entrada a niveles inferiores
Devuelve: una matriz que contiene el nombre completo del nodo subyacente (si lo hay) o el siguiente nodo global (si no hay un nodo anidado).

  1. iris_query ($ global, $ subscript1, ..., $ subscriptN);
    Todos los parámetros de la función son cadenas o números. El primero es el nombre de lo global, luego los índices.

    Formulario de uso de PHP y equivalente de ObjectScript:

     iris_query('^ccc', 'new2', 'res2'); // $Query(^ccc("new2", "res2")) 
  2. iris_query ($ arrayGlobal);
    El único parámetro es la matriz en la que se almacenan el nombre global y los índices del nodo inicial.

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

    Nos dará una conclusión:

     ^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 

El orden difiere del orden en que lo establecemos, ya que en el global automáticamente cuando inserta todo se ordena en orden ascendente.
Funciones de servicio
iris_set_dir ($ FullPath)
Establecer un directorio con una base de datos
Devuelve: VERDADERO o FALSO - en caso de error.

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

Debe completarse antes de conectarse a la base de datos.
iris_exec ($ CmdLine)
Ejecute el comando DB
Devuelve: VERDADERO o FALSO - en caso de error.

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

iris_connect ($ login, $ pass)Conectarse a DB
iris_quit ()Romper la conexión a la base de datos
iris_errno ()Obtener código de error
iris_error ()Obtenga una descripción de texto del error

Si quieres jugar con el módulo tú mismo, entonces:

Especialmente para los usuarios de Habr, se creó un Dockerfile para construir la imagen.
 git clone https://github.com/intersystems-community/php_ext_iris cd php_ext_iris/iris docker-compose build docker-compose up -d 

Prueba de la página de demostración en localhost: 52080 en el navegador.

Los archivos PHP que se pueden editar y reproducir con ellos están en la carpeta php / demo y se montarán dentro del contenedor.

Para probar IRIS, use el administrador de inicio de sesión, la contraseña es SYS .

Para ingresar la configuración de IRIS, use la siguiente URL:
http: // localhost: 52773 / csp / sys / UtilHome.csp

Para ingresar a la consola IRIS de este contenedor, use el comando:
 docker exec -it iris_iris_1 iris session IRIS 


Especialmente para los usuarios de Habr e InterSystems Caché, se levantó la virtualka con módulo php.
Página de demostración en ruso.
Página de demostración en inglés.
Iniciar sesión: habr_test
Contraseña: burmur # @ 8765

Para la autoinstalación del módulo en InterSystems Caché
  1. Tener Linux Probé en Ubuntu, en Windows el módulo también debería estar construido, pero no lo probé.
  2. Descargue la versión gratuita:
  3. Instale el módulo cach.so en PHP de acuerdo con las instrucciones .

En el contenedor docker de mi computadora personal (AMD FX-9370 @ 4700Mhz 32GB, LVM, SATA SSD), por diversión, hice dos pruebas primitivas sobre la velocidad de inserción de nuevos valores en la base de datos.

  • La inserción de 1 millón de elementos nuevos en el global tomó 1.81 segundos o 552K inserciones por segundo.
  • Actualizar un valor en el mismo global 1,000,000 de veces tomó 1.98 segundos o 505K actualizaciones por segundo. Un hecho interesante es que ocurre la inserción que la actualización. Aparentemente, esto es una consecuencia de la optimización inicial de la base de datos para una inserción rápida.

Está claro que estas pruebas no pueden afirmar que sean precisas y útiles, ya que son primitivas, hechas en un contenedor. En hardware más potente con un sistema de disco SSD PCIe, se pueden lograr decenas de millones de inserciones por segundo.

Qué se puede completar y el estado actual


  1. Puede agregar funciones útiles para trabajar con transacciones (aún puede usarlas a través de iris_exec).
  2. La función de devolver toda la estructura de lo global no está implementada para que PHP no pase por alto lo global.
  3. La función de guardar la matriz PHP como un subárbol no está implementada.
  4. El acceso a las variables locales de la base de datos no está implementado. Solo a través de iris_exec, aunque es mejor a través de iris_set.
  5. No implementado sin pasar por lo global en profundidad en la dirección opuesta.
  6. No se implementa el acceso a la base de datos a través del objeto utilizando métodos (similares a las funciones actuales).

El módulo actual probablemente aún no esté listo para la producción: no hubo pruebas de cargas altas y pérdidas de memoria. Sin embargo, si alguien lo necesita, contácteme (Sergey Kamenev updates@mail.ru).

Conclusión


Durante mucho tiempo, los mundos de PHP y las bases jerárquicas de los globales prácticamente no se cruzan, aunque los globales proporcionan una funcionalidad fuerte y rápida en tipos de datos específicos (médicos, personales).

Espero que este módulo sirva como un ímpetu para los experimentos de programadores PHP con programadores globales y ObjectScript para el desarrollo simple de interfaces web en PHP.

PD: ¡Gracias por su atención!

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


All Articles