Buscar

Limpiar filtro
Artículo
Muhammad Waseem · 26 abr, 2022

Conjuntos de datos "Plug and play" con ObjectScript Package Manager (ZPM)

#datasets{ font-family: Arial, Helvetica, sans-serif; border-collapse: collapse; width: 100%; } #datasets td, #datasets th { border: 1px solid #ddd; padding: 8px; } #datasets tr:nth-child(even){background-color: #f2f2f2;} #datasets th { padding-top: 12px; padding-bottom: 12px; text-align: left; background-color: #2b3589; color: white; } #datasets tr { line-height: 10px; } Con el lanzamiento de InterSystems IRIS 2021.2 Preview y la nueva funcionalidad LOAD DATA, los conjuntos de datos se pueden añadir con Objectscript Package Manager (ZPM) Medical Datasets contiene los siguientes 12 conjuntos de datos. Para tablas de conjuntos de datos y detalles de datos, echa un vistazo a la Demo online, usando SuperUser | SYS ID Dataset Name Tables Licence 1 Synthetic Medical Data 11 Public Domain 2 Health Care Analytics - 1 8 Public Domain 3 Global Suicide Data 7 Public Domain 4 COVID-19 Polls1 7 CC-BY 5 Cancer Rates by U.S. State 2 Public Domain 6 Opioid Overdose Deaths 2 Public Domain 7 Heart Disease Prediction 1 Public Domain 8 Yellowpages medical services dataset 1 Public Domain 9 Hospital ratings 1 Public Domain 10 Heart Failure Prediction 1 (CC BY 4.0) 11 Diabetes Dataset 1 Public Domain 12 Chronic Kidney Disease 1 Public Domain Para usar cualquiera de los conjuntos de datos de la tabla anterior, podemos añadirlo a nuestro namespace usando la ID del conjunto de datos. Así que vamos a añadir el conjunto de datos "Synthetic Medical Data" En primer lugar, tenemos que instalar el paquete dataset-medical, usando el siguiente comando: zpm "install dataset-medical Ahora ya podemos importar el conjunto de datos. El conjunto de datos se puede añadir o eliminar por Terminal o por Aplicación web. AÑADIR DATASET DESDE EL TERMINAL Podemos añadir un conjunto de datos llamando a la función ImportDS de la clase dc.data.medical.utility pasando la ID del conjunto de datos desde el Terminal: do ##class(dc.data.medical.utility).ImportDS(1) ¡Eso es todo! Se ha creado nuestro primer conjunto de datos. Se crea un conjunto de datos "sintético" con 11 tablas y 83 341 registros, que pueden ser confirmados por el Management Portal Eliminar conjuntos de datos Utiliza el siguiente comando para eliminar un conjunto de datos en particular pasando su ID do ##class(dc.data.medical.utility).RemoveDS(1) Añadir TODOS los conjuntos de datos Para instalar todos los conjuntos de datos, pasa 999 a la función ImportDS do ##class(dc.data.medical.utility).ImportDS(999) Eliminar TODOS los conjuntos de datos Y para eliminar todos los conjuntos de datos, pasa 999 a la función RemoveDS do ##class(dc.data.medical.utility).RemoveDS(999) AÑADIR DATASET DESDE EL TERMINAL Ve a http://localhost:52773/csp/datasets/index.csp y haz clic en "Install DataSet": Ver datos desde la aplicación web Ve a http://localhost:52773/csp/datasets/index.csp y haz clic en cualquier tabla de la columna lateral Eliminar Conjuntos de datos desde la aplicación web Ve a http://localhost:52773/csp/datasets/index.csp y haz clic en "Remove dataset": El conjunto de datos se eliminó con éxito. ¡Espero que os resulte útil!
Artículo
Heloisa Paiva · 11 abr, 2023

Creando una conexión ODBC - Paso a paso

Introducción Este artículo tiene la intención de ser un sencillo tutorial de cómo crear conexiones ODBC y trabajar con ellas, ya que me pareció que empezar con ellas es un poco confuso. Yo tuve la ayuda de unas personas increíbles, y creo que todos merecemos ese apoyo. Voy dividir cada pequeña parte en sesiones, así que puedes ir directamente a la que necesites, aunque recomiendo leerlo todo. Voy a usar los datos de ejemplo creados en un artículo previo, Tutorial rápido para crear bases de datos de ejemplo: Samples.PersistentData, con las propiedades Name y Age. Creando la conexión Abre el ODBC Data Sources - busca ODBC en la barra de búsqueda de tu ordenador y lo encontrarás. Selecciona la pestaña System DNS Haz clic en Add Selecciona el driver adecuado - para este ejemplo, estoy usando InterSystems IRIS ODBC35 Elige un nombre para la conexión Escribe el servidor, puerto y namespace que quieres conectar (por ejemplo: IP 12.0.0.1, puerto 1972 y namespace SAMPLE) Escribe el usuario y contraseña que vas a usar para conectar Haz clic en "try connection" para comprobar si todo funciona bien - si no, verifica otra vez el usuario y contraseña, servidor, puerto y namespace, y también verifica si tu IRIS está iniciado (para este ejemplo), o si necesitas una VPN para esta conexión. Nota: No sé si esto funciona de forma similar en Linux o iOS, ¡lo siento! Usando tu conexión en una Business Operation en producción Este es solo uno de ejemplos de cómo puedes poner en práctica esta conexión, pero es uno muy utilizado. Con una Business Operation con adaptador "EnsLib.SQL.OutboundAdapter" en producción, abre la pestaña de configuración y expande la parte de Parámetros Básicos. Vas a ver un DSN input como este: Expande el input y encuentra la conexión que acabamos de crear. Si no esta ahí, verifica si la creaste en el ODBC Data Source correcto (32-bit o 64-bit). Si no esta ahí, sigue los pasos otra vez de la otra opción y verifica el input DSN otra vez. Credenciales IRIS puede necesitar un usuario y contraseña para acceder a esta conexión, por lo que tienes que proporcionarlo. Justo debajo del input DSN, vas a encontrar un input Credenciales con una lupa a su lado. Haz clic en la lupa y verás el menú de credenciales. En la pestaña a la derecha, haz clic en "Nuevo", escribe un ID que te ayudará a identificar la credencial, el usuario y contraseña necesarios y guárdalo. ¡Genial! Ahora que tienes tus credenciales, puedes volver a la producción y seleccionarlas por el ID que has elegido. P.D.: un ejemplo para que pruebes Para ese sencillo tutorial, he creado la siguiente clase en un namespace diferente de aquel en el que está "Sample.PersistentData": Class Sample.ODBC.Operation Extends Ens.BusinessOperation { Parameter ADAPTER = "EnsLib.SQL.OutboundAdapter"; Property Adapter As EnsLib.SQL.OutboundAdapter; Parameter INVOCATION = "Queue"; Method LegalAge(Request As Sample.request, Response As Sample.response) As %Status { // instanciate the response Do Request.NewResponse(.Response) // Execute the query and select the first result Do ..Adapter.ExecuteQuery(.result, "SELECT Name, Age from Sample.PersistentData where Age > 20") Do result.%Next() // just for visualizing, sets the first result in the response Set Response.result = result.%Get("Name")_" "_result.%Get("Age") Quit 1 } XData MessageMap { <MapItems> <MapItem MessageType="Sample.request"> <Method>LegalAge</Method> </MapItem> </MapItems> } } Gracias por leerme y espero que os resulte útil. No dudéis en escribirme si tenéis alguna duda o comentario.
Artículo
Alberto Fuentes · 14 oct, 2019

Mejoras en procesamiento JSON

¡Hola a tod@s! Me gustaría comentar con vosotros algunas de las mejoras en procesamiento JSON que incorpora IRIS desde la versión 2019.1. Utilizar JSON como formato de serialización es muy común a la hora de construir aplicaciones hoy en día, especialmente si desarrollamos o interactuamos con servicios REST. Dar formato a cadenas a JSON Ayuda mucho poder dar un formato fácilmente interpretable por una persona a una cadena JSON. Especialmente cuando queremos depurar código y acabamos teniendo que examinar por ejemplo una respuesta JSON de un tamaño considerable. Al principio, las estructuras simples son fáciles de leer, pero a medida que comenzamos a tener múltiples elementos anidados unos en otros puede complicarse. Aquí tenemos un ejemplo sencillo: {"name":"Gobi","type":"desert","location":{"continent":"Asia","countries":["China","Mongolia"]},"dimensions":{"length":1500,"length_unit":"km","width":800,"width_unit":"km"}} Si la tuviésemos formateada de manera que nos facilitase la lectura, nos permitiría explorar de forma sencilla su contenido. Echemos un vistazo a la misma cadena JSON pero formateada de forma más apropiada: { "name":"Gobi", "type":"desert", "location":{ "continent":"Asia", "countries":[ "China", "Mongolia" ] }, "dimensions":{ "length":1500, "length_unit":"km", "width":800, "width_unit":"km" } } Incluso con este ejemplo tan simple ya vemos como el resultado es un bastante más largo, así que nos hacemos a la idea de por qué en muchas ocasiones este formato no es el que se utiliza por defecto en muchos sistemas. Pero sin embargo, con este formato tan "explicativo" podemos identificar muy fácilmente subestructuras y hacernos una idea rápidamente de si algo no está del todo bien. En InterSystems IRIS 2019.1 se incluye un paquete con el nombre %JSON donde podemos encontrar un conjunto de herramientas, entre ellas un formateador, que nos ayudará a conseguir lo que acabamos de ver en el ejemplo anterior. Dar formato a nuestros objetos dinámicos, arrays y cadenas JSON para obtener una representación más fácilmente interpretable por una persona. %JSON.Formatter es una clase con una interfaz muy simple. Todos los métodos son métodos de instancia, así que el primer paso es obtener una instancia de la clase: USER>set formatter = ##class(%JSON.Formatter).%New() La razón para desarrollarlo de esta forma es que así podremos configurar nuestra instancia del formateador de manera que incluyamos por ejemplo ciertos caracteres para la indentación (espacios en blanco o tabuladores), caracteres de fin de línea, etc. y después lo podremos reutilizar donde nos haga falta. El método Format()recibe bien un objeto dinámico / array o una cadena JSON. Echemos un vistazo a un ejemplo sencillo utilizando un objeto dinámico USER>do formatter.Format({"type":"string"}) { "type":"string" } Ahora el mismo ejemplo pero utilizando una cadena JSON: USER>do formatter.Format("{""type"":""string""}") { "type":"string" } El método Format()devuelve la cadena formateada al dispositivo actual, pero también tenemos disponibles los métodos FormatToString() y FormatToStream()en caso de necesitar el resultado en una variable. Algo más complicado Lo que hemos visto hasta ahora está bien, pero no merece un artículo solamente para ello :). InterSystems IRIS 2019.1 incluye también una manera muy sencilla de serializar objetos persistentes y no-persistentes hacia o desde JSON. La clase que nos interesa para este fin es %JSON.Adaptor. El concepto es muy similar al de %XML.Adaptor, de ahí el nombre. Cualquier clase que quisiéramos serializar hacia o desde JSON necesita heredar de %JSON.Adaptor. La clase herederá entonces unos cuantos métodos, de los cuales cabe reseñar %JSONImport() y %JSONExport(). Para explicarlos, lo mejor es verlo con un ejemplo. Asumamos como punto de partida que tenemos las siguientes clases: Class Model.Event Extends (%Persistent, %JSON.Adaptor) { Property Name As %String; Property Location As Model.Location; } y Class Model.Location Extends (%Persistent, %JSON.Adaptor) { Property City As %String; Property Country As %String; } Tenemos una clase Event persistente, que se relaciona con una Location. Ambas clases heredan de %JSON.Adaptor. Esto les permite instanciar, rellenar un objeto y exportarlo directamente como una cadena JSON: USER>set event = ##class(Model.Event).%New() USER>set event.Name = "Global Summit" USER>set location = ##class(Model.Location).%New() USER>set location.City = "Boston" USER>set location.Country = "United States of America" USER>set event.Location = location USER>do event.%JSONExport() {"Name":"Global Summit","Location":{"City":"Boston","Country":"United States of America"}} Por supuesto, también podemos ir en sentido inverso utilizando %JSONImport(): USER>set jsonEvent = {"Name":"Global Summit","Location":{"City":"Boston","Country":"United States of America"}} USER>set event = ##class(Model.Event).%New() USER>do event.%JSONImport(jsonEvent) USER>write event.Name Global Summit USER>write event.Location.City Boston Los métodos para importar y exportar funcionan para estructuras de datos anidadas arbitrariamente. De forma análoga a %XML.Adaptor puedes especificar la lógica de mapeo para cada propiedad individual a través de su correspondiente parámetro. Vamos a cambiar la definición de la clase Model.Event para ver cómo funciona: Class Model.Event Extends (%Persistent, %JSON.Adaptor) { Property Name As %String(%JSONFIELDNAME = "eventName"); Property Location As Model.Location(%JSONINCLUDE = "INPUTONLY"); } Asumiendo que tenemos la misma estructura de objeto asignado a la variable event que en el ejemplo anterior, una llamada a %JSONExport() retornaría lo siguiente: USER>do event.%JSONExport() {"eventName":"Global Summit"} La propiedad Name se mapea al campo eventName y la propiedad Location se excluye de la exportación que hace %JSONExport(), pero sin embargo estará presente cuando se carguen datos a partir de una cadena JSON cuando se llame a %JSONImport() . Hay además otros parámetros que nos permitirán ajustar el mapeo: %JSONFIELDNAME corresponde al nombre del campo en el contenido JSON. %JSONIGNORENULL permite sobreescribir el comportamiento por defecto a la hora de manejar cadenas vacías para las propiedades tipo string. %JSONINCLUDE controla si la propiedad se incluirá o no en la salida / entrada JSON. Si %JSONNULL es true (=1), entonces las propiedades sin rellenar son exportadas con valor null. En otro caso, el campo correspondiente a la propiedad simplemente se ignorará durante la exportación. %JSONREFERENCE especifica cómo se tratan las referencias a objetos. "OBJECT" es el valor por defecto e indica que las propiedades de las clases referenciadas se utilizan para representar al objeto referenciado. Otras opciones disponibles son "ID", "OID" y "GUID". De esta forma tenemos un gran nivel de control de forma muy cómoda. Quedan atrás los días en que manualmente tenías que mapear tus objetos a JSON. Una cosa más En lugar de configurar los parámetros de mapeo a nivel de las propiedades, puedes configurar tu mapeo JSON también en un bloque XData. El siguiente bloque XData con el nombre OnlyLowercaseTopLevel tiene la misma configuración que nuestra clase Event anterior. Class Model.Event Extends (%Persistent, %JSON.Adaptor) { Property Name As %String; Property Location As Model.Location; XData OnlyLowercaseTopLevel { <Mapping xmlns="http://www.intersystems.com/jsonmapping"> <Property Name="Name" FieldName="eventName"/> <Property Name="Location" Include="INPUTONLY"/> </Mapping> } } Sin embargo, aquí hay una diferencia importante: los mapeos JSON en bloques XData no cambian el comportamiento por defecto, y además, para utilizarlos, tienes que referenciarlos en el correspondiente %JSONImport() y %JSONExport() como último argumento, por ejemplo: USER>do event.%JSONExport("OnlyLowercaseTopLevel") {"eventName":"Global Summit"} Si no existe un bloque XData con el nombre que le hemos pasado, utilizará el mapeo por defecto. Con esta aproximación, podemos tener configurados diferentes mapeos y referenciar aquel que nos haga falta en cada caso, permitiendo aún mayor control a la vez que nos permite tener mapeos más flexibles y reutilizables.
Artículo
Ricardo Paiva · 28 jul, 2022

Consejos y trucos del nuevo comando LOAD DATA

Estos días he estado trabajando con la excelente y nueva funcionalidad: [LOAD DATA](https://docs.intersystems.com/iris20212/csp/docbook/DocBook.UI.Page.cls?KEY=RSQL_loaddata). Con este artículo me gustaría compartir mis primeras experiencias con todos. Los siguientes puntos no contienen ningún orden ni ningún otro análsis. Son solo cosas que observé al utilizar el comando **LOAD DATA**. Y se debe tener en cuenta que estos puntos se basan en la versión 2021.2.0.617 de IRIS, que es una versión de prueba. Por ello, es posible que mis observaciones no apliquen a las nuevas versiones de IRIS. Pero quizás sean útiles para otros. #### 1) La ruta del archivo está en el lado del servidor He hecho mis primeras pruebas mediante JDBC. La primera sorpresa que me encontré: ¡El archivo y la ruta del archivo deben, _por supuesto ;-)_ estar en el lado del servidor! El controlador JDBC **no** se encarga de esto en el lado del cliente. Probablemente esto es obvio, pero no lo había considerado al principio. #### 2) El sufijo del archivo no es relevante Los documentos dicen: > "_Los nombres de los archivos deben incluir un sufijo .txt o .csv (valores separados por comas)._" Según mis observaciones, el comportamiento no es así. El sufijo no es relevante. #### 3) ¡Lee los documentos! ... o ¿dónde están las filas con errores? Cuando cargué algunos archivos de datos, perdí filas. Si hay algún problema con una línea, la línea se ignora. Esto sucede silenciosamente en segundo plano y no se notifica activamente al cliente. Después de consultar me di cuenta de que tengo que revisar %SQL\_Diag.Result y %SQL\_Diag.Message para ver los problemas de forma detallada. También me di cuenta de que este comportamiento ya está descrito en esta página: ... así que hay que leer los manuales ;-) Algunos ejemplos de lo que se puede ver: SELECT * FROM %SQL_Diag.Result ORDER BY createTime DESC Revisa la columna errorCount de tu descarga. Se pueden ver los detalles (de la fila) en %SQL_Diag.Message SELECT * FROM %SQL_Diag.Message ORDER BY messageTime DESC Se puede filtrar por un _diagResult_ específico (%SQL\_Diag.Result.ID = %SQL\_Diag.Message.diagResult) SELECT * FROM %SQL_Diag.Message WHERE diagResult=4 ORDER BY messageTime DESC #### 4) LOAD DATA no es compatible con $SYSTEM.SQL.Schema.ImportDDL Para mi aplicación de prueba [Openflights Dataset](https://openexchange.intersystems.com/package/openflights_dataset) intenté cargar todos los archivos externos con LOAD DATA. Las sentencias se agrupan dentro de un archivo de texto (sql) donde anteriormente también creé las tablas. Aprendí que no se puede hacer eso mediante $SYSTEM.SQL.Schema.ImportDDL. Por cierto, la documentación de [ImportDDL](https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.UI.Page.cls?KEY=GSQL_IMPORT) señala que no todas las sentencias SQL son compatibles. Solo unas pocas sentencias SQL aparecen en esta página. LOAD DATA lamentablemente no es una de ellas... Y, por cierto, USE DATABASE tampoco. #### 5) Para administrar unicode hay que cambiar una configuración Para evitar problemas con el código de los datos durante la carga, hay que poner esta configuración en el servidor de %Java: -Dfile.encoding=UTF-8 Consulta más detalles en esta [publicación](https://community.intersystems.com/post/load-data-sqlcode-any-idea#comment-177086). Este problema debería desaparecer en la próxima versión de IRIS. #### 6) El proceso de carga se detiene con un error, pero los datos se cargan La carga de datos mediante JDBC se para por un error %qparsets. Se ve así: Error: [SQLCODE: <-400>:<Fatal error occurred>] [Error: <<UNDEFINED>zExecute+83^%sqlcq.OPENFLIGHTS.cls10.1 *%qparsets>] [Location: <ServerLoop>] Pero hay que preocuparse, los datos se cargaron :-) Consulta más detalles en esta [publicación](https://community.intersystems.com/post/load-data-sqlcode-any-idea#comment-177076). Este problema debería desaparecer en la próxima versión de IRIS. Este artículo ha sido etiquetado como "Mejores prácticas" ("Best practices"). Los artículos con la etiqueta "Mejores prácticas" incluyen recomendaciones sobre cómo desarrollar, probar, implementar y administrar mejor las soluciones de InterSystems.
Artículo
Alberto Fuentes · 12 mar, 2021

Configuración del servidor web Apache HTTPD para HealthShare

Una cuestión muy común es cuál es la configuración ideal para el servidor web Apache HTTPD cuando se utiliza con HealthShare. El propósito de este artículo es describir la configuración inicial recomendada del servidor web para cualquier producto HealthShare.  Como punto de partida, se recomienda la versión 2.4.x (64-bit) de Apache HTTPD. Existen versiones anteriores como la 2.2.x, pero no se recomienda esta versión por rendimiento y escalabilidad de HealthShare. ## Configuración de Apache ### Módulo de la API de Apache sin NSD HealthShare requiere la opción de instalación del módulo de la API de Apache sin NSD. La versión de los módulos vinculados de forma dinámica depende de la versión de Apache: * CSPa24.so (Apache versión 2.4.x) Es mejor dejar que la configuración de las Caché Server Pages (CSP) en el httpd.conf de Apache sea realizada por la instalación de HealthShare, la cual se explica en detalle más adelante en este documento. Sin embargo, la configuración puede realizarse manualmente. Para más información, consulta la Guía de Configuración de Apache que se encuentra en la documentación de InterSystems:  Opción recomendada: módulo de la API de Apache sin NSD (CSPa24.so) ## Recomendaciones del Módulo de multiprocesamiento (MPM) de Apache ### MPM Prefork de Apache vs. MPM Worker {#ApacheWebServer-ApachePreforkMPMVs.WorkerMPM} El servidor web de Apache HTTPD incluye tres módulos de multiprocesamiento (*Multi-Processsing Modules - MPM*): Prefork, Worker y Event. Los MPM son responsables de conectar los equipos con los puertos de red, aceptar solicitudes y generar procesos hijos o secundarios que se encarguen de administrarlos. Por defecto, Apache se suele configurar con MPM Prefork, que no escala muy bien para un número de transacciones elevadas o altas cargas de trabajo de usuarios concurrentes. En los sistemas de producción de HealthShare, el MPM **_Worker_** de Apache debería habilitarse por razones de rendimiento y escalabilidad. Se prefiere el MPM Worker por las siguientes razones: * El MPM Prefork utiliza varios procesos secundarios con un hilo de ejecución (*thread*) cada uno, y cada proceso controla una conexión a la vez. Al utilizar Prefork, las solicitudes concurrentes se ven afectadas porque como cada proceso solo puede manejar una sola solicitud a la vez, las solicitudes se quedan esperando en cola hasta que se libere un proceso del servidor. Además, para escalar, son necesarios más procesos secundarios de Prefork, que consumen una gran cantidad de memoria. * El MPM Worker utiliza varios procesos secundarios con muchos hilos de ejecución (*thread*) cada uno. Cada hilo de ejecución maneja una conexión al mismo tiempo, lo que es de gran ayuda para la concurrencia y reduce el uso de la memoria. Worker gestiona mejor la concurrencia que Prefork, ya que generalmente habrá hilos de ejecución libres disponibles para atender las solicitudes, en lugar de los procesos de Prefork con un solo hilo que podría estar ocupado. ### Parámetros del MPM Worker de Apache {#ApacheWebServer-ApacheWorkerMPMParameters} Utilizando hilos de ejecución para atender las solicitudes, Worker puede atender una gran cantidad de solicitudes empleando menos recursos del sistema que el Prefork utilizando procesos.  Las directivas más importantes utilizadas para controlar el MPM Worker son **ThreadsPerChild**, que controla el número de hilos de ejecución desplegados por cada proceso hijo y **MaxRequestWorkers**, que controla el número total máximo de hilos que se pueden lanzar. Los valores comunes de la directiva que se recomiendan para el MPM Worker se especifican en la siguiente tabla: Parámetros recomendados para el servidor web Apache HTTPD Directivas del MPM Worker de Apache Valor recomendado Comentarios MaxRequestWorkers  Número máximo de usuarios simultáneos en el Visor Clínico de HealthShare, o el de otros componentes de HealthShare que se configuraron como la suma de todos los tamaños de grupo (*Pool Size*) de Business Services entrantes a todos los productos definidos por las producciones. * Nota: Si no se cuenta con esta información al momento de realizar la configuración, podemos comenzar con un valor de “1000”   MaxRequestWorkers establece el límite en la cantidad de solicitudes concurrentes que se atenderán, es decir, limita la cantidad total de hilos de ejecución que estarán disponibles para atender a los clientes. Es importante configurar correctamente MaxRequestWorkers porque si se ajusta con un nivel muy bajo, entonces se desperdiciarán los recursos; y si se configura con un nivel muy alto, entonces el rendimiento del servidor se verá afectado. Hay que tener en cuenta que cuando se intentan más conexiones que workers disponibles, las conexiones se situarán en una cola de espera. La cola de espera predeterminada se puede ajustar con la directiva ListenBackLog.   MaxSpareThreads 250   MaxSpareThreads atiende los hilos de ejecución inactivos en todo el servidor. Si hay demasiados hilos inactivos en el servidor, se finalizan los procesos secundarios hasta que el número de hilos inactivos sea menor que este número. El aumento en la cantidad de hilos libres por encima de los predeterminados, tiene como objetivo reducir la probabilidad de que se tengan que recrear procesos.   MinSpareThreads 75   MinSpareThreads atiende los hilos inactivos del servidor. Si no hay suficientes hilos inactivos en el servidor, se crean procesos hijo hasta que el número de hilos inactivos sea mayor que este número. La reducción en la cantidad de hilos libres por debajo del valor predeterminado, tiene como objetivo reducir la probabilidad de que se tengan que recrear procesos.   ServerLimit MaxRequestWorkers se divide por ThreadsPerChild   El valor máximo de MaxRequestWorkers durante la vida útil del servidor. ServerLimit es un límite estricto en el número de procesos secundarios activos, y debe ser mayor o igual que la directiva MaxRequestWorkers dividida por la directiva ThreadsPerChild. Con worker, sólo se usa esta directiva si la configuración de MaxRequestWorkers y ThreadsPerChild necesita más de 16 procesos en el servidor (predeterminado).   StartServers 20   La directiva StartServers establece el número de procesos del servidor secundario creados al inicio. Como la cantidad de procesos es controlada de forma dinámica dependiendo de la carga, por lo general hay pocas razones para ajustar este parámetro, excepto para asegurar que el servidor esté listo para administrar una gran cantidad de conexiones justo cuando se inicia.   ThreadsPerChild 25   Esta directiva establece el número de hilos creados por cada proceso secundario, 25 de forma predeterminada. Se recomienda mantener el valor predeterminado, porque si aumenta podría llevar a la dependencia excesiva de un solo proceso.   Para más información, consulta la documentación correspondiente a la versión de Apache: * [Directivas comunes de Apache 2.4 MPM](http://httpd.apache.org/docs/2.4/mod/mpm_common.html) ### Ejemplo para configurar el MPM Worker de Apache 2.4 En esta sección se especifica cómo configurar el MPM Worker para un servidor web Apache 2.4 de RHEL7, necesario para permitir hasta 500 usuarios simultáneos de TrakCare. 1. Primero, verificar el MPM mediante el siguiente comando:![](/sites/default/files/inline/images/images/apache1.png) 2. Edita el archivo de configuración /etc/httpd/conf.modules.d/00-mpm.conf cuando sea necesario, agregando y eliminando el signo # para que solo se carguen los módulos MPM Worker. Modifica la sección MPM Worker con los siguientes valores, en el mismo orden que se indica a continuación:![](/sites/default/files/inline/images/images/Apache2.png) 3. Reinicia Apache 4. Después de que reinicie Apache correctamente, valida los procesos de worker ejecutando los siguientes comandos. Deberías ver algo parecido a lo siguiente, validando el proceso httpd.worker:![](/sites/default/files/inline/images/images/Apache3.png) ## **Fortalecimiento de Apache** ### Módulos requeridos de Apache La instalación del paquete de distribución oficial de Apache habilitará, por defecto, un conjunto específico de módulos de Apache. Esta configuración predeterminada de Apache cargará estos módulos en cada proceso httpd. Se recomienda deshabilitar todos los módulos que no sean necesarios para HealthShare, por las siguientes razones: * reducir el consumo de recursos del proceso httpd daemon, * reducir la posibilidad de que falle la segmentación desde un módulo problemático, * reducir las vulnerabilidades de la seguridad. En la siguiente tabla se describen los módulos de Apache recomendados para HealthShare. Cualquier módulo que no se encuentre en la lista, se puede deshabilitar: | Nombre del módulo | Descripción | | ----------------- | ---------------------------------------------------------------------------------------------------------------------- | | alias | Mapea diferentes partes del sistema de archivos al equipo principal en el árbol de documentos y redirecciona la URL. | | authz_host | Proporciona un control de acceso basado en el nombre del equipo principal del cliente y la dirección IP. | | dir | Proporciona una redirección de la barra final que aloja un directorio con archivos índice. | | headers | Controla y modifica los encabezados de las solicitudes y respuestas de HTTP | | log_config | Registra las solicitudes que se le hicieron al servidor. | | mime | Asocia las extensiones para los nombres de los archivos solicitados con el comportamiento y el contenido de los mismos | | negotiation | Permite seleccionar el contenido del documento que mejor se ajuste a las funciones del cliente. | | setenvif | Permite configurar variables de entorno basadas en las características de la solicitud | | status | Muestra el estado del servidor y las estadísticas de rendimiento | ### Cómo deshabilitar los módulos Los módulos que no sean necesarios deben deshabilitarse para fortalecer la configuración y de esta manera reducir los puntos vulnerables en la seguridad. El cliente es responsable de las políticas de seguridad del servidor web. Por lo menos, deben deshabilitarse los siguientes módulos. | Nombre del módulo | Descripción | | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | asis | Envía archivos que contienen sus propios encabezados HTTP | | autoindex | Genera un directorio con índices, y muestra la lista de directorios cuando ningún archivo index.html está presente | | env | Modifica la variable de entorno que se transfirió a los scripts CGI y a las páginas SSI | | cgi | cgi, ejecución de scripts CGI | | actions | Ejecuta scripts CGI basados en el tipo de medios o método solicitado, o una acción que se desencadena en las solicitudes | | include | Documentos HTML analizados por el servidor (incluidos los que están del lado del servidor) | | filter | Filtrado inteligente de solicitudes | | version | Manejo de la información en las versiones de los archivos de configuración mediante IfVersion | | userdir | Mapeo de solicitudes a directorios específicos de los usuarios. Es decir, el ~username que se encuentra en la URL se traducirá a un directorio en el servidor | ## **Apache SSL/TLS** {#ApacheWebServer-ApacheSSL/TLS} InterSystems recomienda que todas las comunicaciones que se realizan mediante TCP/IP entre los servidores y los clientes de HealthShare se cifren con SSL/TLS para proteger los datos durante las transferencias y garantizar la confidencialidad y la autenticación. Además, InterSystems también recomienda que se utilice HTTPS para llevar a cabo las comunicaciones entre los navegadores (clientes) de los usuarios y la capa del servidor web de la arquitectura propuesta. Asegúrate de consultar las políticas de seguridad de tu empresa, para garantizar que cumple con cualquier requisito de seguridad específico que tenga.  El cliente es responsable de suministrar y administrar los certificados SSL/TLS. Si utilizas certificados SSL, agrega ssl_module (mod_ssl.so). ## Parámetros adicionales para el fortalecimiento de Apache Para fortalecer aún más la configuración de Apache, realiza los siguientes cambios en httpd.conf: * TraceEnable debe desactivarse para evitar posibles problemas de seguimiento entre sitios. ![](/sites/default/files/inline/images/images/Apache4.png) * ServerSignature debe desactivarse para que no se visualice la versión del servidor web. ![](/sites/default/files/inline/images/images/Apache5.png) ## Parámetros de configuración suplementarios de Apache {#ApacheWebServer-SupplementalApacheConfigurationParameters} ### Keep-Alive {#ApacheWebServer-Keep-Alive} La configuración de Apache Keep-Alive permite utilizar la misma conexión TCP para la comunicación mediante HTTP, en vez de abrir una nueva conexión para cada nueva solicitud, es decir, Keep-Alive mantiene una conexión persistente entre el cliente y el servidor. Cuando la opción Keep-Alive está habilitada, la mejora en el desempeño proviene de la descongestión de la red, la reducción de la latencia en las subsiguientes solicitudes y el menor uso del CPU ocasionado por la apertura simultánea de las conexiones. Keep-Alive está ACTIVO por defecto y el estándar HTTP v1.1 requiere que se asuma. Sin embargo, hay ciertas condiciones para habilitar Keep-Alive; Internet Explorer debe ser la versión 10 o superior para evitar problemas como el tiempo de espera, que se presenta en las versiones anteriores de Internet Explorer. Además, intermediarios como firewalls, balanceadores de cargas y proxies, etc., pueden interferir con las “conexiones TCP persistentes” y pueden ocasionar el cierre imprevisto de las conexiones.   Cuando se habilita Keep-Alive, su tiempo de espera también debe ser ajustarse. El tiempo de espera predeterminado de Keep-Alive para Apache es excesivamente bajo, y debe aumentarse en la mayoría de las configuraciones debido a que pueden surgir incidencias asociadas con la interrupción de las solicitudes AJAX (es decir, hipereventos). Estas incidencias pueden evitarse si se asegura que el tiempo de espera de Keep-Alive en el servidor es mayor que para el cliente.  En otras palabras, el cliente debe cumplir con el tiempo de espera y cerrar la conexión en vez de que lo haga el servidor.  La mayoría de las veces los problemas ocurren (en Internet Explorer más que en otros navegadores), cuando el navegador intenta utilizar una conexión (particularmente para una PUBLICACIÓN) que se supone que estaría abierta.  Consulta los siguientes valores recomendados de KeepAlive y KeepAliveTimeout para un servidor web HealthShare. Para habilitar KeepAlive en Apache, realiza los siguientes cambios en httpd.conf: ![](/sites/default/files/inline/images/images/Apache6.png) ### CSP Gateway Para el parámetro KeepAlive de CSP Gateway, deja el valor predeterminado de No Action, porque el estado de KeepAlive se determina por los encabezados de respuesta HTTP de cada solicitud.
Artículo
Estevan Martinez · 30 jul, 2019

Estructura Interna de los Bloques de Bases de Datos en Caché (Parte 2)

¡Hola a tod@s!Este artículo es la continuación de mi artículo anterior, donde expliqué cómo es la estructura de una base de datos en Caché. En ese artículo describí los tipos de bloques, las conexiones que existen entre ellos y su relación con los globales. Como el artículo era completamente teórico, realicé un proyecto que ayuda a visualizar el árbol de bloques, y en este artículo explicaré su funcionamiento muy detalladamente.Con el propósito de hacer una demostración, creé una nueva base de datos y eliminé los globales que Caché inicializa de forma predeterminada para todas las bases de datos nuevas. Crearemos un global sencillo: set ^colors(1)="red" set ^colors(2)="blue" set ^colors(3)="green" ​ set ^colors(4)="yellow" Tengan en cuenta que en la imagen se muestran los bloques que conforman el global que creamos. Este es uno sencillo, esta es la razón por la que vemos su descripción en el bloque de tipo 9 (bloque del catálogo de globales). Lo siguiente es el bloque "puntero superior e inferior" (tipo 70), ya que el árbol de globales aún no es lo suficientemente profundo, y puede utilizar un puntero para un bloque de datos que todavía cabe en un solo bloque de 8 KB. Ahora, escribiremos tantos valores en otro global que no cabrán en un solo bloque y veremos los nuevos nodos en el bloque puntero señalando hacia los nuevos bloques de datos, que no caben en el primero. Escribiremos 50 valores, cada valor tendrá una longitud de 1000 caracteres. Recuerde que el tamaño del bloque en nuestra base de datos es de 8192 bytes. set str="" for i=1:1:1000 { set str=str_"1" } for i=1:1:50 { set ^test(i)=str } ​ quit Observen la siguiente imagen: Tenemos bastantes nodos en el nivel del bloque puntero que señala los bloques de datos. Cada uno de los bloques de datos contiene punteros para el siguiente bloque (es el "enlace correcto"). Offset señala el número de bytes que están ocupados en este bloque de datos. Tratemos de simular una separación de los bloques. Añadiremos tantos valores al bloque que su tamaño final superará los 8KB, lo cual provocará que el bloque se divida en dos. Ejemplo del código: set str="" for i=1:1:1000 { set str=str_"1" } set ^test(3,1)=str set ^test(3,2)=str ​ set ^test(3,3)=str El resultado puede verse a continuación: El bloque 50 se dividió y ahora está lleno de datos nuevos. Los valores que se reemplazaron ahora se encuentran en el bloque 58, y en el bloque puntero ahora aparece un puntero para este bloque. Los otros bloques permanecen sin ningún cambio. Un ejemplo con cadenas largas Si utilizamos cadenas con más de 8KB (el tamaño del bloque de datos), obtendremos bloques de "datos grandes". Por ejemplo, podemos simular una situación de este tipo escribiendo cadenas con 10,000 bytes. Ejemplo del código: set str="" for i=1:1:10000 { set str=str_"1" } for i=1:1:50 { set ^test(i)=str ​ } Echemos un vistazo al resultado: Como resultado, la estructura de los bloques en la imagen se mantuvo igual, ya que no agregamos nuevos nodos globales, sino que solo modificamos los valores. Sin embargo, el valor de Offset (el número de bytes ocupados) cambió para todos los bloques. Por ejemplo, el valor de Offset para el bloque #51 ahora es 172 en lugar de 7088. Ahora está claro que el nuevo valor no cabe en el bloque ya que, desde el puntero hasta el último byte de datos debería ser diferente pero, ¿dónde están nuestros datos? Por el momento, mi proyecto no es compatible con la posibilidad de mostrar información sobre los "bloques grandes". Utilicemos la herramienta ^REPAIR para obtener información sobre el contenido nuevo en el bloque #51. Permítanme explicar con más detalle el funcionamiento de esta herramienta. Vemos un puntero que señala como el bloque correcto al #52, y el mismo número se especifica en el bloque puntero principal del siguiente nodo. La compilación de los globales se establece con el tipo 5. El número de nodos que tienen cadenas largas es 7. En algunos casos, el bloque puede contener tanto valores de datos para algunos nodos como cadenas largas para otros, todo ello dentro de un mismo bloque. También vemos que la siguiente referencia del puntero debe esperarse al inicio del siguiente bloque. Respecto a los bloques de cadenas largas: vemos que la palabra clave "BIG" se especifica como el valor del global. Lo que esta palabra nos indica es que los datos se almacenan en "bloques grandes". La misma línea incluye la longitud total que tiene la cadena y la lista de los bloques que almacenan este valor. Echemos un vistazo al "bloque de cadenas largas", el bloque #73. Desafortunadamente, este bloque aparece codificado. Sin embargo, podemos observar que nuestros datos se encuentran después de la información de servicio, a partir del bloque del encabezado (que siempre tiene una longitud de 28 bytes). Conocer el tipo de datos hace que la decodificación del contenido del encabezado sea bastante sencilla: PositionValueDescriptionComment0-3E4 1F 00 00Offset pointing at the end of dataWe get 8164 bytes, plus 28 bytes of the header for a total of 8192 bytes, the block is full.418Block typeAs we remember, 24 is the type identifier for long strings.505CollateCollate 5 stands for “standard Caché”8-114A 00 00 00Right linkWe get 74 here, as we remember that our value is stored in blocks 73 and 74 Permítanme recordarles que los datos del bloque 51 solo ocupan 172 bytes. Esto sucedió cuando guardamos valores grandes. Por ello, parece como si el bloque estuviera casi vacío con solo 172 bytes de datos útiles y sin embargo, ¡ocupa 8KB! Está claro que en una situación como esta el espacio libre se llenará de valores nuevos, pero Caché también nos permite comprimirlos como un global. Por esta razón, la clase %Library.GlobalEdit tiene el método CompactGlobal. Para comprobar la eficacia de este método utilizaremos nuestro ejemplo con un gran volumen de datos, por ejemplo, al crear 500 nodos. Esto es lo que obtuvimos: kill ^test for l=1000,10000 { set str="" for i=1:1:l { set str=str_"1" } for i=1:1:500 { set ^test(i)=str } } quit En la siguiente imagen no se muestran todos los bloques, pero este punto debe quedar claro. Tenemos muchos bloques de datos, pero con un menor número de nodos. Si ejecutamos el método CompactGlobal: write ##class(%GlobalEdit).CompactGlobal("test","c:\intersystems\ensemble\mgr\test") Echemos un vistazo al resultado. El bloque puntero ahora solamente tiene 2 nodos, lo cual significa que todos nuestros valores se encuentran en esos dos nodos, mientras que inicialmente teníamos 72 nodos en el bloque puntero. Por lo tanto, nos deshicimos de 70 nodos y así redujimos el tiempo de acceso hacia los datos cuando pasan por el global, ya que se necesitan menos operaciones para leer los bloques. El método CompactGlobal acepta varios parámetros, como el nombre del global, la base de datos y el valor que indica la capacidad de llenado, el 90% lo hace de forma predeterminada. Y ahora vemos que Offset (el número de bytes ocupados) es igual a 7360, lo cual se encuentra alrededor de ese 90%. Algunos parámetros de salida de la función: el número de megabytes procesados y el número de megabytes después de la compresión. Anteriormente, los globales se comprimían con ayuda de la herramienta ^GCOMPACT, que ahora se considera obsoleta. Cabe destacar que una situación donde los bloques solo se llenan parcialmente es bastante normal. Por otra parte, en ocasiones es posible que no deseemos comprimir los globales. Por ejemplo, si su global se leyó casi completamente y tuvo pocas modificaciones, la compresión puede ser útil. Pero si el global se modifica todo el tiempo, cierta escasez en los bloques de datos evita el problema de tener que dividir los bloques con mucha frecuencia, y el almacenamiento de los nuevos datos será más rápido. En la siguiente parte de este artículo revisaré otra característica de mi proyecto, la cual se implementó durante el primer hackatón que se realizó en la escuela de InterSystems de 2015, un diagrama de distribución para los bloques en las bases de datos y su aplicación práctica. ¡Espero que les haya resultado útil!
Artículo
Jose-Tomas Salvador · 31 mar, 2020

Configurar un entorno para utilizar Docker en máquinas virtuales Ubuntu sobre Hyper-V de Windows 10

Esta vez quiero hablar de algo que no es específico de InterSystems IRIS, pero que creo que es importante si quieres trabajar con Docker y tu máquina de trabajo es un PC o portátil con Windows 10 Pro o Enterprise. Como probablemente sabes la tecnología de contenedores viene básicamente del mundo Linux y, a día de hoy, es en los hosts que corren Linux donde pueden mostrar su máximo potencial. Los que usamos Windows vemos que tanto Microsoft como Docker han hecho grandes esfuerzos estos últimos años y nos permiten correr contenedores Linux en nuestro sistema Windows de una manera muy sencilla... pero no está soportado para entornos productivos y, aquí viene el gran problema, no es fiable si queremos mantener persistencia de datos fuera del contenedor, en el sistema host,... debido principalmente a las importantes diferencias entre los sistema de archivos de Windows y Linux. Al final el propio Docker for Windows utiliza una pequeña máquina Linux virtual (MobiLinux) sobre la que realmente se levantan los contenedores.... lo hace de forma transparente para el usuario de windows... y de hecho funciona muy bien hasta que, como digo, quieren hacer que tus bases de datos sobrevivan más allá de la vida del contenedor... En fin,... que me enrollo,... el caso es que muchas veces, para evitar problemas y simplificar, lo que se precisa es de un sistema Linux completo... y, si nuestra máquina es Windows, la única forma de tenerlo es vía una máquina virtual. Al menos hasta que salga WSL2 en Windows 10 en unos meses, pero eso es otra historia. En este artículo te voy a contar, paso a paso, como instalar un entorno en el que puedas trabajar con contenedores Docker sobre un Ubuntu en tu servidor Windows. Vamós allá... 1. Activa Hyper-V Si no lo tienes ya activado, entra en añadir características de Windows y activa Hyper-V. Tendrás que reiniciar. 2. Crea una máquina virtual Ubuntu sobre Hyper-V Creo que no hay forma más fácil de crearse una máquina virtual (MV). Simplemente abre la venta de Administrador de Hyper-V, ve a la opción Creación Rápida... (a la derecha de la ventana) y crea tu máquina virtual en alguna de las versiones de Ubuntu que te ofrezca (podrías bajarte el iso de otra distribución de Linux y crearte la MV con una distro distinta). En mi caso he elegido la última versión disponible de Ubuntu, la 19.10. En todo caso, lo mismo que hagamos vale para la 18.04. En unos 15 o 20 minutos, según tarde en bajarse la imagen, tendrás tu nueva máquina virtual creada y lista. Importante: Deja las opciones de conmutador por defecto (Default Switch). Esto te dará acceso a Internet tanto desde el host windows como desde la máquina virtual. 3. Crea una red local Uno de los problemas de utilizar máquinas virtuales que yo me he encontrado a menudo tiene que ver con la configuración de red... unas veces funciona, otras no, funciona si estoy con Wi-fi pero no si estoy con cable o al revés,... si tenemos una VPN en el host windows, a lo mejor no nos va el internet en la MV o no tenemos acceso entre la MV y mi host (Windows),... en fin, un poco locura. Genera inseguridad si utilizas tu máquina para hacer desarrollo, pequeñas demos o presentaciones en las que muy probablemente no es tan importate tener acceso a internet como que la comunicación entre tu host y tu(s) MV(s) funcione de forma fiable. Con una red local ad-hoc, compartida por tu host Windows y tus máquinas virtuales, lo solucionas. Para comunicarse entre ellas, utilizas esa red y listo. Asignas IPs fijas a tu host y a las MVs que vayas creando y ya lo tienes. Hacerlo es muy fácil con estos pasos que te voy a dar. Simplemente debes ir a "Administrador de Conmutadores Virtuales" que encontrarás en tu Adminitrador de Hyper-V: Una vez dentro, ve a la opción Nuevo Conmutador de red virtual (vendría ser una nueva tarjeta de red): Nos aseguramos eso sí que sea una Red Interna, le damos el nombre que nos guste y el resto de opciones por defecto: Si ahora nos vamos al Panel de Control de Windows --> Centro de Redes y Recursos compartidos, veremos que tenemos ahí el conmutador que acabamos de crear: 4. Configura la Red Local compartida por el Host y las Máquinas Virtuales Ahora ya puedes terminar de configurar tu nueva red local... coloca el cursor sobre la conexión asociada a Mi Nuevo Conmutador LOCAL, pincha y ve a propiedades y de ahí al protocolo IPv4 para asignar una IP fija: Importante: La IP que asignes aquí, será la IP de tu host (Windows) en esta subred local. 5. Asocia y configura la nueva red local a la máquina virtual Ahora vuelve a la ventana del Administración de Hyper-V. Si tienes la MV arrancada debes pararla. Una vez parada, entra en su configuración y añádele el nuevo conmutador interno: (Nota.- En la imagen verás que hay otro conmutador, el Hyper-V Conmutador INTERNO , es para otra subred que tengo creada en mi caso. Pero no es necesario para este ejemplo) Cuando le des a Agregar, sólo te quedará seleccionar el conmutador que has creado antes: Bien, pues hecho esto... le das a Aplicar y Aceptar y listo. Ya puedes iniciar y entrar de nuevo en tu máquina virtual para terminar de configurar la conexión interna. Para ello, una vez rearrancada la máquina, pincha en el icono de red (arriba a la derecha) y verás que tienes 2 redes: eth0 y eth1. La eth1 aparece apagada... por ahora: Entra en la configuración de Ethernet (eht1) e indica una IP fija en esa subred local, por ejemplo: 155.100.101.1, la máscara 255.255.255.0 y listo. Ya tienes tu máquina virtual, identificada con la IP 155.100.101.1 en la misma subred que el host. 7. Permite el acceso a Windows 10 desde tu máquina virtual Lo que probablemente te encuentres es que Windows 10 no te permite conexiones desde otros servidores y, para él, la máquina virtual que acabas de crear es precisamente eso, un servidor externo... así que tendrás que añadir una regla en el Firewall para poder conectarte desde estas máquinas virtuales. ¿Cómo? Muy fácil, busca Firewall de Windows Defender en el Panel de Control de Windows, ve a Configuración Avanzada y crea una nueva Regla de Entrada: Indica un rango o rangos de puertos... (o también puedes indicar todos los puertos)... Como acción queremos que se permita la conexión... Para todos los tipos de red... Le das un nombre a la regla... E importante, inmediatamente a continuación, abre las propiedades de la regla que acabas de crear y limita el ámbito de aplicación, para que sólo se aplique en conexiones dentro de tu Subred local... 8. LISTO. Instala Docker y cualquier otra aplicación en tu nueva Máquina Virtual Ubuntu Una vez que hayas pasado por todo el proceso de instalación y tengas tu nueva MV lista y actualizada, con acceso a internet, etc... puedes instalarte las aplicaciones que quieras, ... como mínimo Docker, que de eso se trata en este caso, también puedes instalarte un cliente VPN corporativo, VS Code , Eclipse+Atelier... En concreto, para instalar Docker, entra en tu MV y sigue las instrucciones que verás aquí: https://docs.docker.com/install/linux/docker-ce/ubuntu/ Asegúrate de que Docker funciona, bájate alguna imagen de prueba, etc... Una vez que estés seguro de que todo está bien, estás listo. Y bueno, con esto... ¡ya lo tienes!, ahora podrás tener contenedores ejecutándose a pleno potencial en tu máquina virtual Ubuntu, con la que te podrás conectar desde tu host Windows 10, desde un navegador u otra aplicación y a la inversa, desde la máquina o máquinas Ubuntu, a tu host Windows 10. Todo ello utilizando direcciones IP locales en tu subred, que funcionarán independientemente de si tienes una VPN conectada o no, o de si estás por Wi-Fi o por cable. Ah... un último consejo. Si quieres intercambiar ficheros entre Windows 10 y tus máquinas virtuales, una opción muy sencilla y práctica es WinSCP. Es gratuito y funciona muy bien. Bueno, seguro que hay otras configuraciones... pero esta me ha servido a mí. Espero que te ayude también a tí y te evite dolores de cabeza. ¡Happy Coding! ¡ Hola Salva ! ( @Jose-Tomas.Salvador ) Sabes que no soy fanático de Docker Desktop for Windows. Pero me motivaste a probarlo.Mi contribución al concurso Open Exchange WebSocket Client JS with IRIS Native API as Docker Micro Server funciona tan bien en Windows como en Linux. Tiene un poco de ajuste de código común .Una sorpresa positiva. ¡ Gracias por la sugerencia ! ¡Hola Robert! Me alegro de que te haya servido!! Ahora toca probar con el soporte nativo a Linux que incorpora la última versión de Windows... veremos que tal el WSL2. Un fuerte abrazo. Este artículo está etiquetado como "Mejores prácticas" ("Best practices") (Los artículos con la etiqueta "Mejores prácticas" incluyen recomendaciones sobre cómo desarrollar, probar, implementar y administrar mejor las soluciones de InterSystems).
Artículo
Joel Espinoza · 2 jul, 2019

Desarrollar un backend de servicios REST para una aplicación Angular 1.x con Caché - comencemos aquí

Resulta que un día estás trabajando en Widgets Direct, distribuidor líder de widgets y accesorios, y tu jefe te pide que desarrolles un nuevo portal dedicado a los clientes, el cual permita que la cartera de clientes tenga acceso a la siguiente generación de widgets... y él quiere que utilices Angular 1.x para comunicarte con el servidor Caché del departamento. Solamente hay un problema: Nunca has utilizado Angular y no sabes cómo hacer que se comunique con Caché (o IRIS).En esta guía veremos cómo se realiza todo el proceso de configuración de un conjunto de subsistemas en Angular, el cual se comunica con un backend de Caché utilizando JSON para llamar un API REST. Esta es la página principal de una serie que incluye varias partes sobre la creación de una aplicación con Documentos-Angular1x-REST-JSON-Caché. La lista de artículos disponibles es la siguiente:ArtículoEnlaceParte 1 - Configuración y Hello Worldhttps://community.intersystems.com/post/lets-write-angular-1x-app-cach%C3%A9-rest-backend-part-1-many Parte 2 - "De modo que tu jefe te gritó por enviarle una página web con un Hello World sin formato" o por trabajar con JSONhttps://community.intersystems.com/post/lets-write-angular-1x-app-cach%C3%A9-rest-backend-part-2Parte 3 - Vamos a iterar con algunos conjuntos y ng-repeathttps://community.intersystems.com/post/lets-write-angular-1x-app-cach%C3%A9-rest-backend-part-3Parte 4 - Aplicación en la IUhttps://community.intersystems.com/post/lets-write-angular-1x-app-cach%C3%A9-rest-backend-part-4Parte 5 - Manipulemos nuestros Widgetshttps://community.intersystems.com/post/lets-write-angular-1x-app-cach%C3%A9-rest-backend-part-5Parte 6 - Regresemos a los Datos persistenteshttps://community.intersystems.com/post/lets-write-angular-1x-app-cach%C3%A9-rest-backend-part-6Parte 7 - Las cosas van a rompersehttps://community.intersystems.com/post/lets-write-angular-1x-app-cach%C3%A9-rest-backend-part-7Parte 8 - Puntos extra por el rompimientohttps://community.intersystems.com/post/lets-write-angular-1x-app-cach%C3%A9-rest-backend-part-8Parte 9 - Veamos un poquito de CRUDhttps://community.intersystems.com/post/lets-write-angular-1x-app-cach%C3%A9-rest-backend-part-9Parte 10 - Reconociendo los méritos de EDIThttps://community.intersystems.com/post/lets-write-angular-1x-app-cach%C3%A9-rest-backend-part-10Parte 11 - Modificar nuestro formulariohttps://community.intersystems.com/post/lets-write-angular-1x-app-cach%C3%A9-rest-backend-part-11Parte 12 - WWWidgetshttps://community.intersystems.com/post/lets-write-angular-1x-app-cach%C3%A9-rest-backend-part-12Parte 13 - Cómo crear un Widgethttps://community.intersystems.com/post/lets-write-angular-1x-app-cach%C3%A9-rest-backend-part-13Requisitos previosEn estos ejemplos se utilizó Caché 2016.2, donde se implementó una nueva sintaxis para manejar el contenido en JSON. En Caché 2016.1 había soporte preliminar para JSON, pero la sintaxis es diferente e incompatible con las versiones posteriores. El código que se mostró en estos ejemplos fue escrito de una forma que pueda adaptarse fácilmente a la sintaxis de 2016.1, ya que no se utilizó ninguna de las funciones extendidas de JSONAunque hay cierto grado de compatibilidad con REST y JSON en las versiones anteriores, recomiendo ampliamente conocer más sobre la versión 2016.1 como un requisito mínimo.En este tutorial no veremos Angular detalladamente. Pero hay muchos cursos excelentes sobre los Principios básicos de Angular, que están disponibles de forma gratuita en línea y se mencionan en las Partes 1 y 2. Tanto AngularJS como Angular 2 y superiores son ampliamente compatibles con la tecnología InterSystems, ya sea Caché 2016.2 como IRIS 2019.1 permiten implementar APIs REST y WebServices con rapidez y simplicidad.EnlacesEl código que se utilizó en estos ejemplos podrá consultarse en Github poco tiempo después de que se publique el artículo. Puedes tener acceso a él desde https://github.com/iscChris/CacheRESTStack
Artículo
Bernardo Linarez · 29 ago, 2019

Conectarse a Caché con SQuirreL SQL, un cliente externo de SQL

¡Hola a tod@s!El Portal de Administración del Sistema Caché incluye una potente herramienta de consultas en SQL basada en la web, aunque para algunas aplicaciones lo más conveniente es utilizar un cliente dedicado SQL que esté instalado en la PC del usuario.SQuirreL SQL es un conocido cliente SQL de código abierto construido en Java, que utiliza JDBC para conectarse a un DBMS. Como tal, podemos configurar SQuirreL para que se conecte a Caché usando el controlador JDBC en Caché.Encontrar el controlador JDBC de Caché en archivos JAREl archivo JAR que contiene el controlador JDBC de Caché se instala automáticamente por el instalador de Caché cuando se instala una instancia completa de Caché o cuando se instalan únicamente los componentes de cliente. Puede encontrarse en el directorio lib, debajo del directorio de instalación principal.Para esta instalación Caché del cliente en un equipo PC con Windows, el archivo JAR puede encontrarse en la siguiente ruta:C:\InterSystems\CACHEClient\lib\cachejdbc.jarInstalación de SQuirreL SQLSQuirreL SQL puede descargarse desde el sitio web principal de SQuirreL:http://www.squirrelsql.org/Siga las instrucciones del sitio web para instalar SQuirreL SQL y compruebe que funciona adecuadamente.Añadir una entrada para el controlador en SQuirreL SQLAbra SQuirreL SQL y seleccione la pestaña "Drivers" que se encuentra en el lado izquierdo de la ventana. Haga clic en el icono “+” para crear una nueva entrada del controlador.En el cuadro de diálogo "Add Driver", seleccione la pestaña "Extra Class Path" y haga clic en "Add" para añadir una nueva entrada para el archivo JAR del controlador JDBC de Caché.En la parte superior de la ventana "Add Driver", introduzca un nombre para el controlador como “Caché 2016.1”. Al crear una conexión hacia una base de datos se mostrará una URL de ejemplo, la cual sirve como una excelente referencia. Para "Example URL", introduzca este ejemplo de una URL:jdbc:Cache://127.0.0.1:56772/Samples O bien, puede utilizar una URL que etiquete cada parte de la URL:jdbc:Cache//[HOST NAME OR IP]:[SUPERSERVER PORT]/NAMESPACEHaga clic en el botón "List Drivers" que se encuentra a la derecha de la ventana "Add Drivers" y, a continuación, seleccione "com.intersys.jdbc.CacheDriver" en la lista desplegable Class Name.Más información sobre la configuración de la conexión JDBC y las propiedades del controlador JDBC de Caché, en la siguiente documentación:http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=BGJD_connecting#BGJD_connecting_urlhttp://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=BGJD_connecting#BGJD_connecting_connpropsHaga clic en "OK" para guardar la nueva entrada del controlador.Incorporar una entrada para la conexión (Alias)En SQuirreL SQL, un perfil de conexión para el servidor de una base de datos específica se llama Alias. Seleccione la pestaña "Aliases" que se encuentra en la parte izquierda de la ventana principal y haga clic en el icono "+" para añadir un nuevo alias.En la ventana "Add Alias", introduzca un nombre para este Alias. Seleccione nuestro nuevo controlador en el menú desplegable.Después de seleccionar el controlador, el cuadro URL se completará automáticamente utilizando la URL de ejemplo que añadimos a la configuración del controlador. Edite esta URL para utilizar el nombre de host o dirección IP correctos y el número de puerto del super servidor para el servidor de Caché, y configure el espacio de nombres correcto.Introduzca el nombre de usuario y la contraseña de un usuario de Caché, que tenga los privilegios SQL adecuados.Haga clic en el botón "Test" y compruebe que la conexión se realizó correctamente.Haga clic en OK para guardar el nuevo Alias.Conectarse a CachéAhora puede conectarse al servidor de la base de datos haciendo doble clic en la entrada de la pestaña Alias que se encuentra en la ventana principal.Para obtener instrucciones sobre cómo utilizar SQuirreL SQL para ejecutar consultas, visualizar esquemas de información y realizar otras tareas, consulte la documentación de la aplicación en el sitio web de SQuirreL SQL. ¿Como se pueden crear los usuarios? Hola Paola ! Si te refieres a los usuarios de Caché / IRIS / Ensemble, estos se crean en la SMP (System Management Portal), en la opción System Administration > Security > Users. Cuando instalas el producto en el modo de seguridad intermedio, normalmente te pide que ingreses una contraseña, esta será asignada a los usuarios SuperUser, _SYSTEM y Admin. Puedes usar alguno de ellos para realizar directamente consultas SQL en Squirrel, pero también puedes ingresar con ellos al SMP para crear un usuario particular que será usado para usar Squirrel. Muchos éxitos.
Artículo
Robert Cemper · 1 feb, 2022

El futuro de ObjectScript

Si desarrollas en IRIS, te enfrentas a dos fenómenos principales: un motor de almacenamiento de datos increíblemente rápido y con un excelente diseño un lenguaje para trabajar en este motor de almacenamiento, llamado ObjectScript Motor de almacenamiento Se trata de un concepto de almacenamiento organizado jerárquicamente y se consideró "anticuado" cuando "relacional" era la palabra de moda del momento.Sin embargo, si usas sus fortalezas, con su sencilla y eficiente construcción, supera a todos sus competidores "gigantes".Y esto sigue siendo cierto desde el día 1. Me sorprende cómo esos competidores copiaron a lo largo del tiempo varias funciones de este motor de almacenamiento, confirmando indirectamente la calidad del concepto básico. ObjectScript Mirando el lenguaje, es bastante fácil separar el subconjunto perfectamente delimitado de elementos del lenguaje que manipulan y navegan por el motor de almacenamiento, que ha visto solo algunas extensiones y ajustes a lo largo de su vida. Yo llamo a esto el NÚCLEO.Sus compañeros son la Navegación (en su mayoría similar a otros lenguajes) y la Estética (todo para manipular el contenido de los datos). La navegación es un requisito estructural inevitable, pero ha tenido varias mejoras, principalmente para comodidad de los programadores. No es obligatorio, pero es similar a los lenguajes más nuevos. La estética es el campo de más rápido crecimiento y hasta hoy sus propuestas me sorprenden de vez en cuando. Su existencia viene de cuando el motor de almacenamiento también formaba parte de un sistema operativo (y nada más) sobre el HW. Historia Desde el pasado, los desarrolladores solían escribir aplicaciones con ObjectScript. Incluso InterSystems hizo esto a gran escala (Interoperability, es decir, Ensemble), Analytics (es decir, DeepSee, Health*...). ObjectScript era/es el "puede con todo, hace de todo". Pero ObjectScript era más bien un "llanero solitario". El intento de tener algo más popular en el mercado fue BASIC (ya era un "caballo muerto" en ese momento). La oportunidad de saltar a la ola de JavaScript se perdió ~ hace unos 15 años. La disponibilidad de una amplia gama de adaptadores de lenguajes pudo ocultar la dimensión del problema durante algún tiempo, pero nunca alcanzó la fuerza que el principal competidor mostró cuando PL/SQL fue congelado y reemplazado por Java. Hoy en día Veo que el número de desarrolladores formados en ObjectScript se reduce con el tiempo por puros efectos demoscópicos. Encontrar y educar desarrolladores que quieran escribir en ObjectScript es un ejercicio duro, que experimenté personalmente varias veces. E incluso si son brillantes y muy hábiles, no es garantía de que se queden con uno. La demanda sigue siendo espectacular. Delante de este trasfondo, veo la llegada de Python embebido como lenguaje completo al mismo nivel que ObjectScript como un gran paso adelante con IRIS.La navegación y la estética están cubiertas y añadir las funcionalidades NUCLEARES no requiere un aprendizaje dramático. De modo que vemos un mercado mucho más grande de recursos de desarrollo especializados que acaban con la limitación que en ese aspecto teníamos con ObjectScript . Futuro Con Python, veo mucha energía nueva entrando en el mundo de IRIS. Después de todo, es un paso que algunos competidores dieron hace bastante tiempo y el inconveniente de "nunca visto en ningún otro lugar" queda superado. El paradigma: "Lo programo porque es posible"; se romperá. Nos libera de una serie de ruedas re-inventadas. ObjectScript seguirá funcionando en paralelo y tendrá un papel importante en todas las actividades cercanas al NÚCLEO de almacenamiento. Nadie (seguramente) lo usará para lógica empresarial o (espero que no) para replicar interfaces que ya están disponibles fuera a mayor escala. ObjectScript se reducirá a una dimensión que vemos hoy para C, C ++ o para código en cualquier lenguaje ensamblador. Las aplicaciones existentes no se verán afectadas. La forma en que se integró Python parece ser una garantía para una migración sin problemas y sin presión de tiempo. Los desarrolladores de ObjectScript de hoy no perderán sus trabajos. Hay suficientes tareas complejas en torno al diseño y mantenimiento de bases de datos. Pero podrían deshacerse de tareas poco atractivas (en mi opinión): estilos CSS, colorear páginas web,. . . . Sin olvidar el exigente desafío de leer y comprender el código existente e interpretar y comunicar su contenido y lógica. Los "Golf Code" (1) pueden ser divertidos, pero se trata de una lógica de negocio muy seria en muchas aplicaciones escritas tradicionales antiguas e incluso en algunas utilidades en "%SYS". Veo a los desarrolladores de hoy como los sacerdotes de ObjectScript, del mismo modo que los sacerdotes egipcios que comprenden los jeroglíficos y leen "El libro de los muertos" y celebran sus milagros. 15 de julio de 2021
Artículo
Esther Sanchez · 5 oct, 2022

Cómo aprovechar al máximo las publicaciones en la Comunidad de Desarrolladores

¡Hola Comunidad! ¿Sabéis cómo publicar en la Comunidad de Desarrolladores? ¿Y conocéis todos los tipos de publicaciones que hay? ¿Y sabéis que podéis, por ejemplo, publicar encuestas en una publicación? ¿o adjuntar PDFs? Si queréis sacar el máximo partido a las publicaciones y, por tanto, a la Comunidad... seguid leyendo, porque os vamos a contar tooooodos los detalles de las publicaciones: Reglas generales Preguntas Artículos y Anuncios Debates Reglas generales Para empezar a participar en la Comunidad, haced clic en el botón "Nueva publicación" arriba del todo en la página de inicio de la Comunidad: Aparecerá el editor para crear una Pregunta, un Anuncio, un Artículo o un Debate. Cada tipo de publicación tiene su propio conjunto de campos, unos obligatorios y otros opcionales. Primero vamos a hablar sobre los campos comunes a todos los tipos de publicación y después veremos los particulares de cada uno. Básicamente, todas las publicaciones tienen un Título*, Cuerpo*, Etiquetas y otras opciones adicionales. Todos los campos marcados con un asterisco (*) son obligatorios. Así que primero hay que elegir el tipo de publicación: Pregunta, Anuncio, Artículo o Debate. Después, hay que expresar la idea principal de forma clara y concisa y escribirla en el Título. En el Cuerpo de la publicación hay que escribir lo que quieres compartir con la Comunidad. Hay dos opciones de edición: WYSIWYG o Markdown. Elegid la que prefiráis y el resultado será el mismo, por supuesto. vs. Después de escribir el texto, hay que escoger el Grupo, que es la tecnología, producto o servicio de InterSystems del que vais a hablar en la publicación. Después, en el campo Etiquetas se pueden añadir etiquetas relacionadas con el contenido de la publicación. Hay muchas etiquetas, así que hay que elegir las adecuadas, porque otras personas utilizan las etiquetas para buscar información en la Comunidad. Después hay un enlace para ver más opciones. Estas son: Adjuntar PDF, indicando el nombre del documento. Añadir una encuesta. Hay que rellenar los campos con la pregunta, posibles respuestas, duración de la encuesta, etc. Una vez cumplimentados todos los campos, se puede visualizar la publicación en Vista previa para comprobar cómo se verá una vez publicada; se puede Guardar para seguir editándola después; o se puede Publicar. También se puede programar la publicación. Solo hay que hacer clic en la flecha hacia abajo, elegir Programar publicación y fijar el día y la hora cuando se va a publicar la publicación. Por último, hay que hacer clic en Programar publicación y se publicará el día y la hora escogida. Y básicamente estos son los campos comunes a todos los tipos de publicaciones. Preguntas Como su nombre indica, hay que escoger este tipo de publicación cuando necesitéis ayuda y/o queráis consultar algo. En la Comunidad de Desarrolladores en español hay un montón de especialistas y alguien puede haberse encontrado en vuestra misma situación. Así que no hay que dudar en preguntar... y en responder! 😉 Al escribir una pregunta, hay que formular la idea principal y escribirla como título. A continuación, elegir la "Versión del producto" que estéis utilizando, porque diferentes versiones tienen diferentes funcionalidades y clases; y una respuesta puede ser válida para unas versiones, pero no para otras. Para ser incluso más preciso, se puede indicar en el campo $ZV la versión completa que se está usando. Para obtenerla, se puede abrir Terminal y ejecutar el comando: write $ZV Se puede hacer lo mismo en el IDE que estéis usando o verlo en el "Management Portal": El resto de campos de la pregunta son los mismos que hemos descrito anteriormente. Artículos y Anuncios Los Artículos se utilizan para compartir conocimientos y/o tu experiencia. Y los Anuncios para dar una noticia, anunciar algo, etc. Estos dos tipos de publicaciones tienen los campos comunes y otros adicionales, como son: Anuncio anterior, Anuncio siguiente y Enlace a la aplicación en Open Exchange. Es decir, si el Artículo/Anuncio está relacionado con otro, se puede añadir el enlace de ese Artículo/Anuncio anterior en el campo "Artículo/Anuncio anterior" y así, al final de la publicación, se pueden ver todas las publicaciones relacionadas. No hay que abrir el Artículo/Anuncio anterior para enlazarlo con la nueva publicación, ya que se hace automáticamente. También se puede ir fácilmente de una publicación a otra con los botones de navegación situados en la parte de arriba de los artículos que están relacionados. Por último, si la publicación está relacionada con una aplicación en Open Exchange, se puede añadir el enlace al proyecto en el campo "Enlace a la aplicación en Open Exchange". Debates Si queréis comentar alguna funcionalidad o compartir vuestra experiencia o preguntar la opinión de otros desarrolladores, se puede iniciar un Debate. Este tipo de publicación tiene todos los campos comunes y también los enlaces a Publicación anterior y Siguiente publicación. ¡Y eso es todo lo que necesitáis saber para participar en la Comunidad y aprovechar al máximo todo lo que ofrece! Esperamos que os resulte útil... ¡y esperamos vuestros comentarios!
Artículo
Laura Blázquez García · 23 feb, 2025

Creando respuestas FHIR con una producción de interoperabilidad en IRIS

Cuando creamos un repositorio FHIR en IRIS, tenemos un endpoint para acceder a la información, crear nuevos recursos, etc. Pero hay algunos recursos en FHIR que probablemente no tengamos en nuestro repositorio, por ejemplo, un recurso Binary (este recurso devuelve un documento, como un PDF, por ejemplo). He creado un ejemplo en el que cuando se solicita un recurso Binary, el endpoint de FHIR devuelve una respuesta, como si existiera en el repositorio. En primer lugar, necesitamos un Namespace y un endpoint FHIR. Después, necesitamos configurar una producción de interoperabilidad que se conectará al endpoint de FHIR. Esta producción debe tener estos elementos: Business Operations: HS.Util.Trace.Operations (de hecho, esto es opcional, pero puede ser muy útil) HS.FHIRServer.Interop.Operation, con la propiedad TraceOperations establecida a *FULL* Business Service: HS.FHIRServer.Interop.Service, con la propiedad TraceOperations establecida a *FULL* y la propiedad Target Config Name con el valor del nombre de la operación HS.FHIRServer.Interop.Operation Así es como se ve la producción: Después de crear esta producción, necesitamos conectarla con el endpoint de FHIR. Por lo tanto, editamos el endpoint de FHIR y configuramos el parámetro Service Config Name con el nombre del Business Service: Ahora, si comenzamos a enviar solicitudes al repositorio FHIR, veremos todas las trazas en el Visor de mensajes: Ahora podemos tener un Business Process para controlar qué hacer con rutas específicas. En este ejemplo tenemos un Business Process que recibe cada solicitud (ahora el Business Service está conectado a este Business Process, en lugar de la Business Operation) y 2 nuevas Business Operation que realizan otras acciones que se explicarán más adelante: Veamos el Business Process llamado FHIRRouter: Si echamos un vistazo, veremos que, si la propiedad RequestPath contiene "Binary/", entonces haremos algo con esta solicitud: generar nuestra respuesta Binary personalizada. De lo contrario, enviaremos la solicitud al repositorio FHIR directamente. Veamos la secuencia llamada "Generate Binary": En primer lugar, creamos una nueva instancia de HS.FHIRServer.Interop.Response. Y obtenemos el ID del documento de la propiedad RequestPath. ¿Cómo? Cada vez que alguien quiere un recurso Binary, debe solicitarlo con el ID del documento en la ruta URL, algo así: ..../fhir/r4/Binary/XXXXX. Por lo tanto, extraemos el ID del documento de la ruta con esta expresión: $Replace(request.Request.RequestPath,"Binary/","") (No es muy elegante, pero funciona). Si tenemos un ID de documento, entonces realizamos una llamada a una Business Operation llamada Find para encontrar el nombre de archivo asociado a ese ID de documento: De hecho, esta Business Operation Find siempre devuelve el mismo nombre de archivo: Es un ejemplo de lo que podemos hacer. Si tenemos un nombre de archivo, entonces, llamamos a otra Business Operation llamada File para obtener el contenido de este archivo, codificado en base64: Y finalmente, podemos devolver 2 tipos de respuestas: Si no tenemos el contenido del archivo (porque no tenemos un ID de documento o no encontramos su nombre de archivo o contenido asociado), devolvemos una respuesta 404, con este contenido personalizado: set json = { "resourceType": "OperationOutcome", "issue": [ { "severity": "error", "code": "not-found", "diagnostics": "<HSFHIRErr>ResourceNotFound", "details": { "text": "No resource with type 'Binary'" } } ] } set json.issue.%Get(0).details.text = json.issue.%Get(0).details.text_" and id '"_context.docId_"'" set qs = ##class(HS.SDA3.QuickStream).%New() do qs.Write(json.%ToJSON()) set response.QuickStreamId = qs.%Id() set response.ContentType = "application/fhir+json" set response.CharSet = "UTF-8" Si tenemos contenido de archivo, entonces devolvemos una respuesta 200 con este contenido personalizado set json = { "resourceType": "Binary", "id": "", "contentType": "application/pdf", "securityContext": { "reference": "DocumentReference/" }, "data": "" } set json.id = context.docId set json.securityContext.reference = json.securityContext.reference_json.id set json.data = context.content.Read(context.content.Size) set qs = ##class(HS.SDA3.QuickStream).%New() do qs.Write(json.%ToJSON()) set response.QuickStreamId = qs.%Id() set response.ContentType = "application/fhir+json" set response.CharSet = "UTF-8" La clave aquí es crear un HS.SDA3.QuickStream, que contenga el objeto JSON. Y añadir este QuickStream a la respuesta. Y ahora, si probamos nuestro endpoint, si solicitamos un documento Binary, veremos la respuesta: Y si solicitamos un documento Binary que no existe (puedes probarlo sin pasar ningún ID de documento), veremos la respuesta 404: En resumen, conectando nuestro endpoint FHIR con interoperabilidad podemos hacer lo que queramos, con todas las capacidades de InterSystems IRIS.
Artículo
Alberto Fuentes · 6 jul, 2021

Cómo aprovechar las consultas y ObjectScript con el framework AppS.REST

Hace un tiempo se publicó el [paquete AppS.REST](https://community.intersystems.com/post/appsrest-new-rest-framework-intersystems-iris). AppS.REST es un *framework* para exponer fácilmente clases persistentes de IRIS como recursos REST. Las clases que tienen habilitado AppS.REST soportan operaciones CRUD con poco esfuerzo del desarrollador, acortando la brecha entre los datos persistentes en IRIS y los consumidores de datos, como una aplicación front-end de Angular. ¡Pero las clases de IRIS son mucho más que una simple definición para cargar y guardar registros individuales! Este artículo tiene como objetivo destacar algunas maneras de aprovechar el poder de IRIS en tus aplicaciones REST.  Usando la aplicación de ejemplo Phone.Contact, veremos el soporte de consultas incluido en AppS.REST, el uso de consultas de clase y finalmente los métodos ObjectScript. ## Preparación Puedes encontrar el [paquete AppS.REST en Open Exchange](http://openexchange.intersystems.com/package/apps-rest). Este artículo utilizará ejemplos de la [aplicación Sample Phonebook (disponible en github)](https://github.com/intersystems/apps-rest/blob/master/docs/sample-phonebook.md).  Ambos paquetes se pueden instalar fácilmente con Objectscript Package Manager, que se puede encontrar [aquí](https://openexchange.intersystems.com/package/ObjectScript-Package-Manager-2%C2%A0%C2%A0).  Una vez instalados, la aplicación Sample Phonebook generará algunos datos ficticios y configurará la aplicación web necesaria. Esa aplicación web reenviará todas las solicitudes a la clase Sample.Phonebook.REST.Handler. Como estoy haciendo las pruebas en mi equipo local, todas mis solicitudes http estarán en `http://localhost:52773/csp/USER/phonebook-sample/api/`, pero tu servidor y tu puerto pueden ser diferentes a los míos. Utilicé la extensión Talend API Tester de Google Chrome para probar todas estas llamadas REST, pero hay muchas buenas herramientas API disponibles. ## Cómo habilitar solicitudes simples Echemos un vistazo a la clase Model.Person, que está habilitada para REST al extender de AppS.REST.Model.Adaptor. Vemos que se muestra como el recurso "contact", como se define en el parámetro RESOURCENAME. Class Sample.Phonebook.Model.Person Extends (%Persistent, %Populate, %JSON.Adaptor, AppS.REST.Model.Adaptor) { Parameter RESOURCENAME = "contact"; Ten en cuenta que aunque la clase IRIS se llama "Person", estamos mostrando la clase como el recurso "contact" a nuestros consumidores de REST. Empezaremos con una solicitud básica de GET para demostrar como un consumidor de datos puede interactuar con este recurso:   `http://localhost:52773/csp/USER/phonebook-sample/api/contact/2` { "_id": "2", "name": "Harrison,Angela C.", "phones":[ {"_id": "2||15","number": "499-388-2049","type": "Office"}, {"_id": "2||32","number": "227-915-3954","type": "Mobile"} ]} Genial, ¡ya estamos consumiendo datos de IRIS a través de REST!  ## Consultas pre-construidas listas para usar Lo anterior es bueno para cargar una instancia particular conocida de un recurso, pero ¿qué sucede si quieres aprovechar las funcionalidades de consulta de datos de IRIS? El *framework* AppS.REST nos da algunas capacidades de consulta listas para usarse directamente. Por ejemplo, la siguiente solicitud GET busca todos los contactos: `http://localhost:52773/csp/USER/phonebook-sample/api/contact` Fíjate que tiene el mismo aspecto que la anterior, sólo que sin especificar el ID del contacto. Para solicitar contactos con un nombre determinado, podemos añadir un parámetro URL: `http://localhost:52773/csp/USER/phonebook-sample/api/contact?name[eq]=Harrison,Angela C.` [{ "_id": "2", "name": "Harrison,Angela C.", "phones":[{"_id": "2||15", "number": "499-388-2049", "type": "Office"…] }] Podemos imaginar una aplicación que permita al usuario buscar contactos por medio de las primeras letras de su apellido. Podemos conseguirlo con otro operador: `http://localhost:52773/csp/USER/phonebook-sample/api/contact?name[stwith]=Harri` Ahora recuperamos todos los contactos cuyos nombres empiezan con "Harri": [{ "_id": "2", "name": "Harrison,Angela C.", "phones":[{"_id": "2||15", "number": "499-388-2049", "type": "Office"…] },{ "_id": "47", "name": "Harrison,Yan N.", "phones":[{"_id": "47||26", "number": "372-757-5547", "type": "Mobile" },…] }] AppS.REST admite 7 operadores que se traducen directamente a SQL:         "lte": " < "         "gte": " > "         "eq": " = "         "leq": " <= "         "geq": " >= "         "stwith": " %startswith "         "isnull": " es nulo" ## Cómo exponer consultas de clase como acciones REST ¿Qué pasa si queremos aprovechar consultas más complicadas o previamente existentes en nuestra aplicación REST?  ¡Podemos exponer cualquier consulta de clase como una acción REST! La clase Person tiene una consulta llamada FindByPhone, que utiliza la tabla `PhoneNumber` para buscar personas por un phoneFragment: Query FindByPhone(phoneFragment As %String) As %SQLQuery { select distinct Person from Sample_Phonebook_Model.PhoneNumber where $Translate(PhoneNumber,' -+()') [ $Translate(:phoneFragment,' -+()') } El *framework* AppS.REST generará automáticamente la representación de JSON apropiada a partir de los ID que devuelve la consulta. Para exponer la consulta como acción REST, añadimos una entrada en el bloque XData llamado ActionMap de la clase Person para definir un endpoint "find-by-phone" en el recurso contact: ``` XData ActionMap { } ``` Esa acción ahora se dirige a la consulta de clase de FindByPhone, y espera un argumento para phoneFragment como parámetro de la URL. Ahora, si hacemos una solicitud GET al endpoint $find-by-phone del recurso de contacto, obtendremos todos los contactos que tengan un número de teléfono que coincida:http://localhost:52773/csp/USER/phonebook-sample/api/contact/$find-by-phone?phoneFragment=641 [{ "_id": "19", "name": "Ipsen,Rob Z.", "phones":[{"_id": "19||211", "number": "641-489-2449", "type": "Home"}] },{ "_id": "86", "name": "Newton,Phil Y.", "phones":[{"_id": "86||108", "number": "380-846-4132", "type": "Mobile"}] }] Cómo exponer métodos de ObjectScript como acciones REST Mostrar consultas puede ser útil, pero ¿qué pasa si queremos mostrar el código de la aplicación que está escrito en ObjectScript? Echemos un vistazo al método AddPhoneNumber de Model.Person: Method AddPhoneNumber(phoneNumber As Sample.Phonebook.Model.PhoneNumber) As Sample.Phonebook.Model.Person {     Set phoneNumber.Person = $This     $$$ThrowOnError(phoneNumber.%Save())     Quit $This } Este método de instancia se llama para añadir un número de teléfono a una persona que ya existe en la base de datos, así que vamos a ver cómo se puede mostrar como una acción REST, al igual que el ejemplo de consulta de clase anterior. En el ActionMap de Model.Person, vemos otra entrada que llama al método AddByPhone: <action name="add-phone" target="instance" method="POST" call="AddPhoneNumber"> <argument name="phoneNumber" target="phoneNumber" source="body" /> </action> A diferencia de la acción find-by-phone, esta se dirige a una instancia de Model.Person, por lo que nuestra solicitud de URL deberá incluir un ID después del recurso: http://localhost:52773/csp/USER/phonebook-sample/api/contact/2/$add-phone El *framework* AppS.REST automáticamente creará una instancia de un objeto para la persona con ID=2, y ejecutará el método de instancia en él. El Action Map define esto como una acción POST, y espera un phoneNumber en la estructura de la solicitud. AppS.REST traducirá automáticamente la estructura JSON de la publicación en una instancia de la clase Phone, ya que ésta también se extiende a AppS.REST.Model.Adaptor. Poniendo todo junto, ejecutamos POST en http://localhost:52773/csp/USER/phonebook-sample/api/contact/2/$add-phone con la siguiente estructura: {"number":"123-456-7890","type":"Mobile"} Si la solicitud tiene éxito, el número de teléfono se añade a nuestro contacto y recibimos de vuelta el objeto Person completo, con el nuevo número de teléfono incluido: { "_id": "2", "name": "Harrison,Angela C.", "phones":[ {"_id": "2||301", "number": "123-456-7890", "type": "Mobile"}, {"_id": "2||15", "number": "499-388-2049", "type": "Office"}, {"_id": "2||32", "number": "227-915-3954", "type": "Mobile"} ]} Las acciones son muy flexibles, lo que te permite mostrar casi cualquier funcionalidad de las clases a tus consumidores REST. Conclusión Espero que esta rápido vistazo de la aplicación Sample.Phonebook haya demostrado las muchas maneras en que el *framework* AppS.REST facilita la exposición rápida y sencilla de las clases de IRIS a través de REST.  AppS.REST.Model.Adaptor permite habilitar operaciones CRUD sobre REST para tus clases IRIS con muy pocos pasos manuales. Mostrar las consultas de clase y los métodos de ObjectScript se puede hacer definiendo Acciones en el ActionMap de tu clase.
Artículo
Alberto Fuentes · 27 abr, 2023

Cómo mantener contenta a la API: limpieza de las utilidades SQL

Con IRIS 2021.1, realizamos una importante revisión de nuestra API de utilidades SQL en [`%SYSTEM.SQL`](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&CLASSNAME=%25SYSTEM.SQL). Sí, eso fue hace algún tiempo, pero la semana pasada un cliente hizo unas preguntas sobre ello y entonces @Tom.Woodfin me empezó a presionar un poco ;-) para que describiera con más detalle en la Comunidad de Desarrolladores las razones de estos cambios. ¡Así que allá vamos! A principios de 2020, la API `%SYSTEM.SQL` pasó de ser un útil envoltorio de clases alrededor de unas rutinas clave a un gran número de puntos de acceso no tan coherentes para diversas funcionalidades relacionadas con SQL. Eso no debería sorprendernos, ya que el motor SQL de IRIS (y antes el motor SQL de Caché) creció mucho en funcionalidades y facilidad de uso. Como nos preocupamos mucho por la compatibilidad con las versiones anteriores, casi todos los cambios de la API fueron una incorporación neta, un nuevo método o una extensión de la estructura de un método. En algunos casos, este objetivo de compatibilidad con las versiones anteriores significó que el intento de simplificar las cosas mediante la eliminación de un argumento de métodos (para algo que ahora estaba automatizado) se diluyó al eliminar el argumento en la clase de referencia, pero dejándolo en su lugar para no alterar el código que lo utilizaba. En otras palabras, la API comenzó a parecerse a un famoso plato italiano que no es una pizza. _Si a estas alturas piensas: "¡Qué lío has hecho, eso nunca nos pasaría a nosotros!" - puedes dejar de leer aquí. _ _... Creo que aún sigues ahí :-)_ Probablemente sea justo decir que este tipo de crecimiento orgánico es inevitable y, en cierto modo, no necesariamente negativo, ya que significa que progresas y te preocupas por tus usuarios, ya que no destruyes su código al adaptar la API entre cada versión. Pero en algún momento hay que sacar la escoba y limpiar. Y eso es lo que hicimos en 2020.3 con las herramientas SQL de IRIS. ## Ordenando las cosas El primer problema que queríamos resolver era la gran cantidad de métodos, que eran demasiados para que una sola API fuera manejable. Así que empezamos por dividir la clase en una serie de clases API más pequeñas y más centradas en el paquete `%SYSTEM.SQL`, lo que significaba que podríamos conservar la agradable sintaxis abreviada de $SYSTEM. * [`SYSTEM.SQL.Functions`](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&CLASSNAME=%25SYSTEM.SQL.Functions) – por si no lo sabes, `%SYSTEM.SQL` tiene equivalentes en ObjectScript para todas las funciones escalares simples de IRIS SQL, como `%SYSTEM.SQL.ABS()`. * [`SYSTEM.SQL.PTools`](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&CLASSNAME=%25SYSTEM.SQL.PTools) – las [Herramientas para analizar rendimiento](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GSQLOPT_querytoolkit) son un grupo de utilidades para analizar en profundidad el rendimiento de las consultas individuales. Este avanzado conjunto de herramientas lo utilizan principalmente el servicio de Soporte de InterSystems y los clientes más experimentados, pero es una API muy potente y bien documentada, por lo que merece la pena echarle un vistazo si necesitas resolver esa dichosa consulta * [`%SYSTEM.SQL.Schema`](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&CLASSNAME=%25SYSTEM.SQL.Schema) – la verificación de la existencia de tablas, la importación o exportación de sentencias DDL y otras funciones para consultar o modificar objetos del esquema ahora están aquí. * [`%SYSTEM.SQL.Security`](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&CLASSNAME=%25SYSTEM.SQL.Security) – agrupa los puntos de acceso ObjectScript para los comandos GRANT y REVOKE * [`%SYSTEM.SQL.Statement`](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&CLASSNAME=%25SYSTEM.SQL.Statement) – como probablemente sabrás, InterSystems IRIS incluye una completa gestión y almacenamiento de sentencias SQL. Esta clase agrupa métodos para administrar el contenido del [Índice de sentencias](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GSQLOPT_sqlstmts), como importar y exportar planes, así como freezing y thawing, si se quiere forzar el uso de alguna en particular. * [`%SYSTEM.SQL.Stats.Table`](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&CLASSNAME=%25SYSTEM.SQL.Stats.Table) – Aquí es donde puedes recopilar [estadísticas de las tablas](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GSQLOPT_opttable#GSQLOPT_opttable_tunetable) y, opcionalmente, anularlas, importarlas o exportarlas. Es un nivel más profundo que los demás, ya que hay otros elementos sobre los que nos gustaría recopilar y gestionar estadísticas en el futuro. * [`SYSTEM.SQL.Util`](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&CLASSNAME=%25SYSTEM.SQL.Util) – No todo encaja en un cajón temático limpio, así que aquí es donde ponemos los restos: la funcionalidad que queríamos conservar de %SYSTEM.SQL, pero para la que no encontramos un lugar más adecuado. Solo un número muy reducido de tareas extremadamente comunes, como [`%SYSTEM.SQL.Explain()`](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&CLASSNAME=%25SYSTEM.SQL#Explain), se quedaron en la clase de nivel superior. Por supuesto, todavía nos preocupamos por los usuarios con un código que llama a los puntos de acceso preexistentes, por lo que hemos dejado todos los métodos originales en la clase `%SYSTEM.SQL` y los hemos marcado como internos y obsoletos usando palabras clave para los métodos, dejando una nota sobre la nueva ubicación preferida de la función. Una de las ventajas de este enfoque con nuevas clases es que no tenemos que preocuparnos de que lo nuevo se interponga en el camino de lo que ya existe. En la mayoría de nuestras API REST (como /api/deepsee/ y /api/iknow/*), introdujimos un número de versión en la propia URL de la API (por ejemplo, /api/iknow/v1/namespace/domains) para tener en cuenta estos cambios en la API. También probamos brevemente esa idea aquí, pero pensamos que quedaría muy mal en el nombre de una clase y no invitaría a los usuarios a adoptarla en vez del punto de acceso base que ya existe y está obsoleto. Los nuevos puntos de acceso también son un poco más extensos, pero al menos el token extra tiene sentido con respecto a lo que intenta conseguir. De manera implícita, esto significa que suponemos con cierto optimismo que nunca volveremos a tener que cambiar estas API, así que vamos a echar un vistazo al otro cambio importante que hemos hecho: revisar las estructuras de los métodos. ## Limpieza profunda Como hemos descrito en la introducción, no solo veíamos un número desmesurado de métodos, sino que algunos tenían una estructura desmesurada en el mismo método. Algunos buenos ejemplos eran los métodos TuneTable() y Export(), a los que se les agregaron un gran número de indicadores a lo largo de los años. Normalmente, los argumentos más interesantes son los nuevos del final, y habitualmente veía incluso a los desarrolladores más experimentados contar las comas antes de poner ese 1 o 0 para anular un valor (esperamos que decente) predeterminado. Claramente era un área que podíamos mejorar, y así lo hicimos. La primera pregunta que debemos hacernos es, obviamente, si merecía la pena mantener cada argumento. Algunos han sido superados con el tiempo o por nuevas funcionalidades, y pueden omitirse sin problemas. Después, pusimos los más importantes y obligatorios al principio y agrupamos todos los indicadores opcionales en un nuevo argumento _qualifiers_, de naturaleza similar a los calificadores utilizados en varios métodos %SYSTEM.OBJ. Admitimos un objeto dinámico o un formato heredado. El primero se puede pasar como una instancia %DynamicObject, o en formato de cadena JSON, como { "silent": 1, "logFile": "/tmp/xyz.log" }. El último utiliza el mismo formato que en `%SYSTEM.OBJ` usando barras, en el que los calificadores anteriores podrían expresarse como "/silent /logFile=/tmp/xyz.log". Este mecanismo ha sido una excelente forma de ofrecer métodos API fáciles de usar, de documentar y de evolucionar. Además de cambiar la lista de argumentos, también hicimos otras cosas más sencillas, como un uso más estandarizado de los valores de retorno %Status y los parámetros ByRef. Y por último, pero no por ello menos importante, cambiar los nombres de los métodos para que sean más uniformes y autoexplicativos, a menudo facilitado por el paso a un subpaquete con nombre razonable. ¿Sabrías decir sin mirar si el antiguo método `%SYSTEM.SQL.Export()` exportaba datos o información del esquema? ¡No tengo nada más que decir! ## Pensando en el futuro ¿Ya terminamos? Definitivamente no. Estoy muy agradecido por el tiempo que el equipo ha dedicado a revisar este cambio de especificación, implementación y pruebas, pero cuando lo lanzamos con 2020.3, ya habíamos identificado algunos casos en los que podríamos haber ido un poco más lejos o haber simplificado aún más las cosas. Continuaremos buscando oportunidades de este tipo y, ahora que la mayor parte de esta primera gran limpieza se ha llevado a cabo, podemos impulsar de forma práctica cambios más pequeños que se ajusten a las normas descritas anteriormente. Si estás buscando una razón por la que antes podías llamar a `%SYSTEM.SQL.FreezePlans(1, 3, "MyTable")` y ahora se recomienda usar `%SYSTEM.SQL.Statement.FreezeRelation("MyTable")`, espero que esto te haya sido útil. Si buscabas consejos generales sobre la evolución de la API, espero que al menos esto no te haya parecido una pérdida de tiempo :-) y estaré encantado de escuchar vuestras opiniones sobre cómo lo hicimos y cómo lo haríais vosotros de otra manera. No hay absolutamente ninguna ciencia exacta en lo anterior. Adoptamos un enfoque pragmático, para intentar que las cosas fueran más fáciles de utilizar sin cambiar los métodos por el placer de hacerlo y tratando de economizar en tiempo de desarrollo y pruebas. Esperamos que, con el tiempo, también ahorre tiempo en el aprendizaje y el uso de la API, pero sobre todo, ¡que ahorre el tiempo dedicado a contar las comas!
Artículo
Eduardo Anglada · 7 sep, 2021

ObjectScript sobre ODBC

Este es un ejemplo de código que funciona en IRIS 2020.1 y en Caché 2018.1.3 No se mantendrá sincronizado con las nuevas versiones. Y NO cuenta con el servicio de soporte de InterSystems. De vez en cuando, puedes encontrarte una situación en la que, por diferentes razones, ODBC es la única opción para acceder a un sistema remoto. Lo cual es suficiente mientras necesites examinar o cambiar tablas. Pero no puedes ejecutar directamente algunos comandos o cambiar algunos globals. En este artículo vamos a ver 3 procedimientos SQL que permiten acceder a los globals usando ODBC. SQLprocedure Ping() devuelve Server::Namespace::$ZV. Permite verificar la conexión. SQLprocedure Xcmd(<commandline>,<resultvar>) ejecuta la línea de comandos que envías y devuelve el resultado en <resultvar>. SQLprocedure Gset(<global>,<subscript>,<value>,<$data>) te permite establecer o eliminar un global. <global> es un nodo del global perteneciente al *namespace* del servidor remoto. Hay que incluir el símbolo inicial, por ejemplo, '^MyGlobal' . <subscript> representa el subíndice completo incluyendo los paréntesis, por ejemplo: '(1,3,"something",3)' . <value> es el valor que queremos establecer. <$data> controla si se establece el Nodo global o se ejecuta un ZKILL en él, por ejemplo: 1, 11 para establecer el valor. 0,10 para borrar (ZKILL) el valor. El procedimiento Gset está diseñada para hacer uso del Global Scanning descrito en otro artículo. Gset permite copiar globals a través de cualquier conexión ODBC. Instalación: - En el sistema remoto necesitas la clase que se encuentra en OpenExchange.- Además necesitas definir los procedimientos como "Linked SQL Procedures", para ello emplea este asistente: SMP>System>SQL> Wizards>Link Procedure En los ejemplos se usa el namespace rccEX.- Si quieres ejecutar la copia de globals también necesitas instalar la clase Global Scanning desde OEX Ejemplos: USER>do $system.SQL.Shell() SQL Command Line Shell [SQL]USER>>select rccEX.Ping() Expression_1 cemper9::CACHE::IRIS for Windows (x86-64) 2020.1 (Build 215U) Mon Mar 30 2020 20:14:33 EDT Verifica la existencia del global ^rcc [SQL]USER>>select rccEX.Xcmd('set %y=$d(^rcc)','%y') ok: 10 Establece algún valor en ^rcc4(1,"demo",3,4) [SQL]USER>>select rccEX.Gset('^rcc4','(1,"demo",3,4)','this is a demo',1) Expression_1 ok: ^rcc4(1,"demo",3,4) Haz una copia de ^rcc2 a ^rcc4. Primero vemos el contenido de ^rcc2: USER>>select reference,value,"$DATA" from rcc_G.Scan where rcc_G.scan(^rcc2,4)=1 Reference Value $Data ^rcc2 10 (1) 1 1 (2) 2 11 (2,"xx") 10 (2,"xx",1) "XX1" 1 (2,"xx",10) "XX10" 1 (2,"xx",4) "XX4" 1 (2,"xx",7) "XX7" 1 (3) 3 1 (4) 4 11 (4,"xx") 10 (4,"xx",1) "XX1" 1 (4,"xx",10) "XX10" 1 (4,"xx",4) "XX4" 1 (4,"xx",7) "XX7" 1 (5) 5 1 16 Rows(s) Affected Ahora ejecuta la copia del contenido de ^rcc2 en ^rcc4: [SQL]USER>>select rccEX.Gset('^rcc4',reference,value,"$DATA") from rcc_G.Scan where rcc_G.scan('^rcc2',4)=1 Expression_1 ok: ^rcc4 ok: ^rcc4(1) ok: ^rcc4(2) ok: ^rcc4(2,"xx") ok: ^rcc4(2,"xx",1) ok: ^rcc4(2,"xx",10) ok: ^rcc4(2,"xx",4) ok: ^rcc4(2,"xx",7) ok: ^rcc4(3) ok: ^rcc4(4) ok: ^rcc4(4,"xx") ok: ^rcc4(4,"xx",1) ok: ^rcc4(4,"xx",10) ok: ^rcc4(4,"xx",4) ok: ^rcc4(4,"xx",7) ok: ^rcc4(5) 16 Rows(s) Affected Un agradecimiento especial para @Anna.Golitsyna por inspirarme a publicar esto.