Api REST/Mongo WireListener (Parte II - Utilización)
Esta publicación es la continuación de la correspondiente a la parte de configuración de la Api REST/Mongo de Informix.
En esta parte, presento algunos ejemplos utilizando la Api REST/Mongo de Informix. Es importante tener actualizada la versión de la herramienta curl ya que las versiones viejas pueden llegar a tener comportamientos diferentes.
Api REST
He incluído ejemplos para cada uno de las diferentes funciones de HTTP: GET, PUT, POST y DELETE.
Digamos que queremos consultar una tabla estructurada o SQL (No una collection de Mongo)
La salida vendrá siempre en formato JSON, por más que lo que estemos consultando sea una tabla SQL. Si vemos el esquema de la tabla que vamos a consultar a continuación utilizando el dbschema:
dbschema -d propiedades -t tipoprop
DBSCHEMA Schema Utility INFORMIX-SQL Version 12.10.FC9DE
{ TABLE "informix".tipoprop row size = 20 number of columns = 2 index size = 9 }
create table "informix".tipoprop
(
idtipoprop serial not null ,
desctipoprop varchar(15),
primary key (idtipoprop) constraint "informix".pk_tipoprop__idtipoprop
);
curl -X GET 'http://myhost.myempresa.com.ar:27018/propiedades/tipoprop/'
[{"_id":{"$oid":"5b2ba960c004f844dcb62ff2"},"idtipoprop":1,"desctipoprop":"PH"},
{"_id":{"$oid":"5b2ba960c004f844dcb62ff3"},"idtipoprop":2,"desctipoprop":"casa"},
{"_id":{"$oid":"5b2ba960c004f844dcb62ff4"},"idtipoprop":3,"desctipoprop":"departamento"},
{"_id":{"$oid":"5b2ba960c004f844dcb62ff5"},"idtipoprop":4,"desctipoprop":"negocio"}]
Digamos que queremos consultar uno de los registros de la tabla
Por ej. BDD: propiedades, Tabla: TipoProp, Valor: "casa" (desctipoprop="casa").
En JSON, la condición sería:
{desctipoprop:"casa"}
Acá hay un tema, hay que especificarlo encoded. Dado que muchas veces las sentencias URL suelen incluir caracteres que están fuera del ASCII set, la sentencia debe ser convertida a un formato ASCII válido. Entonces { corresponde a %7B, : a %3A y } a %7D.
Para facilitarnos un poco la escritura de la URL, podemos utilizar algún conversor on-line, por ej: https://www.urlencoder.org/ (setear el Do Not encode new lines -- es para que no agregue los fin de línea).
La comunidad Mongo utiliza las comillas dobles y no las simples. Esto es importante al momento de ejecutar la consulta, ya que necesitaremos encerrar entre comillas simples toda la sentencia.
Introducimos la cláusula escrita en JSON: {desctipoprop:"casa"}, hacemos click en >ENCODE<, y obtenemos: %7Bdesctipoprop%3A%22casa%22%7D
Además de la condición, lo que necesitamos utilizar para el filtro es utilizar la función query:
curl -X GET 'http://myhost.myempresa.com.ar:27018/propiedades/tipoprop?query=%7Bdesctipoprop%3A%22casa%22%7D'
[{"_id":{"$oid":"5b2ba960c004f844dcb62ff3"},"idtipoprop":2,"desctipoprop":"casa"}]
Digamos ahora que queremos que nos aparezcan los registros ordenados por el campo desctipoprop de forma descendiente
En JSON, la condición sería:
{desctipoprop:1}
Acá vemos que para ordenar utilizamos la función sort, donde el 0 es ascendiente, y el 1 es descendiente.
curl -X GET 'http://myhost.myempresa.com.ar:27018/propiedades/tipoprop?sort=%7Bdesctipoprop%3A1%7D'
[{"_id":{"$oid":"5b2ba960c004f844dcb62ff2"},"idtipoprop":1,"desctipoprop":"PH"},
{"_id":{"$oid":"5b2ba960c004f844dcb62ff3"},"idtipoprop":2,"desctipoprop":"casa"},
{"_id":{"$oid":"5b2ba960c004f844dcb62ff4"},"idtipoprop":3,"desctipoprop":"departamento"},
{"_id":{"$oid":"5b2ba960c004f844dcb62ff5"},"idtipoprop":4,"desctipoprop":"negocio"}]
curl -X GET 'http://myhost.myempresa.com.ar:27018/propiedades/tipoprop?sort=%7Bdesctipoprop%3A0%7D'
[{"_id":{"$oid":"5b2ba960c004f844dcb62ff5"},"idtipoprop":4,"desctipoprop":"negocio"},
{"_id":{"$oid":"5b2ba960c004f844dcb62ff4"},"idtipoprop":3,"desctipoprop":"departamento"},
{"_id":{"$oid":"5b2ba960c004f844dcb62ff3"},"idtipoprop":2,"desctipoprop":"casa"},
{"_id":{"$oid":"5b2ba960c004f844dcb62ff2"},"idtipoprop":1,"desctipoprop":"PH"}]
Digamos ahora que queremos ocultar campos y que nos aparezcan los registros ordenados
Por desctipoprop de manera ascendiente, y que no muestre el campo idtipoprop
En JSON, las funciones y condiciones serían:
fields: {idtipoprop:0} y sort: {desctipoprop:0}
Acá vemos que además del sort usamos fields para descartar la columna idtipoprop. Nuevamente lo que se "encodea" no es todo, solo las condiciones, el resto queda legible.
curl -X GET 'http://myhost.myempresa.com.ar:27018/propiedades/tipoprop?sort=%7Bdesctipoprop%3A0%7D&fields=%7Bidtipoprop%3A0%7D'
[{"_id":{"$oid":"5b2ba960c004f844dcb62ff5"},"desctipoprop":"negocio"},
{"_id":{"$oid":"5b2ba960c004f844dcb62ff4"},"desctipoprop":"departamento"},
{"_id":{"$oid":"5b2ba960c004f844dcb62ff3"},"desctipoprop":"casa"},
{"_id":{"$oid":"5b2ba960c004f844dcb62ff2"},"desctipoprop":"PH"}]
O si queremos ocultar el _id y con ordenamiento descendiente por idtipoprop, las funciones y condiciones serían:
fields: {_id:0} y sort: {idtipoprop:0}
curl -X GET 'http://myhost.myempresa.com.ar:27018/propiedades/tipoprop?sort=%7Bidtipoprop%3A0%7D&fields=%7B_id%3A0%7D'
[{"idtipoprop":4,"desctipoprop":"negocio"},
{"idtipoprop":3,"desctipoprop":"departamento"},
{"idtipoprop":2,"desctipoprop":"casa"},
{"idtipoprop":1,"desctipoprop":"PH"}]
Digamos que queremos insertar (POST) un registro en la tabla SQL (estructurada)
Insertemos el registro de id: 5, con el tipo de de propiedad: "casa"
A diferencia de las consultas el insert no se "encodea" y el parámetro que se utiliza es el -d o también puede ser --data, y un tema no menor, la data completa va encerrada entre comillas simples. Si no lo notaron hasta ahora, en MongoDB se utilizan comillas dobles.
curl -X POST 'http://myhost.myempresa.com.ar:27018/propiedades/tipoprop/' -d '{"idtipoprop":5,"desctipoprop":"casa"}'
[{"serverUsed":"myhost.myempresa.com.ar/10.0.110.222:30000","n":1,"keys":[5],"ok":1.0}]
Consultamos nuevamente aquellos documentos del tipo "casa", y encontramos dos registros ahora, en vez de uno solo
En JSON, la condición sería:
{desctipoprop:"casa"}
curl -X GET 'http://myhost.myempresa.com.ar:27018/propiedades/tipoprop?query=%7Bdesctipoprop%3A%22casa%22%7D'
[{"_id":{"$oid":"5b2ba960c004f844dcb62ff3"},"idtipoprop":2,"desctipoprop":"casa"},
{"_id":{"$oid":"5b2bb103c004f844dcb62ff6"},"idtipoprop":5,"desctipoprop":"casa"}]
Si intentamos insertar otra vez el mismo registro, nos dará error de ID repetido
curl -X POST 'http://myhost.myempresa.com.ar:27018/propiedades/tipoprop/' --data '{"idtipoprop":5,"desctipoprop":"casa"}'
{"err":"unable to insert to propiedades.tipoprop (code: -268, errmsg: Unable to insert into table tipoprop: -268 Unique constraint (informix.pk_tipoprop__idtipoprop) violated. | Unique constraint (informix.pk_tipoprop__idtipoprop) violated. (SQL Code: -268, SQL State: 23000) | ISAM error: duplicate value for a record with unique key. (SQL Code: -100, SQL State: IX000))"}
Digamos que quiero eliminar la tabla (collection) tipoprop completa
curl -X DELETE 'http://myhost.myempresa.com.ar:27018/propiedades/tipoprop/' {"msg":"dropped collection","ns":"propiedades.tipoprop","ok":true}
Al eliminar sin condición, elimina todos los documentos, y además nótese que indica que eliminó toda la collection, es decir que elimina la tabla completa directamente!.
Digamos que quiero volver a insertar los 5 registros
curl -X POST 'http://myhost.myempresa.com.ar:27018/propiedades/tipoprop/' --data '[{"idtipoprop":1,"desctipoprop":"PH"},{"idtipoprop":2,"desctipoprop":"casa"},{"idtipoprop":3,"desctipoprop":"departamento"},{"idtipoprop":4,"desctipoprop":"negocio"},{"idtipoprop":5,"desctipoprop":"casa"}]'
{"n":5,"ok":true}
Comprobamos…
curl -X GET 'http://myhost.myempresa.com.ar:27018/propiedades/tipoprop/'
[{"_id":{"$oid":"5b2bb882c004f844dcb62ffb"},"idtipoprop":1,"desctipoprop":"PH"},
{"_id":{"$oid":"5b2bb882c004f844dcb62ffc"},"idtipoprop":2,"desctipoprop":"casa"},
{"_id":{"$oid":"5b2bb882c004f844dcb62ffd"},"idtipoprop":3,"desctipoprop":"departamento"},
{"_id":{"$oid":"5b2bb882c004f844dcb62ffe"},"idtipoprop":4,"desctipoprop":"negocio"}},
{"_id":{"$oid":"5b2bb914c004f844dcb62fff"},"idtipoprop":5,"desctipoprop":"casa"}]
Como podemos ver podemos insertar múltiples documentos a la vez. Nótese por qué es tan importante que utilicemos las doble comillas para los documentos, y no comillas simples!. Seleccionando/Copiando el resultado de hacer un GET de todos los documentos de una collection, luego así tal cual está, podemos volver a insertar esos documentos eliminados en la collection con un POST.
Ahora, por más que la tabla antes era una tabla SQL (CREATE TABLE TipoProp idTipoProp SERIAL, descTipoProp VARCHAR(15));), ahora luego de haberla recreado a través de la API REST, la tabla es una collection de Mongo. Para la API REST, es indistinto, la sintaxis sigue siendo la misma.
Así es como se ve ahora la tabla:
dbschema -d propiedades -t TipoProp -ss
DBSCHEMA Schema Utility INFORMIX-SQL Version 12.10.FC9DE
{ TABLE "mongo".tipoprop row size = 4240 number of columns = 4 index size = 146 }
create collection "propiedades.tipoprop" table "mongo".tipoprop
(
id char(128) not null ,
data "informix".bson,
modcount bigint,
flags integer,
primary key (id)
) extent size 64 next size 64 lock mode row;
revoke all on "mongo".tipoprop from "public" as "mongo";
create unique index "mongo".tipoprop__id_ on "mongo".tipoprop
("informix".bson_value_lvarchar(data,'_id') ) using btree
in datadbs extent size 64 next size 64 collection '{ "ns" : { "name" : "propiedades.tipoprop.$_id_" } , "idx" : { "v" : 1 , "key" : { "_id" : [ 1 , "$string" ] } , "ns" : "propiedades.tipoprop" , "name" : "_id_" , "unique" : true , "index" : "tipoprop__id_" } }';
Digamos que ahora quiero eliminar el registro de idtiporpop: 5, solamente
En JASON, las condición sería:
{idtipoprop:5}
curl -X DELETE 'http://myhost.myempresa.com.ar:27018/propiedades/tipoprop?query=%7Bidtipoprop%3A5%7D'
{"n":1,"ok":true}
Comprobamos...
curl -X GET 'http://myhost.myempresa.com.ar:27018/propiedades/tipoprop/'
[{"_id":{"$oid":"5b2bb882c004f844dcb62ffb"},"idtipoprop":1,"desctipoprop":"PH"},
{"_id":{"$oid":"5b2bb882c004f844dcb62ffc"},"idtipoprop":2,"desctipoprop":"casa"},
{"_id":{"$oid":"5b2bb882c004f844dcb62ffd"},"idtipoprop":3,"desctipoprop":"departamento"},
{"_id":{"$oid":"5b2bb882c004f844dcb62ffe"},"idtipoprop":4,"desctipoprop":"negocio"}]
Digamos que ahora quiero actualizar la descripción del registro idtipoprop: 4, cambiándola de "negocio" a "oficina"
curl -X PUT 'http://myhost.myempresa.com.ar:27018/propiedades/tipoprop?query=%7Bidtipoprop%3A4%7D' -d '{$set:{desctipoprop:"oficina"}}'
{"n":1,"ok":true}
Comprobamos...
curl -X GET 'http://myhost.myempresa.com.ar:27018/propiedades/tipoprop?query=%7Bidtipoprop%3A4%7D'
[{"_id":{"$oid":"5b155b84c004f844dcb62fd3"},"idtipoprop":4,"desctipoprop":"oficina"}]
Como podemos ver tenemos que utilizar el operador $set para indicar que queremos modificar el campo "desctipoprop". Si no utilizamos $set, debemos incluir el registro completo, para que no elimine ningún campo.
Api Mongo
Debemos crear una BDD utilizando dbaccess en Informix. En mi caso la llamé mongodb, con unbuffered logging. Deberemos darle al usuario "mongo" permisos de conexión a la BDD nueva. A pesar que en el archivo de propiedades existe ésta propiedad: database.buffer.enable=true, la BDD no es creada automáticamente, a diferencia de la Api REST que sí la crea automáticamente (hasta haciendo una consulta de una collection y BDD que no existan, las crea a ambas). El hecho que la Api Monho no tenga éste mismo comportamiento, ha sido considerado como un defecto que será solucionado en un futuro release.
Adicionalmente al usuario "mongo" utilizado por el WireListener, necesitaremos crear en Informix un nuevo usuario con permisos de recursos a la BDD mongodb, es decir, que pueda crear objetos del tipo collections. El usuario que cree yo es "ifxaer". El usuario no es mapeado, es decir que puede NO existir a nivel de SO. Entonces el usuario "mongo" con permisos de conexión a sysmaster, será el usuario que autentica la conexión desde Robo 3T (Herramienta Mongo Shell). Una vez que se conecta, debemos comenzar a usar una BDD mongodb, y luego habrá que autenticar el usuario "ifxaer" contra la BDD mongodb. Si bien puede ser el mismo usuario "mongo", mi recomendación es que se cree un usuario diferente para trabajar en la BDD mongodb con permisos de resource.
-- Creamos el usuario "ifxaer" en Informix
DATABASE sysmaster;
CREATE USER 'ifxaer' WITH PASSWORD '****' --> Ésta es la password contra la que se autenticará la conexión desde Robo 3T.
PROPERTIES USER 'nobody';
GRANT CONNECT TO 'ifxaer';
-- Le asignamos permisos de recursos en la BDD
DATABASE mongodb;
GRANT RESOURCE TO ifxaer;
Levantamos el cliente Robo 3T, y realizamos los siguientes seteos de conexión:
Una vez que tenemos todos los datos ingresados, podemos hacer click en "Test", en ese momento se corren los testeos de conexión, le damos "close" y luego "save", para crear la conexión. Luego le damos "Connect" y sobre el server conectado le damos click derecho y "Open Shell", para abrir un shell de mongoDB. Nos aparecerá una solapa que dirá "New Shell".
En el shell le indicamos que utilizaremos la BDD mongodb: use mongodb
Para ejecutar los comandos ingresados en el mongo shell, se hace click sobre play o el triángulo verde. También le podemos dar F5 o Ctrl + Enter. Luego autenticamos el usuario "ifxaer" contra la BDD mongodb: db.auth("ifxaer","mipassword").
Si no se ven errores, el éxito de la autenticación se visualiza con un 1.
Luego creamos una collection "assets" y le insertamos valores, o bien directamente insertamos valores, ya que la collection será creada implícitamente:
db.createCollection("assets")
Apenas hago la creación, puedo comprobar que la collection "assets" acaba de ser creada por el usuario "ifxaer", simplemente haciendo un dbschema desde el server:
dbschema -d mongodb -t assets
DBSCHEMA Schema Utility INFORMIX-SQL Version 12.10.FC9DE
{ TABLE "ifxaer".assets row size = 4240 number of columns = 4 index size = 146 }
create collection "mongodb.assets" table "ifxaer".assets
(
id char(128) not null ,
data "informix".bson,
modcount bigint,
flags integer,
primary key (id)
);
revoke all on "ifxaer".assets from "public" as "ifxaer";
create unique index "ifxaer".assets__id_ on "ifxaer".assets ("informix"
.bson_value_lvarchar(data,'_id') ) using btree collection
'{ "ns" : { "name" : "mongodb.assets.$_id_" } , "idx" : { "v" : 1 , "key" : { "_id" : [ 1 , "$string" ] } , "ns" : "mongodb.assets" , "name" : "_id_" , "unique" : true , "index" : "assets__id_" } }';
Luego inserto tres registros en la collection "assets":
db.assets.insert({"type" : "SW" , "name" : "DataStage" , "brand" : "IBM" , "version" : "11.5.0.1"})
db.assets.insert({"type" : "HW" , "name" : "Netezza" , "brand" : "IBM" , "version" : "7.2.1.5" })
db.assets.insert({"type" : "SW" , "name" : "Cognos" , "brand" : "IBM" , "version" : "11.0.10" })
Hacemos una búsqueda de los tres registros insertados en la collection "assets":
db.assets.find()
/* 1 */
{
"_id" : ObjectId("5acf94a28a765430dd0e756d"),
"type" : "SW",
"name" : "DataStage",
"brand" : "IBM",
"version" : "11.5.0.1"
}
/* 2 */
{
"_id" : ObjectId("5acf94b98a765430dd0e756e"),
"type" : "HW",
"name" : "Netezza",
"brand" : "IBM",
"version" : "7.2.1.5"
}
/* 3 */
{
"_id" : ObjectId("5adf5e0f8f2783f647f083f0"),
"type" : "SW",
"name" : "Cognos",
"brand" : "IBM",
"version" : "11.0.10"
}
Podemos visualizar esos mismos registros desde el server:
echo "SELECT data::JSON, * FROM assets" | dbaccess mongodb
Database selected.
(expression) {"_id":ObjectId("5acf94a28a765430dd0e756d"),"type":"SW","name":"D
ataStage","brand":"IBM","version":"11.5.0.1"}
id 5acf94a28a765430dd0e756d
data \
IBM
modcount 0
flags 0
(expression) {"_id":ObjectId("5acf94b98a765430dd0e756e"),"type":"HW","name":"N
etezza","brand":"IBM","version":"7.2.1.5"}
id 5acf94b98a765430dd0e756e
data Y
M
modcount 0
flags 0
(expression) {"_id":ObjectId("5adf5e0f8f2783f647f083f0"),"type":"SW","name":"C
ognos","brand":"IBM","version":"11.0.10"}
id 5adf5e0f8f2783f647f083f0
data X
modcount 0
flags 0
3 row(s) retrieved.
Database closed.
Si queremos ejecutar una parte de la sentencia
Podemos seleccionar con el mouse la parte que queremos ejecutar antes de correrla. Por ej:
db.assets.find().explain("executionStats")
Más consultas utilizando el lenguaje de consulta de MongoDB
db.assets.find({type:"SW"}).sort({version:+1}) --> Es una consulta que filtra por registros que tengan type = "SW", y que los muestre ordenados por versión de manera ascendente
Una prueba final utilizando la api REST
Podemos acceder a los registros de la collection "assets", de la BDD mongodb que fueron creados a través de la api Mongo. Como el WireListener de la api REST está corriendo con el usuario "mongo", con darle permisos de connect a la BDD mongodb, será suficiente, ya que serán solamente consultas.
curl -X GET "http://myhost.myempresa.com.ar:27018/mongodb/assets"
[{"_id":{"$oid":"5acf94a28a765430dd0e756d"},"type":"SW","name":"DataStage","brand":"IBM","version":"11.5.0.1"},
{"_id":{"$oid":"5acf94b98a765430dd0e756e"},"type":"HW","name":"Netezza","brand":"IBM","version":"7.2.1.5"},
{"_id":{"$oid":"5adf5e0f8f2783f647f083f0"},"type":"SW","name":"Cognos","brand":"IBM","version":"11.0.10"}]
Si queremos ejecutar sentencias SQL desde el Mongo Shell
Tenemos que tener habilitado en el archivo de propiedades del Mongo WireListener el parámetro security.sql.passthrough=true
Por otro lado la sintaxis debe corresponderse a:
db.getCollection("system.sql").find({ "$sql": "sql_statement" })
Errores
Posibles errores que recibiremos en el log de Informix o en el cliente Robo 3T.
Si el usuario no fue creado en Informix o la clave es incorrecta
Error: PAM authorization has failed. (SQL Code: -79885, SQL State: IX000)
Para saber si un usuario fue creado:
DATABASE sysuser;
SELECT *
FROM sysmongousers
WHERE username = "ifxaer";
username ifxaer
hashed_password 95aa51af220422404b60e7be54263444
Es bastante segura su encriptación, si bien no es la más alta, utiliza SHA128.
Si el usuario existe en Informix, pero no tiene permisos en la BDD mongodb
Error: No connect permission. (SQL Code: -387, SQL State: IX000) | ISAM error: no record found. (SQL Code: -111, SQL State: IX000)
No es suficiente con que tenga permiso de connect a mongodb, sino que tiene que tener permiso de resource, es decir que pueda crear tablas en la BDD. En el mundo MongoDB, el equivalente a una tabla es una collection, y en el mundo NoSQL una de las principales características es que no son BDD estructuradas, es decir que la aplicación puede crear objetos de diferentes formatos y tipos de datos.
Referencias
SQL:
TXs: