lunes, 8 de diciembre de 2014

Introducción a WCF con GTK# y MonoDevelop

Windows Communication Foundation (WCF) es un framework que soporta aplicaciones orientadas a servicios con herramientas que facilitan la construcción y el consumo de servicios independientes de la plataforma, proporciona un modelo unificado de programación para aplicaciones distribuidas con tecnologías como: Web Services, Remoting, COM, DCOM, WSE, MSMQ,etc.

Este modelo está enfocado a desarrollar servicios orientados a procesos de negocio que los clientes pueden acceder y utilizar sin conocer los detalles de su implementación. Los beneficios de las aplicaciones orientadas a servicios son:

  1. Los servicios independientes actúan como bloques de construcción que pueden reutilizarse para la construcción de nuevas aplicaciones o servicios.
  2. Las aplicaciones en este contexto están totalmente desacopladas de los procesos de negocio y se convierten únicamente en interfaces de usuario (UI) que hacen uso de los servicios, además pueden o no estar construidas con las mismas herramientas de programación que el servicio.
  3. El éxito depende más de los procesos de negocio que de la tecnología.
  4. Los servicios son un grupo de métodos que comparten funcionalidades, esto permite que las aplicaciones respondan a los requerimientos cambiantes sin que se desarrollen desde cero esos requerimientos.

En este contexto WCF tiene dos niveles:

  1. A nivel lenguaje de programación se ve como un sistema compuesto por objetos persistentes, reglas de negocio que pueden estar en objetos de .NET o en store procedures y una interfaz de negocio que expresa las operaciones del servicio en donde se revisan las precondiciones de cada operación, se ejecutan las actividades del negocio y se regresa un resultado para el consumidor del servicio.
  2. A nivel servicio se tienen operaciones que pueden ser similares a las funciones a nivel lenguaje pero que son diseñadas para ser parte del servicio. Estas operaciones combinan varias funciones a nivel lenguaje y probablemente utilicen tipos de datos más acorde con ambientes distribuidos.

WCF proporciona funcionalidad a una amplia audiencia de clientes distribuidos que no comparten un mismo espacio de direcciones, estos clientes utilizan el servicio mediante una clase proxy. Los clientes y los servicios se comunican intercambiando mensajes que no están limitados a un conjunto particular de protocolos.

Las aplicaciones orientadas a servicios es un concepto relativo al estilo de la aplicación y a la granularidad de los servicios.

Toda la información necesaria para los clientes: qué es lo que el servicio hace, como debe ser accedido, en qué lugar está disponible, etc. WCF lo encapsula en un concepto llamado EndPoint, que es básicamente una combinación de address, binding y contract (lo que comúnmente se conoce como el ABC de WCF).

Un servicio WCF consta de los siguientes elementos:

  1. Un service host que proporciona el runtime para activar el servicio Web, los hay en tres tipos: Internet Information Services (IIS), Windows Activation Services (WAS) and selfhost managed applications.
  2. El contrato del servicio que es una interfaz a nivel lenguaje de programación que define las operaciones que serán expuestas a través del endpoint.
  3. Clases ordinarias o componentes de .NET que implementan toda la lógica de negocios.

Hay tres componentes básicos para la creación de un servicio en WCF.

  • (a) El servicio WCF
  • (b) El service host
  • (c) El cliente

Como ejemplo, para la creación de cada uno de estos componentes, voy a programar una aplicación GTK# que recibe un número entero, manda la petición al servicio y finalmente recibe su representación binaria como una cadena.

Tarea 1: Creación del servicio WCF

1-. Ejecuta MonoDevelop y crea una nueva solución del tipo “Blank Solution” con el nombre “Samples.MyFirstWCF”.

2-.A la solución “Samples.MyFirstWCF” agrega un proyecto del tipo “Library” con el nombre “Samples.MyFirstWCF.DisplayBitsService”.

3-. Al proyecto “Samples.MyFirstWCF.DisplayBitsService” agrega los siguientes elementos:

  1. Una interface con nombre IDisplayBitsServiceContract
  2. Una clase con nombre DisplayBitsServiceImplementation

4-. Antes de escribir el código es importante agregar al proyecto la referencia al ensamblado System.ServiceModel

5- Escribir el siguiente código para la interfaz IDisplayBitsServiceContract:

6-. Escribir el siguiente código para la clase DisplayBitsServiceImplementation:

La solución debe de verse como en la siguiente imagen:

Tarea 2: Creación del programa Host

1-. Agrega un nuevo proyecto a la solución Samples.MyFirstWCF del tipo Console Project con el nombre de Samples.MyFirstWCF.DisplayBitsSelfHost.

2-. Agrega una referencia al ensamblado System.ServiceModel y al proyecto Samples.MyFirstWCF.DisplayBitsService.

3-.Escribe el siguiente código dentro de la clase MainClass.

La solución debe de verse como en la siguiente imagen:

Paso 3: Creación del cliente GTK#

1-. Agrega un nuevo proyecto del tipo GTK# 2.0 Project con el nombre Samples.MyFirstWCFClient.

2-. Utilizando el diseñador de MonoDevelop creamos una interfaz gráfica como se muestra en la siguiente imagen:

3-. Una aplicación cliente de WCF puede comunicarse con un servicio WCF utilizando una clase proxy. Para generar el código de esta clase, se utiliza la herramienta svcutil (ServiceModel Metadata Utility Tool).

4-. Abrimos una terminal y tecleamos el comando svcutil utilizando como argumento el ensamblado del servicio y la opción /out para ponerle nombre al archivo y no utilizar el predeterminado.

$ svcutil Samples.MyFirstWCF.DisplayBitsService.dll /out:ServiceBitsServiceReference.cs

5-. Una vez generada la clase proxy, la agregamos a la solución GTK# para que la aplicación pueda invocar los métodos del servicio. La clase proxy implementa un channel stack del lado del cliente. Todas las respuestas recibidas desde el servicio pasan a través de este stack, por lo que para comunicarse el cliente y el servicio deben utilizar un stack y una configuración equivalente.

6-. Antes de compilar es importante agregar una referencia al ensamblado System.ServiceModel.

7-. Ahora vamos a generar el código para utilizar el proxy, en la ventana Properties, busca dentro de la categoría Button Signals un evento llamado clicked, haz doble-click o pulsa enter para que MonoDevelop genere la plantilla para el evento.

8-. Agrega el siguiente código para completar el método.

9- En el panel Solution explorer, haz click derecho en la solución Samples.MyFirstWCF, y entonces en el menú Options.

10- Configura la solución para que los proyectos Samples.MyFirstWCF.DisplayBitsSelfHost y Samples.MyFirstWCFClient empiecen cuando se ejecute la solución.

11-. Compila y ejecuta la solución, si todo está bien se verán las siguientes imágenes.

Al presionar el botón el cliente envía la petición al programa host y este le regresa el resultado correcto.

domingo, 19 de octubre de 2014

Ejemplos con Join,Let y Where utilizando LINQ con C#

LINQ cuyo acrónimo significa Language INtegrated Query es un lenguaje declarativo con una sintaxis tipo SQL para consultar, buscar y manipular datos en estructuras y colecciones de .NET. Estas operaciones denominadas query expressions se aplican a Objetos, Datatables, Archivos XML, entidades, Schemas, llaves de registro, archivos de Excel,Objetos de WMI, etc.

Desde su aparición como una extensión a la versión 3.5 de Microsoft .NET, ha sido una poderosa herramienta que unifica las técnicas de acceso a datos en un modelo orientado a objetos .NET independiente de la fuente de datos.

Para ilustrar algunos ejemplos, usaré colecciones de las siguientes clases que representan la relación entre entre una factura y sus detalles como parte de una entidad factura (esto se supone en un sistema de facturación).

Con el siguiente código cargaré algunos datos para los ejemplos:

Bien ahora unos programas como ejemplos.

El operador Join

El operador Join funciona para encontrar la intersección entre dos colecciones de datos en base a un criterio, similar al INNER JOIN de SQL. El código fuente del ejemplo es el siguiente:

En este código las colecciones a unir son:

var invoices = InvoiceData.GetInvoices ();
var details = InvoiceData.GetDetails ();

El código de la consulta utilizando Join es el siguiente:

var queryjoin = from invoice in invoices
  join detail in details on invoice.InvoiceNumber equals detail.InvoiceNumber
 select new Invoice
 {
  InvoiceNumber = invoice.InvoiceNumber,
  Created = invoice.Created,
  Details = new InvoiceDetails
  {
   InvoiceNumber = detail.InvoiceNumber,
   ProductPrice = detail.ProductPrice,
   Quantity = detail.Quantity
  }
 } ;

Para compilar y ejecutar el programa con Mono se utilizan los siguientes comandos:

  • mcs /t:library Invoice.cs InvoiceData.cs InvoiceDetails.cs /o:LinqSamples.dll
  • mcs -r:Invoice.dll Main.cs
  • mono Main.exe

Aquí la imagen del programa en ejecución.

El operador Let

Este operador permite calcular valores cuando se trabaja con múltiples colecciones de datos, en este ejemplo asignamos el valor de la propiedad Subtotal en la clase Invoice.

El código fuente del programa es el siguiente:

La consulta utilizando Let queda de la siguiente manera:

var queryLet = from invoice in invoices
 join detail in details on
 invoice.InvoiceNumber equals detail.InvoiceNumber
 let subtotal = detail.ProductPrice * detail.Quantity
 select new Invoice
 {
  InvoiceNumber = invoice.InvoiceNumber,
  Created = invoice.Created,
  Details = new InvoiceDetails
  {
   InvoiceNumber = detail.InvoiceNumber,
   ProductPrice = detail.ProductPrice,
   Quantity = detail.Quantity
  } ,
  Subtotal = subtotal
 } ;

El resultado al ejecutar este programa es el siguiente:

El operador Where

Si necesitamos múltiples criterios de selección podemos agregarlos con la palabra where justo después de los operadores let y join.

El código de la consulta utilizando Let, Join y Where es el siguiente:

El resultado al ejecutar este programa es el siguiente:

domingo, 13 de julio de 2014

Notas acerca de las aplicaciones web

La flexibilidad del modelo cliente-servidor consiste en que una aplicación se divide en dos partes: la parte del cliente que interacciona con el usuario y la parte del servidor que coordina el flujo de actividades de los clientes.

Para efectos de desarrollo el cliente o los clientes pueden ejecutarse en la misma computadora o en diferentes computadoras dentro de la misma red LAN.

Las aplicaciones cliente-servidor se basan en protocolos y estos a su vez se registran en los documentos RFC (Request for Comment). Cualquiera puede escribir un RFC y enviarlo, si es necesario este RFC puede ser implementado de manera masiva, aunque por lo general muchos protocolos se definen pero pocos acaban implementándose, este diseño abierto de las especificaciones de un protocolo permite la independencia a las aplicaciones con respecto al método que se usara para la comunicación y de esta forma se asegura que un variedad de clientes de diferentes fabricantes de software accedan a un servicio estándar, abierto y con especificaciones comunes, sin necesidad de implementar protocolos propietarios.

Un ejemplo práctico de esto es el servicio de correo electrónico donde un cliente email en Mac, un cliente email en Windows o un cliente email en UNIX pueden conectarse al mismo servicio e intercambiar mensajes entre sí, todo esto sin necesidad de hacer cambios al servicio.

La capacidad de las aplicaciones Web proviene de los datos que transportan los protocolos, no de los protocolos en sí. La seguridad no era una preocupación cuando comenzó Internet como una red que conectaba institutos de investigación, sin embargo cuando Internet se usa para aplicaciones de negocios la seguridad se vuelve la preocupación principal por lo que además de los protocolos estándar se desarrollaron los protocolos seguros, como ejemplo: sftp, https, ssh entre otros.

Cuando se construyo ARPAnet el antecesor de Internet, los primeros servicios que se necesitaban fueron la transferencia de archivos y la emulación de terminal además de los servicios de utilidades. Así que los protocolos como chargen, echo y daytime fueron desarrollados.

martes, 1 de julio de 2014

Obteniendo output parameters de procedimientos PLSQL en PostgreSQL con .NET

En .NET existen dos maneras sencillas de ejecutar una procedimiento PLpgSQL y obtener la información como resultado de su ejecución.

La primera de ellas consiste en utilizar parámetros de salida (output parameters) como argumentos del procedimiento.

Para mostrarles esta manera, en el siguiente código declaro cuatro argumentos para el procedimiento, dos de ellos como parámetros de entrada y dos de ellos como parámetros de salida.

Similar a las funciones o los métodos en otros lenguajes de programación, los procedimientos PlpgSQL son subprogramas que pueden recibir parámetros de entrada (input), salida (output) y ejecutar otros procedimientos, ademas de contar con expresiones para el control del flujo.

Los procedimientos aceptan input parameters (parámetros de entrada), que se utilizan dentro del store procedure como variables locales. También puedes especificar output parameters (parámetros de salida) que permiten que un procedimiento regrese uno o más valores escalares a la rutina que llamo ese procedimiento. Cuando se ejecuta el procedimiento los valores de los parámetros de salida son almacenados en memoria.

Para demostrar su funcionalidad, creo una tabla llamada Products con el siguiente script.

Inserto unos registros de prueba y los ordeno por la columna id de forma descendente de manera que pueda saber cual es el máximo id que me regresá el procedimiento.

Ahora ejecuto el procedimiento, poniéndole valores a sus parámetros. Al terminar su ejecución el procedimiento devolverá el máximo id y la fecha en la que se inserto el registro, estos valores corresponden a sus dos parámetros output.

SELECT usp_insertproduct('M01-04-943419', 'Mesh 3in x 6in');

El código en C# para ejecutar este procedimiento y obtener sus output parameters, quedaría algo así.

La clase principal para ejecutar este código es la siguiente:

Al ejecutar este programa tecleamos el código y el nombre del producto.

Al terminar su ejecución, nos mostrará los valores de los parámetros de salida.

En el arreglo de parámetros definimos los de entrada y de salida. De manera predeterminada todos los parámetros son de entrada excepto los que explícitamente se asignan como salida:

            parameters[2] = new NpgsqlParameter("p_id", p_id);
            parameters[3] = new NpgsqlParameter("p_created",p_created);
            parameters[2].Direction = ParameterDirection.Output;
            parameters[3].Direction = ParameterDirection.Output;

Agregamos los parámetros y ejecutarmos el procedimiento

            cmd.Parameters.AddRange(parameters);
            cmd.ExecuteNonQuery();

Después asignamos los parámetros a variables en C#, para su manipulación en el programa.

       p_id = Convert.ToInt32(cmd.Parameters["p_id"].Value);
       p_created = Convert.ToDateTime(cmd.Parameters["p_created"].Value);

Los output parameters son considerados la mejor opción para regresar valores escalares.

viernes, 23 de mayo de 2014

Utilizando Self-hosting Windows Communication Foundation y PostgreSQL.

Windows Communication Foundation es la parte del .NET Framework que define un modelo de programación común y una API unificada basada en estándares industriales de comunicación para construir aplicaciones distribuidas interoperables que se comunican entre sí mediante el intercambio de mensajes y así cubrir la mayor parte de los requerimientos en computación distribuida.

WCF incluye todas las tecnologías de comunicación abiertas como XML, SOAP y JSON y propietarias de Microsoft como MSMQ, COM+ y .NET Remoting.

Aunque en teoría se pueden construir servicios sin WCF, en la práctica utilizar WCF para construir servicios es mucho más práctico que hacerlo desde cero (start from scratch).

En resumen WCF consiste de un número de bibliotecas .NET que se utilizan para aplicaciones orientadas a servicios.

Antes de empezar con el ejemplo es importante tener claras algunas definiciones que son necesarias para entender el funcionamiento de WCF:

  1. Endpoint: En los sistemas orientados a servicios, es el lugar en donde los mensajes van y vienen cada endpoint requiere de tres elementos: Address, Binding y Contract. Pueden ser especificados de forma imperativa o declarativa.
  2. Address: En donde el servicio se encuentra disponible. Es un URL que se puede formar de 4 partes: Schema, machine, port y path.
  3. Binding: Como se puede acceder al servicio.
  4. Contract: Que operaciones ofrece el servicio.
  5. Servicios: Son procesos independientes definidos por su interfaz.
  6. Componentes: son los bloques de construcción a nivel plataforma tales como las bibliotecas (.dll) que se ensamblan en tiempo de diseño o en tiempo de ejecución.
  7. Objetos: Son los bloques de construcción del nivel más bajo que se encuentran dentro de los componentes y los servicios.

Hay que tener en cuenta que no importa que protocolo se use para el intercambio de mensajes desde las aplicaciones hasta el servicio, WCF siempre representará el mensaje como un objeto Message.

En resumen los servicios WCF son solo objetos que exponen una serie de operaciones que las aplicaciones clientes necesitan invocar. Cuando se construye un servicio se describen estas operaciones mediante un contrato (una interfaz) que debe tener una implementación (clase).

Para que el servicio esté disponible a los clientes se debe de instalar en un entorno de ejecución que hospede el servicio. Hay varias formas en las que puedes hospedar el servicio, puedes usar Internet Information Services (IIS), Windows Activation Services (WAS) y self-host managed applications como una consola de windows o un servicio de Windows.

En el siguiente ejemplo mostraré como utilizar los plantillas de Visual Studio para construir una aplicación con la técnica de self-hosting en la cual el desarrollador es responsable de implementar y administrar el ciclo de vida del proceso de alojamiento.

Este ejemplo utiliza un servicio WCF que realiza la búsqueda del nombre de un producto en una tabla de productos dentro base de datos postgreSQL.

Aquí el script de la tabla de productos

1-.Abrimos Visual Studio y en el explorador de soluciones agregamos un proyecto del tipo WCF Service Library.

2-. Nombramos este proyecto como: Test.WCF.Services

3-.A este proyecto le agregamos dos archivos: la interfaz IProductsContract y la clase ProductsImplementation.

Aquí el código de IProductsContract

Aquí el código de ProductsImplementation.

En este código utilizamos unos ensamblados llamados Tests.WCF.Services.Objects y Tests.WCF.Services.Data que contienen el código de datos y un DTO, este código lo omito en la explicación para centrarme en la construcción del WCF, sin embargo el proyecto completo lo incluyo en el código fuente de la solución.

4-. Agregamos un proyecto del tipo aplicación de consola a nuestra solución, con el nombre: ConsoleHostingService

5-. Tecleamos el siguiente código en la clase Program:

Este código muestra la construcción de un Self-Hosted Service , en la línea siguiente creo el Uniform Resource identifier representado por la clase URI.

 Uri baseAddress = new Uri("http://localhost:8004/");

Después asigno el tipo de objeto de la implementación a una variable.

 Type service = typeof(Tests.WCF.Services.ProductsImplementation);

Ahora hago una instancia de la clase ServiceHost que es la clase que proporciona WCF para alojar endpoints de WCF en las aplicaciones .NET, una vez teniendo la instancia hay que decirle el tipo del servicio que debe inicializarse para recibir las peticiones así como establecerle el endpoint para que los cliente sepan donde enviar las peticiones.

ServiceHost host = new ServiceHost(service, baseAddress);

Bien ahora hay que invocar el método host.Open(); para cargar el runtime de WCF y empezar a escuchar las peticiones.

using (host)
      {
        Type contract = typeof(Tests.WCF.Services.IProductsContract);
        host.AddServiceEndpoint(contract, new WSHttpBinding(), "Products");
        host.Open();
        Console.WriteLine("Products service running.Press  to quit.");
        Console.ReadLine();
        host.Close();
     }

La flexibilidad de self-hosting aplica cuando las aplicaciones son (in-proc), esto es que las aplicaciones están en el mismo proceso que el cliente.

Aquí el código del programa cliente.

Es importante que antes de ejecutar el programa cliente, se ejecute primero el programa que contiene el proceso ServiceHost esto lo podemos configurar en VS, en el submenú Set Startup Projects debajo del menú Debug en VS.

Aquí la búsqueda en la tabla utilizando el Query Management de PgAdmin

Ahora ejecutamos la misma búsqueda, solo que remotamente con la solución de VS, primero ejecutamos el proyecto ConsoleHostingService.

Inmediatamente después ejecutamos el proyecto ConsoleHostingClient.

Iniciamos con la búsqueda de productos, tecleamos la coincidencia a buscar y pulsamos ENTER, esto hará que se mande la petición al servicio WCF.

lunes, 19 de mayo de 2014

Deshabilitar el modo autocommit de transacciones en SQL Server

Hay 3 modos básicos de transacciones con los cuales trabaja SQL Server. Estos modos son:

  1. AutoCommit: Cada comando SQL/T-SQL tiene su propio espacio de transacción y automáticamente se hace la confirmación (COMMIT) cuando el comando termina.
  2. Explicit: En este modo se indica el inicio y el final de una transacción de manera programática. Utilizando los comandos: BEGIN TRAN , COMMIT TRAN y ROLLBACK TRAN. Este modo es comúnmente utilizado en store procedures o scripts.
  3. Implicit: En este modo, cuando emites ciertos comandos SQL Server automáticamente empieza una transacción. Entonces debes de confirmar o deshacer la transacción explícitamente utilizando los comandos COMMIT/ROLLBACK.

Autocommit es el modo predeterminado en SQL Server para las transacciones. Aunque este modo puede ser cómodo para el programador ya que no se tienen que escribir los comandos implicitos para abrir y cerrar una transacción, esto resulta peligro en ciertas ocasiones sobre todo si los datos con los que estamos trabajando se encuentran en un ambiente productivo.

Una manera de disminuir ese riego es deshabilitar el modo autocommit.

Esto se logra habilitando el modo de transacciones implícitas con el comando:

SET IMPLICIT TRANSACTIONS ON

Mostraré un ejemplo, habilitaré el modo de transacciones implícitas en SQL Server 2012 y agregaré un registro nuevo a la tabla HumanResources.Department de la base de datos AdventureWorks2012.
Ejecuto los siguientes comandos para agregar un registro.

SET IMPLICIT TRANSACTIONS ON
go
Insert into HumanResources.Deparment(Name,GroupName,ModifiedDate)
values('Software Developer','Research and Development',getdate())
go

Consulto la tabla para ver que se agregó el registro a la tabla.

SELECT * FROM HumanResources.Department ORDER BY DepartmentID DESC

Hasta este punto, si ejecuto ROLLBACK puedo deshacer la modificación a la tabla, si vuelvo a consultar la tabla los datos son los mismos que estaban almacenados antes del INSERT.

ROLLBACK

Vuelvo a ejecutar el INSERT si ahora deseo que la modificación sea permanente, tengo que ejecutar un COMMIT.

Vuelvo a consultar los datos de la tabla y comprobamos que el registro se guardo de forma permanente.

En este modo, si hay comandos que no han sido confirmados (COMMIT) o rechazados (ROLLBACK), SQL Management nos avisa al momento de cerrar la aplicación.

miércoles, 7 de mayo de 2014

Utilizando Message Queue con C# en .NET Parte II

Continuando con el tema de Microsoft Message Queue, mostraré como recibir un mensaje almacenado en la cola de espera. En la primera parte de este tutorial enviamos un mensaje a una cola de espera por lo que ahora hare un programa que utilice el método Receive() de la clase MessageQueueHelper que puse de ejemplo en la primera parte de este tutorial para obtener ese mensaje y mostrarlo.

Hay que recordar que MSMQ trabaja con un principio FIFO (First In, First Out) lo que significa que el primer mensaje en la cola es el mensaje que recuperarás utilizando los métodos Receive() o Peek() de la clase MessageQueue.

Aquí el código fuente del programa:

Al ejecutar el programa se verá la siguiente salida:

Ahora voy a explicar cómo funcionan los métodos de la clase MessageQueueHelper. El método CreateQueue() recibe tres parámetros que indican las siguientes propiedades:

  1. Path: Indica la ubicación de la cola de espera.
  2. Transactional: Especifica si la cola puede aceptar transacciones de mensajes.
  3. Label: El título que describe de la cola de espera.
Para el valor del Path, el “.” Indica que estamos creando la cola de espera en la maquina local y utilizamos el prefijo Private$ para indicar que es una cola de espera privada, si omites este prefijo indica que la cola de mensajes es pública.

 queue = MessageQueue.Create(queuePath,isTransactional);
 queue.Label = label;

En el método SendMessage() utilizamos le asignamos la clase XmlMessageFormatter para dar un tipo de formato al mensaje. Los MessageFormatters implementan la interfaz IMessageFormatter, la cual puede ser aplicada a un mensaje o a toda la cola de espera. Es indispensable que utilices el mismo MessageFormatter tanto para enviar o para recibir el mensaje. Aquí una lista de los Formatters:

  1. ActiveXMessageFormatter: Se utiliza para trabajar con componentes COM.
  2. BinaryMessageFormatter: Utilizado para serializar o deserializar un mensaje en un formato binario.
  3. XMLMessageFormatter: El formatter predeterminado, serializa y deserializa en formato XML.
Aquí está el código donde se utiliza:

 queue = new MessageQueue(queuePath);
 queue.Formatter = new XmlMessageFormatter();

Por último en el método GetMessage() igual que para enviar el mensaje utilizo una clase MessageFormatter para deserializar el mensaje, también utilizo el método Receive() de la clase MessageQueue para obtener el primer mensaje de la cola de forma síncrona, como argumento recibe un TimeSpan que representa el intervalo de tiempo de espera para recibir el mensaje, este argumento es importante o de lo contario si no se recibe el programa se bloqueara hasta que no haya recibido el mensaje.

 queue = new MessageQueue(queuePath);
 queue.Formatter = new XmlMessageFormatter(new Type[]{
 Type.GetType("System.String")
 });
 queueMessage = queue.Receive(new TimeSpan(0,0,2));
 message = queueMessage.Body.ToString();

lunes, 5 de mayo de 2014

Utilizando Message Queue con C# en .NET

Microsoft Message Queue (MSMQ) es un servicio de Windows que permite la comunicación interproceso entre diferentes computadoras de forma asíncrona, entender cómo funciona puede resultar útil en cualquier momento que los clientes y los servidores no tengan una conexión disponible, ya que los mensajes entre ellos son encolados para su transmisión hasta que ambos estén conectados.

El funcionamiento básico de MSMQ es parecido al de una mensajería entre aplicaciones de software, una aplicación prepara un mensaje y lo escribe hacia una línea de espera o cola de mensajes MSMQ. En este contexto una cola de mensajes es un área de almacenamiento temporal que guarda los mensajes hasta que sean transmitidos. El administrador de colas actúa como un intermediario que transmite el mensaje desde su aplicación origen hasta su destino. El propósito de la cola de mensajes MSMQ es el enrutamiento y garantizar la entrega del mensaje, en caso de que el destinatario del mensaje no esté disponible la cola de mensajes mantendrá el mensaje hasta que pueda ser entregado con éxito.

Este mecanismo de transporte proporciona los siguientes beneficios:

  1. Robustez: Los mensajes son almacenados en la línea de espera hasta que son procesados. Si se cae la conexión entre un cliente y un server los mensajes se guardan hasta que una conexión este presente.
  2. Mensajería basada en prioridad:Los mensajes más importantes son los primeros en ser recibidos, después los mensajes que le siguen y así sucesivamente, hasta que los últimos son los de prioridad más baja.
  3. Garantía de entrega:Cuando se llaman los mensajes a través de la red, MSMQ utiliza una transacción para entregar los mensajes, si se lee un mensaje de la línea de espera y ocurre un error se hace un rollback y el mensaje regresa a la línea de espera.
  4. Seguridad: La tecnología de MSMQ utiliza la seguridad de Windows para proporcionar el control de acceso, la auditoría, encriptación y autenticación de mensajes, de hecho el ensamblado System.Messaging es un wrapper para una API nativa.

Hay tres tipos de líneas de espera en MSMQ:

  1. Públicas: están registradas en el Active Directory y permiten a todas las maquinas enviar y recibir mensajes a través de la red.
  2. Privadas: solo se crean en la máquina local y pueden ser únicamente accedidas desde otra máquina si se conocen los nombres exactos de la máquina y de la línea de espera.
  3. Sistema: son principalmente utilizadas para propósitos administrativos o para aplicaciones que necesitan una línea de espera que guarde copias de los mensajes que son procesados, por ejemplo las aplicaciones de auditoría.

Como ejemplo de la utilización de MS Message Queue con C#, hice el siguiente programa que consta de dos clases: MessageQueueHelper y TestMessageQueue, la primera clase tiene los métodos para crear la cola (CreateQueue), enviar mensaje (SendMessage) y recibir mensaje (GetMessage), la segunda clase es un programa que muestra el uso de los métodos (CreateQueue) y (SendMessage).

Aquí el código de la clase MessageQueueHelper

using System;
using System.Messaging;

namespace Samples{
 public class MessageQueueHelper{
static MessageQueue queue = null;
public static Guid CreateQueue(string queuePath,
  bool isTransactional,
  string label)
{
 if(MessageQueue.Exists(queuePath))
  queue = new MessageQueue(queuePath);
 else
 {
  queue = MessageQueue.Create(queuePath,isTransactional);
  queue.Label = label;
 }
 return queue.Id;

}

public static void SendMessage(string queuePath,
  string message,string subject)
{
 message += "\nCreated on " + DateTime.Now.ToString("dd/MM/yyyy hh:mm:ss");
 MessageQueueTransaction trans = null;
 if(MessageQueue.Exists(queuePath))
 {
  queue = new MessageQueue(queuePath);
  queue.Formatter = new XmlMessageFormatter();
  if(queue.Transactional)
  {
  trans = new MessageQueueTransaction();
  trans.Begin();
  }
  queue.Send(message,subject);

  if(queue.Transactional)
   trans.Commit();
  Console.WriteLine("Message sent.");
 }else
  Console.WriteLine("The queue doesn't exist");
}

public static string GetMessage(string queuePath)
{
 string message = null;
 Message queueMessage = null;
 if(MessageQueue.Exists(queuePath))
 {
  queue = new MessageQueue(queuePath);
  queue.Formatter = new XmlMessageFormatter(new Type[]{
    Type.GetType("System.String")
    });
  queueMessage = queue.Receive(new TimeSpan(0,0,2));
  message = queueMessage.Body.ToString();
 }
 else
  message = "That queue doesn't exist";
 return message;
}

 }
}

Bien ahora el código de la clase TestMessageQueue que prueba los métodos de la clase anterior.

using System;

namespace Samples{
public class TestMessageQueue{
public static void Main(string[] args){
bool isTransactional = false;
Console.WriteLine("\nTest MSMQ\n");
try
{
 Console.Write("\nEnter path > ");
 string path = Console.ReadLine();
 Console.Write("\nIs transactional? (1=true/0=false) > ");
 string sTransactional = Console.ReadLine();
 if(sTransactional.Equals("1"))
  isTransactional = true;
 Console.Write("\nEnter a label > ");
 string label = Console.ReadLine();
 Guid guid = MessageQueueHelper.CreateQueue(path,isTransactional,label);
 Console.WriteLine("\nMessageQueue created with guid {0}\n",guid);
 Console.Write("\nMessage subject > ");
 string subject = Console.ReadLine();
 Console.Write("\nMessage to send > ");
 string message = Console.ReadLine();
 Console.WriteLine();
 MessageQueueHelper.SendMessage(path,message,subject);
}
catch(Exception ex){
 Console.WriteLine(ex.Message);
}
}

}
}

Al ejecutar el programa solicitará los siguientes parámetros:

  • Path: Es el nombre de la cola de espera.
  • Is transactional: 1 si la cola es transaccional, 0 si no lo es.
  • Label: Identificador de la cola.

Al crearse la cola de mensajes el programa regresará el identificador: 00000000−0000−0000−0000−000000000000 por tratarse de una cola de mensajes privadas (private MSMQ).

Ahora el programa solicitará el título y el cuerpo del mensaje, después lo enviará hacia la cola de mensajes creada.

Para ver la cola de mensajes creada con el programa y el mensaje enviado, ejecutamos Computer Management de Windows que se encuentra en la ruta Control Panel\System and Security\Administrative Tools y debajo del nodo de Services and Applications veremos el nodo Private Queues debajo del nodo Message Queuing donde se encuentra la línea de espera creada con el programa de ejemplo.

Debajo de la cola de espera myfirstqueue, se encuentra la carpeta Queue messages en donde se puede ver la etiqueta del mensaje.

Si hacemos doble click en el mensaje aparecerá una ventana con cuatro pestañas en donde podemos ver las propiedades del mensaje, en la pestaña body podemos ver el contenido del mensaje.

martes, 22 de abril de 2014

Utilizando Extension Methods en .NET con C#

En programación orientada a objetos, la herencia es uno de los mecanismos más eficientes para extender la funcionalidad de los objetos que componen un sistema, este mecanismo permite que un objeto herede atributos y comportamientos de otro objeto reduciendo así la cantidad de código que tendrá que mantenerse una vez desarrollado el sistema.

Sin embargo agregar funcionalidad a un objeto que no fue diseñado para ser heredado puede ser una tarea no muy recomendable, ya que implica cambiar el diseño de ese objeto y entonces afectar todos los objetos que dependan de él y sus operaciones que lleven a cabo dentro del sistema.

En .NET existe una característica llamada extension methods (métodos de extension) que permite agregar funcionalidad (métodos) a un objeto o tipo ya existente en el framework o en los ensamblados sin la necesidad de crear subclases, recompilar o modificar el código fuente. Esta técnica incluso permite agregar métodos a objetos que provengan de clases selladas (sealed) sin violar el principio de encapsulamiento.

Como ejemplo de esta técnica, agregaré un par de métodos a un tipo incorporado dentro del framework .NET, utilizaré la estructura DateTime que representa el tiempo (fecha y la hora).

A esta estructura voy a agregarle dos métodos uno llamado LastDay que regresa el último día del mes de un objeto DateTime y otro método llamado After que regresa true si el objeto DateTime que lo invoca es mayor a la fecha que recibe como parámetro.

Aquí el código de la clase donde agrego los extension methods

Aquí el código con el programa que prueba los métodos anteriores.

Al ejecutar el programa se ve la siguiente salida.

Propiedades de los extension methods

Pueden aplicarse a cualquiera de los siguientes objetos: Clases, Estructuras, Interfaces, Delegados, Genéricos y arreglos. Deben ser declarados dentro de una clase estática, y tener la palabra reservada this antes de su primer parámetro, que es el tipo que el método extiende, los demás parámetros son los argumentos del método. Son públicos por que se llaman desde fuera de la clase donde ellos son declarados. Pueden aplicarse en clases selladas (sealed) y tipos intrínsecos porque automáticamente soportan boxing y unboxing, es decir cuando se aplican a un tipo por valor .NET los envuelve como si se tratara de una objeto por referencia.

miércoles, 9 de abril de 2014

Compresión y descompresión de flujos(streams) en .NET

Una capacidad muy útil en cualquier Framework es la de comprimir y descomprimir archivos o flujos de datos, ejemplos aplicados de esta capacidad los tenemos en las aplicaciones que intercambian datos a través de la red como el caso de comprimir el viewstate en las peticiones y respuestas del servidor al cliente en una aplicación ASP.NET o si necesitamos reducir el tamaño de los objetos JSON que viajan de una aplicación hacia un servicio web y viceversa.

.NET proporciona las clases GzipStream y DeflatStream que implementan el algoritmo de compresión estándar gzip que está libre de patente. De hecho la diferencia entre ambas clases es que la clase GzipStream utiliza la especificación gzip descrita en el RFC 1952 que reduce el tamaño de los datos utilizando un código Lempel-Ziv (LZ77) con un CRC de 32 bits, lo que significa que se puede incluir encabezados (headers) con información extra que puede ser utilizada por cualquier herramienta de descompresión o por el comando gunzip de Linux o UNIX.

En resumen si los datos comprimidos van a hacer descomprimidos por una aplicación .NET lo recomendable es usar la clase DeflatStream, de lo contrario si van a hacer descomprimidos por otra aplicación usar la clase GzipStream.

Para mostrar la utilización de la clase DeflatStream escribí el siguiente programa que ejecuta la acción de comprimir o descomprimir dependiendo de la extensión del archivo que le pasemos como parámetro. (Este código sirve para ambos flujos solo hay que reemplazar DeflatStream por GzipStream)

using System;
using System.IO;
using System.IO.Compression;

namespace GzipGunzip
{
class MainClass
{
public static void Main (string[] args)
{
if (args.Length == 1)
{
 try{
 FileInfo fis = new FileInfo(args[0]);
 Console.WriteLine("Input file {0}",fis.FullName);
 if(string.Compare(fis.Extension,".gz") == 0)
  Uncompress(fis.FullName);
 else
  Compress(fis.FullName);
 }catch(Exception ex){
  Console.WriteLine(ex.Message);
 }
}else
 Console.WriteLine("\nUsage: GzipGunzip filename\n");
}

static void Uncompress (string filename)
{
Console.WriteLine ("Uncompressing .... {0} ", filename);
string destFile = filename.Substring (0, filename.Length - 3);
FileStream inputStream = new FileStream (filename
                                        , FileMode.Open
                                        , FileAccess.Read);
using (FileStream outputStream = new FileStream (destFile
                                         , FileMode.Create
                                         , FileAccess.Write)) {
 using(DeflateStream zipStream = new DeflateStream (inputStream
                                             , CompressionMode.Decompress)){
 int inputByte = zipStream.ReadByte ();
 while (inputByte != -1) {
  outputStream.WriteByte ((byte)inputByte);
  inputByte = zipStream.ReadByte ();
 }
 }
}

Console.WriteLine("output: {0}",destFile);
}
static void Compress (string filename)
{
Console.WriteLine ("Compressing .... {0} ", filename);
string destFile = filename + ".gz";
byte[] buffer = null;
using (FileStream inputStream = new FileStream(filename
                                 ,FileMode.Open
                                        ,FileAccess.Read)) {
 buffer = new byte[inputStream.Length];
 inputStream.Read (buffer, 0, buffer.Length);

}
Console.WriteLine ("bytes read: {0}", buffer.Length);
using (FileStream outputStream = new FileStream(destFile
                                   ,FileMode.Create
                                         ,FileAccess.Write)) {
 using(DeflateStream zipStream = new DeflateStream (outputStream
                              , CompressionMode.Compress)){
 zipStream.Write (buffer, 0, buffer.Length);
 }
}
Console.WriteLine("bytes written: {0}",buffer.Length);
}
}
}

Ahora para probar el código, voy a crear un archivo llamado dummy.zeros con un tamaño de 20 megas para pasárselo como parámetro al programa.

Este archivo lo creo con el comando dd en Linux

dd if=/dev/zero of=dummy.zeros bs=1000 count=20000

Ahora muestro el tamaño del archivo dummy.zeros, siendo de 20Mb.

Ahora comprimo el archivo dummy.zeros con el programa de ejemplo GzipGunzip, esto lo hago con el siguiente comando:

mono GzipGunzip.exe dummy.zeros

Si se ejecuta correctamente este programa debe crear un archivo llamado dummy.zeros.gz, el cual es el archivo comprimido de dummy.zeros. Comparamos el tamaño de ambos archivos.

Ahora voy a descomprimir el archivo dummy.zeros.gz , pero antes de hacerlo voy a renombrar el archivo original dummy.zeros como dummy.zeros.bak para evitar que el archivo original se sobreescriba y así poder comparar todos los archivos. Ejecutamos nuevamente el programa GzipGunzip pasándole como parámetro el nombre del archivo.

mono GzipGunzip.exe dummy.zeros.gz

Bien ahora con todos los archivos, podemos comparar sus tamaños y comprobar que las clases de compresión de .NET funcionaron.

Para diseñar programas que utilicen las clases de compresión y descompresión de archivos en C#, el primer paso es crear los flujos de entrada y de salida.

FileStream inputStream = new FileStream (filename , FileMode.Open, FileAccess.Read);
FileStream outputStream = new FileStream (destFile , FileMode.Create, FileAccess.Write))

En el caso de la compresión el flujo de compresión (DeflateStream) debe de envolver al flujo de salida, porque la entrada es un archivo sin comprimir que tendrá como destino un archivo comprimido.

DeflateStream zipStream = new DeflateStream (outputStream, CompressionMode.Compress)

Para la descompresión el flujo de compresión (DeflateStream) deberá de envolver al flujo de entrada ya que es un archivo comprimido que tendrá como destino un archivo sin comprimir.

DeflateStream zipStream = new DeflateStream (inputStream, CompressionMode.Decompress)

Hay que recordar que uno de los factores que degradan el desempeño de las aplicaciones ASP.NET es cuando existe un cuello de botella en el ancho de banda y no en la utilización del procesador, en el caso de la compresión de datos usada junto con el protocolo HTTP se recomienda para texto: JSON, XML, HTML, etc. Esta compresión no es útil para archivos de imágenes, los cuales ya se encuentran comprimidos.

martes, 11 de marzo de 2014

Operadores booleanos en PostgreSQL

La lógica proposicional o calculo proposicional es una herramienta utilizada en muchas áreas del conocimiento: en matemáticas se utiliza para demostrar teoremas, en derecho se utiliza para la formulación de argumentos, en física para establecer el procedimiento para llevar a cabo un experimento, en el campo de la computación tiene un papel relevante ya que se relaciona con las áreas de Inteligencia Artificial y Lenguajes de programación.

En cuanto a esta última área, su utilización va desde la demostración de programas hasta la creación de un paradigma de desarrollo llamado programación lógica relacional cuyo exponente principal es el lenguaje de programación Prolog.

Proposiciones

La proposición simple es la unidad básica de construcción de la lógica proposicional.

Una proposición simple es un enunciado declarativo al cual se le puede asignar uno u otro (nunca ambos) de dos valores:falso representado por la letra (f) o el dígito (0) y verdadero representado por la letra (v) o el dígito (1), estos valores son llamados valores de verdad (truth values).

Algunos ejemplos de proposiciones en un lenguaje natural con su correspondiente valor de verdad.

Japón es un país de Europa (Falso) 
Alemania es un país de Europa (Verdadero) 
Charles Darwin escribió el origen de las especies en 1905 (Falso) 
Charles Darwin escribió el origen de las especies en 1859 (Verdadero)
  

Para la representación de operaciones lógicas PostgreSQL cuenta con el tipo de dato boolean que guarda la representación de cualquiera de los valores de verdad mas el valor NULL. Este tipo de dato cuenta con sinónimos para la representación de valores true y false , sin embargo en el campo de tabla solo se almacenaran los valores t para true y f para false. A continuación la tabla de sinónimos para estos valores:

Valor de campo Sinónimo
T True, t, yes, y, 1
F False, f, no, n, 0

Mostraré unos ejemplos de operaciones booleanas básicas, creamos la siguiente tabla:

Ahora insertaré unos registros, como proposiciones utilizaré las columnas nombrándolas con las letras standard que se usan para representarlas, en otra columna almacenaré su correspondiente valor de verdad, esto lo hago con el siguiente script.

Al ejecutar el script y después la consulta SELECT * FROM propositions observamos que a pesar de los sinónimos en los comandos INSERT, únicamente se almacenaron en la tabla los valores f y t.

Adicionalmente al tipo de dato boolean ,PostgreSQL cuenta con los operadores lógicos o conectores AND , OR y NOT que permiten formar proposiciones compuestas, es decir proposiciones que se forman por dos o más proposiciones simples unidas por conectores.

Conjunción AND

Multiplicación lógica, su tabla de verdad incluyendo el valor NULL es la siguiente:

P Q P AND Q
True True True
True False False
False True False
False False False
Null True Null
Null False False
Null Null Null

Ejecuto unas consultas con el operador AND en PostgreSQL , para confirmar la tabla de verdad (truth table):

Disyunción OR

Suma lógica, su tabla de verdad incluyendo el valor NULL es la siguiente:

P Q P OR Q
True True True
True False True
False True True
False False False
Null True True
Null False Null
Null Null Null

Ejecuto unas consultas con el operador OR en PostgreSQL , para confirmar la tabla de verdad (truth table):

Negación NOT

Este operador invierte el valor de la proposición, su tabla de verdad incluyendo el valor NULL es la siguiente:

S NOT S
True False
False True
Null Null

Ejecutando el operador NOT en PostgreSQL, en las siguientes consultas, para confirmar la tabla de verdad (truth table):

Con este repaso vemos que en el contexto de las bases de datos además de los valores de verdad como True y False tenemos que considerar el valor NULL al momento de hacer operaciones booleanas.

viernes, 21 de febrero de 2014

Notas adicionales de la utilización de triggers en PostgreSQL

Complementando a este post Utilizando Triggers en PostgreSQL, estan un par de notas adicionales.

Con el comando \d (describe) puedes mostrar los triggers que esa tabla tiene asociados, la sintaxis del comando es la siguiente:

\d [table]

Si quieres eliminar el trigger asociado a una tabla hay que ejecutar el siguiente comando DROP TRIGGER ON, la sintaxis del comando es la siguiente:

DROP TRIGGER [name] ON [table]

lunes, 17 de febrero de 2014

Utilizando triggers en PostgreSQL

Adicionalmente a la utilización de reglas o constraints, existe otra forma de verificar y mantener la integridad en el DBMS para las aplicaciones, esto se logra mediante los triggers (disparadores). Un trigger es básicamente un store procedure o una función del lado del servidor que se dispara cuando una determinada acción INSERT,UPDATE o DELETE se ejecuta cada vez que una fila es modificada.

Aunque los triggers sirven en su mayoría para mantener la integridad de la base de datos también pueden usarse para:

  • Auditoría: Los triggers pueden usarse para llenar tablas de auditoría, en donde se registren ciertos tipos de transacciones o accesos hacia tablas.
  • Seguridad: Los triggers refuerzan las reglas de seguridad de una aplicación.
  • Reglas de negocio: Cuando ciertas reglas de negocio son muy elaboradoras y necesitan ser expresadas a nivel base de datos, es necesario que además de reglas y constraints se utilicen triggers.
  • Validación de datos: Los triggers pueden controlar ciertas acciones, por ejemplo: pueden rechazar o modificar ciertos valores que no cumplan determinadas reglas, para prevenir que datos inválidos sean insertados en la base.

A continuación un ejemplo de la utilización de triggers en postgreSQL.

Existe una tabla de facturas como la siguiente:

Creamos unas facturas con el siguiente script.

Bien esta tabla ya cuenta con algunos registros.

Ahora se necesita conocer aquellos registros que por error o intencionalmente cambiaron de fecha o de cantidad. Para cumplir con este requerimiento se crea una tabla en donde se guardará el historial de esos cambios.

Necesitamos una solución para que cada vez que se borre o se actualice un registro en la tabla invoices se registren los cambios en invoices_audit. Bien ya tenemos una razón para crear un trigger.

Antes de utilizar un trigger es indispensable crear una función trigger (trigger function) la cual es similar a un store procedure aunque un poco más restringida y con el acceso a unas variables predefinidas que contienen los valores de la fila que ejecuta el trigger, dependiendo de la operación estas variables son:

  • NEW En la operación INSERT representa el registro que se va a crear, en la operación UPDATE representa el valor del registro después de la actualización.
  • OLD En la operación UPDATE representa el valor antes de la actualización, en la operación DELETE representa el registro que se borrará.
  • TG_NAME El nombre del trigger.
  • TG_WHEN El instante en el cual se ejecutará el trigger, los valores son BEFORE o AFTER.
  • TG_OP La acción que dispara el trigger: INSERT, UPDATE o DELETE.
  • TG_RELNAME El nombre de la tabla que disparó el trigger.
  • TG_RELID El OID de la tabla que disparó el trigger

Esta trigger function se crea sin parámetros y con un valor de retorno de tipo trigger, esta función se ejecutará cada vez que una fila es modificada, a continuación el código de la función en donde se muestra el uso de variables predefinidas.

Siendo lo más importante a continuación el código que crea el trigger , esto es lo que asocia la tabla con la ejecución de la función.

La sintaxis general para crear un trigger es:

CREATE TRIGGER [name] [BEFORE | AFTER] [action]
ON [table] FOR EACH ROW
EXECUTE PROCEDURE [function name(arguments)]
  

El código completo es el siguiente:

Cuando ejecutemos el código de la función trigger y de la creación del trigger desde un archivo, PostgreSQL nos mostrará los siguientes mensajes:

  CREATE FUNCTION
CREATE TRIGGER
  

Observamos que la función trigger y el trigger se crearon exitosamente, utilizando PgAdmin III.

Probamos el trigger actualizando un par de registros en la tabla invoices.

Comprobamos que las actualizaciones se realizaron.

Por último mostramos los registros de la tabla invoices_audit, para comprobar que el trigger guardo los valores que cambiaron de los registros actualizados.

martes, 11 de febrero de 2014

Mostrando los privilegios ACL de objetos en PostgreSQL

Complementando los anteriores posts parte I y parte II de los comandos GRANT y REVOKE.

Como parte del estándar de seguridad SQL1 a cada usuario de un DBMS se le asigna un user-id que determina que comandos le están permitidos o prohibidos ejecutar, generalmente estos user-id son asignados por los administradores o superusuarios del DBMS, quienes son los que mantienen esta información en Access Control List (ACL's).

El estándar ANSI/ISO SQL utiliza el termino authorization-id en lugar de user-id (aquí concuerdo con que el termino authorization-id es mejor y más descriptivo).

Los privilegios ACL's de un objeto los puedes mostrar con los comandos: \dp (display permissions) o \z.

A continuación el significado de cada privilegio (explico algunos que no son tan evidentes):

  • r = SELECT
  • a = INSERT
  • w = UPDATE
  • d = DELETE
  • R = RULES
  • x = REFERENCES
  • t = TRIGGER (tablas)
  • X = EXECUTE (Funciones, procedimientos)
  • U = USAGE (Poder enumerar los objetos dentro de un esquema y
    crear funciones con lenguajes de procedimiento)
  • C = CREATE (Creación de esquemas, objetos, indices y tablas)
  • T = TEMPORARY (Creación de tablas temporales en una base de datos)
  • * = GRANT
  • ALL= arwdRxt

Estos privilegios los despliega el comando \z para tablas, vistas y secuencias.

Para mayor referencia consultar el libro: PostgreSQL, The comprehensive guide to building, programming and administering PostgreSQL databases, Second Edition.; capítulo 23 Security

viernes, 7 de febrero de 2014

Utilizando los comandos GRANT y REVOKE en PostgreSQL - Parte II

Continuando con el post acerca de los comandos GRANT y REVOKE , estos comandos adicionalmente cuentan con las opciones de:

  • ALL PRIVILEGES Otorga todos los privilegios al usuario sobre un particular objeto.
  • PUBLIC En vez de asignar los privilegios a cada usuario (uno por uno) , se utiliza esta palabra para asignar los privilegios a cada uno de los usuarios autentificados en la base de datos, incluso a aquellos que no han sido creados.

Además para el comando GRANT tenemos la opción de:

  • WITH GRANT OPTION Cuando se crea un objeto en la base de datos, el creador de ese objeto es el único que puede otorgar privilegios al objeto para que otros usuarios lo utilicen, con esta opción el propietario del objeto permite que otros usuarios puedan asignar privilegios a un objeto de sus propiedad.

Mostraré unos ejemplos:

ALL PRIVILEGES

Primero el uso de GRANT con la opción ALL PRIVILEGES.

Mostramos los privilegios del usuario martin en la tabla authors con el comando \z authors

Ahora con el usuario postgres (propietario del objeto) le asignamos todos los privilegios al usuario martin. con el siguiente comando:

  GRANT ALL PRIVILEGES ON authors TO martin;
  

Vuelvo a mostrar todos los privilegios del usuario martin y compruebo que todos los permisos le han sido otorgados.

Para retirar todos los privilegios ejecuto como el usuario postgres el siguiente comando:

  REVOKE ALL PRIVILEGES ON authors FROM martin;
  

Muestro nuevamente los privilegios de la tabla y compruebo que el usuario martin no tiene ningún privilegio asignado.

PUBLIC

Ahora con el usuario postgres ejecuto el siguiente comando

  GRANT SELECT ON authors TO PUBLIC;
  

El cual otorga el privilegio SELECT a todos los usuarios incluso a aquellos que no se han creado dentro de la DBMS.

  GRANT SELECT ON authors TO PUBLIC;
  

Para comprobar este privilegio ejecuto una consulta SELECT con un usuario llamado docencia, quien no necesito que se le otorgaran privilegios de manera especifica.

WITH GRANT OPTION

Únicamente el creador del objeto puede usar el comando GRANT para otorgar privilegios a otros usuarios, sin embargo con la opción WITH GRANT OPTION del comando GRANT puede delegarle la capacidad de otorgarle privilegios sobre su objeto a otros usuarios.

Para este ejemplo creo una tabla llamada books con el usuario postgres

  create table Books
(
        bookid serial not null primary key,
        isbn varchar(13) not null,
        title varchar(512) not null,
        numpages integer null,
        year smallint not null
);
  

Ahora bien, el usuario postgres requiere que el usuario martin tenga los privilegios de SELECT e INSERT, pero que también martin tenga la capacidad de otorgarlos a otros usuarios, ejecutamos los siguientes comandos:

  GRANT INSERT, SELECT ON Books TO martin WITH GRANT OPTION;

  GRANT UPDATE ON books_bookid_seq TO martin WITH GRANT OPTION;
  

Ahora martin tiene la capacidad de poder otorgarle los privilegios a docencia en la tabla y en la secuencia.

   GRANT INSERT,SELECT ON Books TO docencia;

   GRANT UPDATE ON books_bookid_seq TO docencia;
   

Para que este usuario pueda ahora ejecutar la consulta y la creación de nuevos registros en la tabla.

lunes, 3 de febrero de 2014

Utilizando los comandos GRANT y REVOKE en PostgreSQL

Cuando se trata de la seguridad de los datos almacenados es muy importante tener en cuenta las siguientes consideraciones sobretodo en ambientes productivos:

  • Los datos de cualquier tabla deben ser accesibles únicamente a los usuarios del DBMS que realmente necesiten esa información, el resto no debe tener acceso.
  • A cada uno de los usuarios del DBMS que tienen acceso a una determinada tabla, se les debe de asignar una cierta acción según sus necesidades, por ejemplo a ciertos usuarios se les permitirá ejecutar un UPDATE, mientras que al resto (o a todos) se les permitirá únicamente ejecutar un SELECT.
  • En ciertos casos incluso para la ejecución de un SELECT solo se permitirá que un usuario del DBMS pueda consultar una tabla a nivel de ciertas columnas.

Cabe recordar que en producción los usuarios del DBMS son asociados más con aplicaciones o grupos de programas que con usuarios operativos. Existen tres conceptos en el esquema de seguridad SQL: Usuarios, Objetos y privilegios.

Privilegios

Los privilegios suelen clasificarse en privilegios del sistema y de objetos.

Los privilegios del sistema permiten al usuario realizar algún tipo de operación que afecta a todo el sistema.

Los privilegios de objetos se definen como las acciones que le son permitidas a un usuario ejecutar en un determinado objeto de la base de datos (tabla,vista,secuencia,función), esto una vez que el usuario haya sido autentificado dentro del DBMS.

Los privilegios de objetos dependen del tipo de objeto, por ejemplo el estándar SQL1 especifica 4 privilegios para tablas y vistas:

  • SELECT - Permite consultar todas las filas.
  • INSERT - Permite la creación de nuevos registros.
  • DELETE - Permite la eliminación de filas.
  • UPDATE - Permite la modificación de filas ya creadas.

Para el resto de los objetos en PostgreSQL pueden o no aplicar los siguientes privilegios:

  • RULE - Permite la creación de reglas para una tabla o una vista.
  • REFERENCES - Permite la creación de llaves foráneas (foreign key) al crear relaciones.
  • TRIGGER - Permite la creación de triggers.
  • EXECUTE - Permite la ejecución de funciones o store procedures.
  • ALL - Permite todos los privilegios.

De manera predeterminada en PostgreSQL cuando se crea un objeto el creador del objeto es el propietario y se le asignan todos los privilegios sobre ese objeto, el resto de los usuarios no tiene ningún privilegio sobre ese objeto.

El DDL (Data Definition Language) incluye dos comandos para conceder y retirar privilegios: GRANT y REVOKE

Como ejemplo voy a crear la siguiente tabla en una base de datos llamada bibl, cuyo dueño de la base de datos es el usuario postgres.

  CREATE TABLE Authors(
 author_id        serial primary key,
 author_name     varchar(256),
 author_lastname     varchar(256),
 author_birthdate     date
 );
  

Paso siguiente voy a insertar unos registros:

insert into authors(author_name,author_lastname,author_birthdate)
values('Elizabeth','Bishop','02/08/1911');

insert into authors(author_name,author_lastname,author_birthdate)
values('Charles','Dickens','07/02/1812');

insert into authors(author_name,author_lastname,author_birthdate)
values('Jack','London','12/01/1876');

insert into authors(author_name,author_lastname,author_birthdate)
values('Joseph','Conrad','03/12/1857');
  

Hago un SELECT y muestro los registros de la tabla.

Como se ve con el usuario postgres pude ejecutar sin ningún tipo de restricción las siguientes acciones: CREATE, INSERT y SELECT

Ahora ingresaré con un usuario distinto al usuario postgres, ingresaré en la base de datos con el usuario martin y ejecutaré un SELECT sobre la tabla authors.

Al ejecutar el SELECT PostgreSQL nos muestra los siguientes mensajes:

ERROR:  permission denied for relation authors
STATEMENT:  SELECT * FROM authors;
ERROR:  permission denied for relation authors

GRANT

Estos mensajes me indican que el usuario martin no tiene los privilegios necesarios para ejecutar el SELECT en esa tabla y que por lo tanto no podrá leer los registros a menos que el usuario propietario postgres conceda el privilegio de hacerlo ejecutando el comando GRANT. La sintaxis básica del comando es:

GRANT [privilegios] ON [objeto] TO {public | group | username}

Así que con la sesión de postgres ejecuto el siguiente comando:

  GRANT SELECT ON authors TO martin;

Regresando a la sesión del usuario martin, vuelvo a ejecutar el comando SELECT.

  SELECT * FROM authors;

Ya es posible que el usuario martin pueda ejecutar la consulta.

Ahora intentaré crear un nuevo registro

insert into authors(author_name,author_lastname,author_birthdate)
values('Gustave','Flaubert','12/12/1821');

PostgreSQL me envía el siguiente mensaje debido a que este usuario no tiene el privilegio de INSERT:

  ERROR:  permission denied for relation authors
  

Concedo al usuario martin el privilegio de INSERT, con el siguiente comando ejecutado por el usuario postgres

GRANT INSERT ON authors TO martin;

Ejecuto nuevamente el INSERT y PostgreSQL me envía ahora el siguiente mensaje:

ERROR: permission denied for sequence authors_author_id_seq

Concedo entonces al usuario martin el privilegio de poder actualizar la secuencia con el siguiente comando (debe ser ejecutado por postgres):

GRANT UPDATE ON authors_author_id_seq TO martin;

Con los privilegios otorgados a la tabla y a la secuencia ahora ya es posible crear el registro.

Para mostrar los privilegios que se tienen sobre un determinado objeto, utilizamos el comando \z. La sintaxis es:

\z [nombre del objeto]

Para este ejemplo ejecutamos:

\z authors;

REVOKE

De la misma manera que se otorgaron los privilegios al usuario martin se le pueden retirar con el comando REVOKE la sintaxis básica del comando es:

REVOKE [privilegios] ON [objecto] FROM {public | group | username }

Así que con el usuario postgres ejecuto los siguientes comandos para retirarle los privilegios otorgados a martin en la tabla y en la secuencia:

REVOKE INSERT,SELECT ON authors FROM martin;
REVOKE UPDATE on authors_author_id_seq FROM martin;

Si mostramos los privilegios de la tabla y de la secuencia, observamos que ya no se muestran los privilegios para el usuario martin , esto por que los quito el usuario postgres, quien es el propietario de los objetos. Para mostrar los privilegios de ambos objetos (secuencia y tabla) únicamente ejecuto el comando \z sin argumentos.