Mostrando entradas con la etiqueta Linux. Mostrar todas las entradas
Mostrando entradas con la etiqueta Linux. Mostrar todas las entradas

viernes, 28 de abril de 2017

Utilizando la clase NpgsqlError de la enumeración NpgsqlException.Errors

Una forma precisa de diagnosticar los errores PostgreSQL en una aplicación :NET es mediante el uso de la clase NpgsqlError de la cual se pueden obtener detalles adicionales a la excepción generada esta clase esta incluida en la enumeración Errors la cual al recorrerse y consultar las propiedades de la clase NpgsqlError podemos obtener información específica acerca de los errores y advertencias que se generan al ejecutar los comandos SQL o Store procedures en PostgreSQL por medio de una aplicación .NET que haga uso del driver Npgsql.

A continuación mostramos una aplicación de consola que se conecta a una base de datos PostgreSQL, ejecuta un comando SQL para crear un nuevo registro en una tabla y envia la excepción generada hacia un archivo de texto.


Aquí el código para crear la tabla Books

Aqui el código de la clase Book


Aquí el código de la clase BooksManager


Aquí el código de la clase Logger

Finalmente, el código la clase principal MainClass.

En la clase BooksManager se encuentra el método estático LogErrors en donde se realiza toda la funcionalidad, aquí es donde se itera por la enumeración Errors de la clase NpgsqlException y se escribe la información en el archivo de texto.

Antes de compilar y ejecutar la aplicación vamos a ocasionar un error en la sentencia de la consulta SQL. Por ejemplo no escribir correctamente el nombre de la tabla.



var commandText = "INSERT INTO Booksssss(title,numpages,pubyear,created)
VALUES(:title, :numpages, :pubyear, :created)";

Al compilar y ejecutar la aplicación veremos la excepción generada a propósito para poder consultar el archivo log y ver más detalles acerca de la excepción generada.
Compilamos la aplicación con los siguientes comandos:

$ dmcs -t:library -r:System.Data,Npgsql Book.cs BooksManager.cs Logger.cs 
/out:TestNpgsqlError.dll

$ dmcs -r:TestNpgsqlError.dll Main.cs

Ejecutamos la aplicación, como se muestra en la siguiente imagen:


Otra prueba es si escribimos erróneamente los parámetros de la cadena de conexión, por ejemplo cambiar el usuario de la base de datos o no teclear correctamente el nombre de la base de datos.



string connStr = "Server=127.0.0.1;Port=5432;Database=testBooks;User ID=postgressss;
Password=Pa$$W0rd";

Podemos consultar la bitácora y ver como por cada excepción se genera un código y los detalles de la excepción.

miércoles, 4 de enero de 2017

Un corto animado de Tux

La popularidad de los sistemas operativos GNU/Linux ha hecho familiar también a su mascota oficial el simpático pinguino Tux.

Pues ya hace bastante tiempo que anduvo circulando está animación en donde se ve la fascinación que tiene la oficial mascota de Linux por el pescado.

El pinguino Tux, la mascota oficial del sistema GNU/Linux.

El pinguino Tux siempre amigable como el sistema operativo que representa.

jueves, 30 de junio de 2016

Q&A Preguntas y respuestas acerca de la administración del sistema UNIX

¿Cuál es la función del administrador de un sistema UNIX?

El administrador de un sistema UNIX es el responsable de configurar y mantener el sistema funcionando correctamente.

¿Qué debe entender el administrador de un sistema para realizar sus responsabilidades adecuadamente?

Entender el hardware y el software del sistema, así como (y esto es lo más importante) las necesidades de los usuarios.

¿Cuales son las responsabilidades de un administrador de sistemas UNIX?

Responsabilidad del Hardware, Software y hacia los usuarios.

Además del conocimiento en Hardware y Software ¿qué otras habilidades son necesarias para un buen administrador de sistemas UNIX?

El conocimiento de un lenguaje de programación Scripting.

¿Cuales son los campos del archivo /etc/password?

  1. user name
  2. encrypted password
  3. user id
  4. group id
  5. comment field
  6. login directory
  7. startup program

¿Cuales son los campos del archivo /etc/group?

  1. group name
  2. password
  3. group id
  4. group list

¿Cuál es la diferencia entre el archivo /etc/profile y .profile?

¿Para qué sirve el comando wall?

¿Para qué sirve el archivo /etc/motd.?

¿Para qué sirve el comando dmesg?

¿Cuál es la diferencia entre un archivo de bloque (block) y un archivo de caracteres (character)?

¿Qué es un archivo de dispositivo?

¿Para qué sirve el comando ln?

¿Cómo se controla el run-level (niveles de arranque) de un sistema UNIX?

¿Cuál es el propósito del archivo /etc/inittab ?

¿Cómo se obtiene el actual run-level del sistema?

¿Cuál es la diferencia entre apagar el sistema con shutdown -h o con reboot -halt?

¿Para qué sirve el comando fsck?

¿Para qué sirve el archivo /etc/fstab?

¿Qué es el root access (ingresar como superusuario)?

¿Cuál e s la diferencia entre memoria física y memoria virtual?

¿Para que sirven los comandos pwck y grpck?

lunes, 22 de febrero de 2016

Utilizando el comando netstat en Linux

netstat muestra el status y la información de las conexiones de red de varios tipos y ver que puertos TCP y UDP esta escuchando la computadora, utilizar el comando netstat con las opciones -lut.

      $netstat -lut
    
Fig 1 netstat con la opción -lut.

Ahora si se quiere ver rápidamente el status de las interfaces:

      $netstat -i 
    
Fig 2 netstat con la opción -i.

Aquí pongo la tabla que describe el significado de cada columna:

Columna Significado
Iface Nombre de la interface
MTU Maximum Transfer Unit – El máximo número de bytes que un paquete puede contener
RX-OK, TX-OK Número de paquetes libres de errores: recibidos (RX) o transmitidos (TX).
RX-ERR, TX-ERR Número de paquetes del errores
RX-DRP, TX-DRP Número de paquetes descartados
RX-OVR,TX-OVR Número de paquetes perdidos debidos a un overflow.
FLG A = multicast recibido, B = broadcast permitido, D= depuración encendida, L = loopback interface (notice the flag en lo), M = todos los paquetes recibidos, N= tramas evitadas, 0 = no ARP en esta interface, P = punto a punto interface, R = interfaz esta corriendo, U = interfaz está arriba.

Otra forma muy útil para revisar que de donde vienen las conexiones es:

      $ netstat -tupn
    
Fig 3 La salida de netstat con la opción -tupn.

Aquí una lista de cada uno de los párametros más comunes en el uso de netstat
-t --tcp Muestra las conexiones socket TCP
-u --udp Muestra las conexiones socket UDP
-p --program Muestra el PID y el nombre de los programas al que los sockets pertenecen
-n --numeric Muestra la dirección IP numérica en vez de determinar el host y el puerto.

lunes, 4 de enero de 2016

Utilizando la clase MemoryStream con imágenes en GTK# y MonoDevelop

El mecanismo de comunicación entre las aplicaciones .NET y los dispositivos de entrada y de salida se realiza mediante streams (flujos secuenciales) de bytes que se obtienen de una fuente de entrada hacia la aplicación y salen de la aplicación hacia una fuente de salida, pensemos en los flujos como corrientes de agua que van de un recipiente a otro.

Dentro del ensamblado System.IO existe la clase abstracta Stream que implementa operaciones de lectura y escritura sincrónica y asíncrona y que sirve como base para cualquier clase derivada que sea utilizada en operaciones de entrada y salida, como ejemplos de clases derivadas tenemos a FileStream, CriptoStream, NetworkStream, BufferedStream y MemoryStream. De estas clases la clase MemoryStream proporciona una área de almacenamiento temporal en memoria o sea un arreglo de bytes sin signo, entre sus usos más comunes se encuentran las operaciones con imágenes, la compresión y descompresión de archivos, el cifrado/descifrado de datos y la serialización entre otros.

Algunas propiedades y métodos de la clase MemoryStream:

    Read: Inicia la operación de lectura de forma secuencial de un número de bytes desde el inicio hasta el final del flujo.
  • ReadByte: Idéntico que Read solo que lee un byte de forma secuencial.
  • Length: Es la longitud en bytes del flujo.
  • Position: Obtiene la posición del apuntador lector del flujo.
  • ReadTimeOut: Se refiere al tiempo de espera para operaciones de lectura.
  • WriteTimeOut: Igual que ReadTimeOut solo que para operaciones de escritura.
  • Flush: Vacia el contenido de los buffers del flujo y obliga a que se escriban en el flujo.
  • GetBuffer: Regresa un arreglo de bytes sin signo que fueron utilizados para crear el flujo.
  • Seek: Coloca el apuntador lector del flujo en una determinada posición.

La clase MemoryStream se considera una buena solución siempre que se trate con grandes cantidades de datos y se requiera una rápida lectura y escritura de los mismos. Un detalle importante es que si en el constructor de la clase se establece el tamaño del arreglo, después no es posible cambiar el tamaño. Por eso es mejor utilizar un constructor vacío e ir utilizando el arreglo según se requiera.

Como ejemplo de su uso, utilizamos una sencilla proyecto GTK# de monodevelop que lee una imagen del disco duro, crea una copia de la imagen en un flujo memorystream para que esa imagen pueda ser ajustada a las medidas que el usuario proporcione y después pueda guardarla con el formato que elija de la aplicación. El diseño del GUI (Graphical User Interface) de la aplicación se muestra en la siguiente imagen, utilizando el designer de MonoDevelop.

Para este proyecto es necesario hacer referencias a los ensamblados: System.Drawing, System.Drawing.Image, System.IO como se muestra en la siguiente imagen:

Al oprimir F5 (Debug) ó Ctrl + F5 en Monodevelop se ejecutará la aplicación como se ve en la siguiente imagen:

Cuando se presiona el botón de “Select image” se ve el FileDialog para escoger la imagen desde el disco duro:

Una vez seleccionada la imagen se mostrará dentro del programa, aunque esa imagen no son los bytes originales del archivo sino los bytes que están contenidos en el buffer del objeto MemoryStream.

Una vez cargada la imagen puedes redimensionarla (en pixeles) de acuerdo a la anchura y la altura que teclees en los valores de los campos de texto width y height. Al presionar el botón “Change size”la imagen se ajustará a las nuevas dimensiones.

Una segunda imagen como ejemplo con diferentes medidas:

Puedes guardar la imagen modificada con utilizando uno de los formatos que están en los radiobuttons

Al presionar el botón “Save As” se abrirá un cuadro de dialogo (filechooser) para guardar la imagen en cualquier parte del sistema de archivos.

Podemos ver la imagen guardada en el sistema de archivos.

La aplicación inicia cuando se escoge un archivo de imagen del sistema de archivos para obtener su matriz de bytes. Esto lo hacemos con el siguiente código:

 byte[] GetBytesFromFile(string filename){
 byte[] imgBytes;
 using(FileStream fis = 
new FileStream(filename,FileMode.Open,FileAccess.Read))
 {
  BinaryReader reader = new BinaryReader(fis);
  imgBytes = reader.ReadBytes((int)fis.Length);
  reader.Close();
 }
  return imgBytes;
 } 

Entonces podemos esa matriz de bytes en el flujo MemoryStream y colocamos su buffer en el control (widget) Image.

 ms = new MemoryStream(fileBytes);
image2.Pixbuf = new Gdk.Pixbuf(ms.ToArray()); 

Aquí ya la aplicación esta lista para redimensionar la imagen esto se logra con el código del evento para el botón “Change Size”.

 protected virtual void OnBtnMakeThumbClicked (object sender, System.EventArgs e)
 {
  int w,h;
  try{
   if(!string.IsNullOrEmpty(txtWidth.Text) && 
      !string.IsNullOrEmpty(txtHeight.Text))
   {
    w = Convert.ToInt32(txtWidth.Text);
    h = Convert.ToInt32(txtHeight.Text);
    Bitmap bitmap = new Bitmap(ms);
    System.Drawing.Image.GetThumbnailImageAbort thumbnailCallback = 
    new System.Drawing.Image.GetThumbnailImageAbort(delegate(){ return false; });
    System.Drawing.Image thumbnail = bitmap.GetThumbnailImage(w,h,thumbnailCallback,IntPtr.Zero);
    byte[] thumbnailBytes = 
((byte[])new ImageConverter().ConvertTo(thumbnail,typeof(byte[])));
    ms = new MemoryStream(thumbnailBytes);
    image2.Pixbuf = new Gdk.Pixbuf(ms.ToArray());
   }
   else
    ShowMessageBox("width & height must be integers.");
  }catch(ApplicationException ex){
   ShowMessageBox(ex.Message);
  }
 } 

En este código se utiliza el método GetThumbnailImage de la clase Image:

 System.Drawing.Image thumbnail = bitmap.GetThumbnailImage(w,h,thumbnailCallback,IntPtr.Zero); 

NOTA: para este código hay que poner toda la ruta de los ensamblados para la clase image ya que exiten dos clases Image una en el ensamblado Gtk y otra en el ensambaldo System.Drawing por lo que hay que diferenciarlas o el compilador se confundirá.

De nueva cuenta se convierte la imagen en una matriz de bytes y la ponemos dentro del buffer del objeto MemoryStream, para después ponerla en el widget de imagen, esto lo realiza las siguientes líneas de código:

 
byte[] thumbnailBytes = 
((byte[])new ImageConverter().ConvertTo(thumbnail,typeof(byte[])));
 ms = new MemoryStream(thumbnailBytes);
image2.Pixbuf = new Gdk.Pixbuf(ms.ToArray()); 

Por último el código del botón para guardar la imagen en el sistema de archivos con el formato seleccionado en los botones de radio.

 protected virtual void OnBtnSaveAsClicked (object sender, System.EventArgs e)
 {
  string filename = null;
  ImageFormat imageFormat = (rbjpg.Active ? ImageFormat.Jpeg : (rbwmf.Active ? ImageFormat.Wmf:
                (rbpng.Active ? ImageFormat.Png : ImageFormat.Bmp)));
  try{
   Gtk.FileChooserDialog fc=  new Gtk.FileChooserDialog("Save image",
                              this,
                              FileChooserAction.Save,
                              "Cancel",ResponseType.Cancel,
                              "Save",ResponseType.Accept);
   if (fc.Run() == (int)ResponseType.Accept) 
{
    filename = fc.Filename + "." + imageFormat.ToString();
    fc.Hide();
    fc.Dispose();
    using(FileStream foutput = 
new FileStream(filename,FileMode.Create,FileAccess.Write))
   {
     Bitmap bmp = new Bitmap(ms);
     bmp.Save(foutput,imageFormat);
    }
   }
   else{
    fc.Hide();
    fc.Dispose();
   }
   
  }catch(Exception ex){
   ShowMessageBox(ex.Message);
  }
 } 

Aquí se utiliza la clase FileStream la cual representa un flujo hacia un archivo para guardar la imagen que se construye a partir de la matriz de imagen del buffer del objeto MemoryStream.

using(FileStream foutput = 
new FileStream(filename,FileMode.Create,FileAccess.Write))
{
 Bitmap bmp = new Bitmap(ms);
 bmp.Save(foutput,imageFormat);
}

martes, 22 de diciembre de 2015

Utilizando Clamav Antivirus en GNU Linux OpenSuse

Siempre ha sido un eje de debate si existen o no los códigos maliciosos (virus, gusanos, etc.) para GNU/Linux , sea cierto o falso, lo verdadero es que siempre es conveniente tener un buen antivirus instalado en el sistema y más si se se ocupa como servidor de correo o servidor de archivos para otros sistemas operativos, sin un antivirus estaremos esparciendo código malicioso por la organización sin darnos cuenta.

Un antivirus es un programa que detecta cierta actividad sospechosa en la computadora, como el acceso a un disco de almacenamiento, la firma de un código malicioso en memoria o un intento para borrar o modificar un archivo, etc.

Hoy día existen varias opciones de antivirus para Linux, siendo ClamAv antivirus (www.clamav.net) una de las más reconocidas y utilizadas, por ofrecer las siguientes características:

  • Open source GNU Public License version 2
  • Escaneo rápido (fast scanning)
  • POSIX compatible
  • Detecta aprox 1 millon de virus, gusanos y troyanos incluyendo los virus de macro de microsoft Office, malware, etc.
  • Escaneo dentro de archivos comprimidos. (zip,rar, 7zip, arj,tar,gzip,bzip2, sfx, cab, etc)

Los dos comandos básicos para empezar a utilizar este antivirus son freshclam y clamscan. El primero actualiza la base de datos de definiciones de códigos maliciosos y el segundo escanea los archivos en su búsqueda.

Lo primero que debe hacerse antes de escanear cualquier archivo o directorio, es actualizar la lista de definiciones de virus.

NOTA: Si un antivirus no tiene actualizada su lista de definiciones, es totalmente inservible.

Para actualizar las definiciones ejecutamos el siguiente comando como root

    # freshclam
  

como un usuario sin privilegios esto se ejecuta de la siguiente forma:

    $ sudo root freshclam
  

Bien ya una vez que actualizamos las definiciones podemos proceder a escanear con el comando clamscan de la siguiente manera:

   clamscan [options] [filename or directory]
  

Por ejemplo para escanear un archivo ejecutable de Windows llamado IPEYE.EXE utilizamos el siguiente comando:

   $ clamscan IPEYE.EXE
  

Cuando ClamAv encuentra un código malicioso en el archivo escaneado, muestra el nombre del código entre el nombre de este archivo y la palabra FOUND, tal como se muestra en la imagen.

Para escanear todos los archivos en el directorio actual, utilizamos el siguiente comando:

   $ clamscan
  

A manera de una guía rápida, aquí presento una lista de las opciones más útiles para utilizar ClamAV:

1) Para escanear todos los archivos desde la raíz, pero unicamente mostrar los archivos infectados y al detectar una amenaza hacer sonar una alerta.

    $ clamscan -r --bell -i /
   

2) para escanear todos los archivos dentro del directorio /home.

    $ clamscan -r /home
   

3) para escanear todos los archivos dentro del directorio /home/martin y mover los archivos infectados al directorio /home/martin/quarantine.

    $ clamscan -r --move=/home/martin/quarantine /home/martin 
   

4) para escanear todos los archivos del directorio /home/martin y eliminar los archivos infectados.

    $ clamscan -r /home/martin --remove
   

5) Para escanear todos los archivos dentro del directorio /home/martin y copiar los archivos al directorio /home/martin/quarantine.

    $ clamscan -r --copy=/home/martin/quarantine /home/martin
   

6) Para escanear los archivos línea a línea contenidos en un archivo de texto llamado: [files2scan.txt]

    $ clamscan -file-list=files2scan.txt
   

Es importante mencionar que ClamAV únicamente escaneara los archivos en donde el usuario tenga permisos, si se requiere un escaneo completo de todo el sistema se deberá ejecutar con una cuenta con permisos de superusuario.

Si tienes un virus no detectado por ClamAV incluso con la última actualización de sus definiciones, favor de envialo a la siguiente dirección: http://www.clamav.net/sendvirus

sábado, 22 de agosto de 2015

Instalando Oracle JRE y JDK en Open SuSe 13.2

Por cuestiones de dependencias no pude instalar los archivos jdk-8u45-linux-x64.rpm y jre-8u45-linux-x64.rpm respectivamente, aunque yo no trabajo con Java, en Linux es una dependencia importante para OpenOffice y DbVisualizer así como para muchos otros programas, por lo sino es posible hacerlo con los RPMS entonces recomiendo una instalación manual, siguiendo los siguientes pasos en OpenSuse 13.2:

1) Se descargan del sitio de Oracle los archivos jre-8u45-linux-x64.tar.gz y jdk-8u45-linux-x64.tar.gz en vez de los rpm.
2) Debajo del directorio /usr se crea el directorio java.(como root)

# mkdir /usr/java
3) Se copian/mueven los archivos descargados al directorio creado anteriormente.

4) Se descomprimen los archivos, de preferencia en el siguiente orden:

  1. tar -zxvf jre-8u45-linux-x64.tar.gz
  2. tar -zxvf jdk-8u45-linux-x64.tar.gz

Al descomprimirse se crea el árbol de archivos del jre y jdk respectivamente.

Bien, ahora sigue un paso crucial que es agregar los recién descomprimidos jre y jdk de Oracle al directorio

/etc/alternatives
. En mí caso desinstalé todas las versiones de openjdk que tenia en OpenSuse y de manera predeterminada me asigno el siguiente:
/usr/lib64/jvm/jre-1.5.0-gcj/bin/java 
como la máquina virtual. Esto porque al parecer existe software que necesita por fuerza una máquina virtual.

5) Así como ya existe una máquina virtual predeterminada y lo que quiero es usar la máquina virtual de Oracle, ejecuto los siguientes comandos para registrar la máquina virtual en el directorio

/etc/alternatives
.

update-alternatives --install /usr/bin/java java /usr/java/jre1.8.0_45/bin/java 1 
update-alternatives --install /usr/bin/java java /usr/java/jdk1.8.0_45/bin/java 0

6) Ahora ejecuto el comando:

update-alternatives --config java

Si todo sale correctamente OpenSuse mostrará la siguiente pantalla:


En este punto seleccionamos la máquina virtual que necesitemos utilizar, en mí caso seleccioné la opción 2 que es el JRE de Oracle.

Si todo sale bien al ejecutar el comando:

java -version

Mostrará el siguiente resultado, mostrando la versión correspondiente a la JVM de Oracle.


Como último detalle hay que agregar al entorno la variable JAVA_HOME, esto en mí caso lo hice agregando las siguientes líneas al archivo .profile

JAVA_HOME = /usr/java/jdk1.8.0_45
export JAVA_HOME

En mí caso lo hice apuntando al jdk en vez del jre, esto lo hice para instalar netbeans posteriormente.

Más información en los siguientes enlaces:

lunes, 17 de agosto de 2015

Entendiendo DataContract y DataMember de WCF con GTK# y MonoDevelop

Un contrato se define como: “un acuerdo entre partes que se debe de cumplir de manera obligada por cada una de ellas”, los contratos deben ser claros, definidos y sin ambigüedad para no dar lugar a una mala interpretación.

Windows Communication Foundation WCF utiliza este mismo concepto para definir un acuerdo entre clientes y servicios de un ambiente SOAP, en este contexto el contrato WCF define:

  • Las operaciones soportadas por el servicio.
  • Los parámetros y los tipos de datos que regresan las operaciones.
  • La estructura de los tipos de datos complejos que se pasan.
  • Errores que pueden ocurrir al ejecutar una operación.

Hay que recordar que los ambientes SOA son heterogéneos, por eso es recomendable que el contrato este separado de la implementación y que esa implementaciòn evite tipos específicos de su tecnología ya que esto podría causar problemas de portabilidad para comunicarse con un cliente que este implementado con una tecnología diferente.

Los contratos en el contexto SOA proporcionan todo el metadata necesario para comunicarse con el servicio, el metadata describe: tipos de datos, operaciones, patrones de intercambio de mensajes y el protocolo de transporte que se utiliza.

WCF define tres principales tipos de contrato:

  • Service contract: Define las operaciones que el servicio tendrá disponibles para que los clientes puedan invocar mediante una petición request.
  • Data contract: Define la estructura de los datos que serán incluidos en el intercambio de los mensajes que van y vienen del servicio al cliente y viceversa.
  • Message contract: Habilita el control de los headers (encabezados) que utilizan los mensajes y como son utilizados por el servicio.

WCF utiliza el protocolo SOAP (Simple Object Access Protocol) como el formato de mensajería para las operaciones, cada operación desde el service contract (contrato de servicio) se vuelve un elemento en el cuerpo del mensaje cuando la operación es invocada.

En la definición de la operación se especifican los datos que son intercambiados entre el cliente y el servicio cuando la operación es invocada, como ejemplo la siguiente operación createAuthor para agregar un autor a una base de datos:

            [OperationContract]
        string CreateAuthor (string Firstname,
            string Lastname,
            DateTime Birthdate,
            bool Gender); 
        

  • Los parámetros de entrada en la operación definen los contenidos del cuerpo del mensaje enviado desde el cliente al servicio.
  • El valor de retorno en la operación define los contenidos del cuerpo del mensaje enviado desde el servicio al cliente.

De manera predetermina el runtime de WCF toma la responsabilidad de convertir los tipos simples de .NET a tipos SOAP y viceversa, definiendo los tipos simples como:

  • Primitivos como enteros y cadenas.
  • Tipos compuestos simples (struct) como DateTime
  • Tipos basados en XML como un XmlElement
  • Colecciones
  • Enumeraciones

Sin embargo para tipos mas complejos como las clases o los tipos de datos definidos por el usuario es recomendable que se programe manualmente el mecanismo de serialización/ desealización y esto se logra definiendo un DataContract para ese tipo de dato.

En síntesis, para que un tipo de dato complejo sea serializado por el serializador de WCF se le debe aplicar un atributo DataContract a la definición de la clase y aplicar un atributo DataMember a cada uno de los campos que necesiten ser serializados sean estos un miembro de datos o una propiedad, los campos a los que no se les aplique el atributo DataMember son ignorados por el serializador.

Como ejemplo de los atributos DataContract y DataMember definimos una clase Author que encapsula los parámetros de entrada de una nueva versión de la operación createAuthor.

Ahora la versión de la operación createAuthor

            
        [OperationContract]
        string CreateAuthor (Author a); 
        

Adicionalmente podemos agregarle las siguientes propiedades al atributo DataContract:

  • Name: Define el nombre del tipo que se genera en el metadata. Por default se utiliza el nombre del tipo.
  • NameSpace: Define el namespace utilizado en el esquema. Por default utiliza “http://tempuri.org”

De igual modo podemos agregar las siguientes propiedades al atributo DataMember:

  • Name: Define el nombre que se utilizará en la generación del metadata. Por default es el nombre del campo.
  • IsRequired: Campo requerido, arroja una excepción si este campo no está cuando ocurra la deserialización.
  • EmitDefaultValue: Le dice al serializador que incluya el valor por default del campo cuando ocurra la serialización.
  • Order: Indica la posición del campo en la secuencia de serialización.

Ejemplo de una aplicación GTK# y un servicio WCF

Ahora un ejemplo funcional de una aplicación GTK# que hace uso de un servicio WCF para insertar y obtener registros de una tabla de autores en una base de datos PostgreSQL, aquí esta el esquema de la tabla. (Para más información de una solución WCF en Monodevelop consultar Introducción a WCF con GTK# y MonoDevelop)

A continuación el código del store en PL/SQL para insertar un registro

Bien ahora una vista de la solución en el explorador de soluciones de Monodevelop.

La solución se llama Samples.WCF.AuthorsCatalog y tiene los siguientes proyectos:

  • Samples.WCF.AuthorsCatalog: Este proyecto contiene la entidad Author que es la clase que es serializada por el runtime para intercambiar los datos entre la aplicación GTK# y el servicio WCF.
  • Samples.WCF.AuthorsCatalogService: Este es el proyecto del servicio WCF, contiene la clase AuthorsDataHelper que es la que se comunica con la base de datos, la clase AuhorServiceImplementation que es la implementación del servicio y la interface IAuthorServiceContract que es el contrato del servicio.
  • Samples.WCF.AuthorServiceHost: Este proyecto contiene una aplicación de consola que es el hosting para el servicio WCF.
  • Samples.WCF.GUIAuthorsCatalog: Este proyecto contiene al cliente GTK# que tiene una GUI (Graphical User Interface) que utiliza la clase proxy para comunicarse con el servicio WCF.

El proyecto Samples.WCF.AuthorsCatalog ejemplifica la aplicación de los atributos DataContract y DataMember con el código de la siguiente clase:

Esta clase sirve como argumento de petición y de respuesta en el contrato y por consecuente en la implementación del servicio como muestro en el código fuente del contrato y de la implementación que se encuentran en el proyecto [Samples.WCF.AuthorsCatalogService]. Aquí esta el código fuente del contrato del servicio IAuthorServiceContract:

Ahora el código fuente de la implementación AuthorServiceImplementation:

Para ejecutar la solución primeramente ejecutamos el programa de consola [Samples.WCF.AuthorServiceHost.exe] que activa el proceso que alberga el servicio WCF, el ejecutable se encuentra dentro del directorio “bin” debajo de la ruta Samples.WCF.AuthorsCatalog/Samples.WCF.AuthorServiceHost/bin/Debug. con el siguiente comando:

            $ mono Samples.WCF.AuthorServiceHost.exe
        

Ahora ejecutamos la solución desde MonoDevelop.

Podemos probar la aplicación al agregar un par de autores, como se muestran en la siguientes imágenes, después de ingresar cada autor pulsamos el botón refresh grid

Cada uno de estos eventos se manejan a tráves de la clase proxy que se genera con el siguiente comando aplicándolo a la dll del servicio.

            $svcutil /out:AuthorServiceReference.cs  Samples.WCF.AuthorsCatalogService.dll
        

Conclusión

WCF proporciona facilidades para tomar el control de la serialización de datos. Sin embargo, hay que entender que entre más control manual tomes de tus servicios estos tenderán a ser menos interoperables.

domingo, 10 de mayo de 2015

Error de MonoDevelop "MonoDevelop.GnomePlatform 5.0"

Después de instalar MonoDevelop utilizando el repositorio del sitio Monodevelop.com para OpenSuse, al intentar ejecutar la aplicación me aparecio esta pantalla.

Este error ya me había aprecido con una versión anterior de Monodevelop y Opensuse, al parecer es debido a una dependencia que no esta instalada.

Si vemos el error a detalle, se lanza por la falta de una dependencia , el nombre de esa dependencia es:

  libgnomeui
  

En OpenSuse se instala esta dependencia utilizando el instalador y desinstalador de software de YAST, una vez seleccionando el paquete que contiene la dependencia e instalandola el error se corrige.

Después de la instalación de la dependencia tendremos una versión funcionando y sin error de MonoDevelop.

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:

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.

jueves, 28 de marzo de 2013

Cursores (Cursors) y Funciones (Functions) PLpg/SQL en PostgreSQL

Un cursor es el nombre que recibe un apuntador (pointer) de solo lectura hacia un conjunto de datos ( resultset) que se obtiene de una consulta SQL asociada, para los cursores pensemos en términos de arreglos similar a los arreglos de un lenguaje de programación, los cursores nos sirven para procesar una a una las filas que componen un conjunto de resultados en vez de trabajar con todos los registros como en una consulta tradicional de SQL.

Los cursores pueden declararse con una consulta SQL sin parámetros o con parámetros, en donde el tamaño del conjunto de datos depende del valor de los parámetros de la consulta. Así por ejemplo declaramos:

Los cursores pueden emplearse dentro de funciones PL/SQL para que las aplicaciones que accedan a PostgreSQL puedan utilizarlos más de una vez.

Como ejemplo tenemos una base de datos llamada myinvoices, que contiene las siguientes tablas:

invoices
invoicedetails

Ahora como ejemplo de un cursor utilizado dentro de una función, declaramos una función utilizando un cursor con parámetros para devolver el total de facturas emitidas dentro de un rango de fechas.

Existen dos errores comunes cuando trabajamos con cursores:

  1. Si tratas de abrir un cursor que ya se encuentra abierto PostgreSQL enviará un mensaje de “cursor [name] already in use”
  2. Si tratas de ejecutar FETCH en un cursor que no ha sido abierto, PostgreSQL enviará un mensaje de “cursor [name] is invalid”

Cuando utilizamos el comando FETCH obtenemos una por una las filas del conjunto de resultados después de cada fila procesada el cursor avanza a la siguiente fila y la fila procesada puede ser entonces utilizada dentro de una variable.

Consultamos los datos de la tabla invoices

Ejecutamos la función, usando los siguientes argumentos:
Obtenemos los siguientes resultados:

miércoles, 6 de marzo de 2013

Entendiendo Transacciones (Transactions) con ADO .NET y PostgreSQL

En .NET las transacciones son representadas por la clase Transaction que implementa la interfaz IDbTransaction definida dentro del ensamblado System.Data esta interfaz proporciona los métodos:

Commit: Confirma la transacción y persiste los datos.
Rollback: Regresa los datos a un estado anterior a la transacción.


Esta interfaz se utiliza para crear clases Transaction asociadas con un proveedor especifico, así para SQL Server tenemos SqlTransaction, para Oracle OracleTransaction y para PostgreSQL NpgsqlTransaction.

La ventaja de crear transacciones en .NET y no en la bases de datos es proporcionar a la aplicaciones la capacidad de las transacciones en caso de utilizar una base de datos que no proporcione o soporte esa característica.
Como ejemplo escribimos un programa en MonoDevelop que utiliza las tablas: Invoices e InvoiceDetails que se utilizaron en esta entrada este programa utiliza una transaction y 4 comandos SQL con los que guarda una factura (invoice) con dos detalles (invoice details), actualizando el total de la factura conforme a la cantidad de productos y su precio.

using System;
using System.Collections.Generic;
using System.Text;
using Npgsql;
using NpgsqlTypes;
using System.Data;
using System.Globalization;


namespace MonoTransExamples
 {
  class Program
  {
  static string connString = "Server=127.0.0.1;Port=5432;Database=myinvoices;
User ID=postgres;Password=Pa$$W0rd";

  static string commandText1 = "INSERT INTO invoices(invoice_number,invoice_date,
invoice_total)" + "VALUES(:number,:date,:total)";

  static string commandText2 = "SELECT MAX(invoice_id) FROM invoices";
  static string commandText3 = "INSERT INTO invoicedetails
(invoice_id,invoiced_description,invoiced_quantity,invoiced_amount)" +
"VALUES(:id,:description,:quantity,:amount)";

static string commandText4 = "UPDATE invoices SET invoice_total =
:total WHERE invoice_id = :id"; static void Main(string[] args) { bool success = false; int recordsAffected = 0; Invoice invoice = new Invoice { Invoice_number = 2099, Invoice_date = new DateTime(2013,01,29,6,6,6,100,Calendar.CurrentEra) }; Invoicedetails[] details = { new Invoicedetails{ Invoice = invoice, Invoiced_description = "walkie-talkie 22-Channel", Invoiced_quantity = 3, Invoiced_amount = 19.99M }, new Invoicedetails{ Invoice = invoice, Invoiced_description = "2 GB SD Memory Card", Invoiced_quantity = 4, Invoiced_amount = 6.99M } }; NpgsqlTransaction transaction = null; NpgsqlConnection conn = null; try { conn = new NpgsqlConnection(connString); conn.Open(); transaction = conn.BeginTransaction(); using (NpgsqlCommand cmd1 = new NpgsqlCommand(commandText1, conn, transaction)) { cmd1.CommandType = CommandType.Text; cmd1.Parameters.Add("number", NpgsqlDbType.Integer, 4).Value = invoice.Invoice_number; cmd1.Parameters.Add("date", NpgsqlDbType.Timestamp).Value = invoice.Invoice_date; cmd1.Parameters.Add("total", NpgsqlDbType.Money).Value = invoice.Invoice_total; recordsAffected = cmd1.ExecuteNonQuery(); } Console.WriteLine("{0} invoiced inserted",recordsAffected); if (recordsAffected > 0) { using (NpgsqlCommand cmd2 = new NpgsqlCommand(commandText2, conn, transaction)) { cmd2.CommandType = CommandType.Text; invoice.Invoice_id = Convert.ToInt32(cmd2.ExecuteScalar()); } Console.WriteLine("Invoice Id {0} ",invoice.Invoice_id); } if (invoice.Invoice_id > 0) { recordsAffected = 0; foreach (Invoicedetails invd in details) { invd.Invoice.Invoice_id = invoice.Invoice_id; using (NpgsqlCommand cmd3 = new NpgsqlCommand(commandText3, conn, transaction)) { cmd3.CommandType = CommandType.Text; cmd3.Parameters.Add("id", NpgsqlDbType.Integer, 4).Value = invd.Invoice.Invoice_id; cmd3.Parameters.Add("description", NpgsqlDbType.Varchar, 512).Value = invd.Invoiced_description; cmd3.Parameters.Add("quantity", NpgsqlDbType.Smallint).Value = invd.Invoiced_quantity; cmd3.Parameters.Add("amount", NpgsqlDbType.Money).Value = invd.Invoiced_amount; recordsAffected += cmd3.ExecuteNonQuery(); } invoice.Invoice_total += invd.Invoiced_amount * invd.Invoiced_quantity; } Console.WriteLine("Total: {0} ,{1} records affected ",invoice.Invoice_total,recordsAffected); } if (recordsAffected == details.Length) { using (NpgsqlCommand cmd4 = new NpgsqlCommand(commandText4, conn, transaction)) { cmd4.CommandType = CommandType.Text; cmd4.Parameters.Add("total",NpgsqlDbType.Money).Value = invoice.Invoice_total; cmd4.Parameters.Add("id",NpgsqlDbType.Integer).Value = invoice.Invoice_id; recordsAffected = cmd4.ExecuteNonQuery(); } Console.WriteLine("Updated invoice {0} with total {1} ", invoice.Invoice_id,invoice.Invoice_total); } if(recordsAffected > 0) success = true; }catch(NpgsqlException ex){ Console.WriteLine("Error {0} ", ex.Message); }finally{ if (success) transaction.Commit(); else transaction.Rollback(); if (conn != null) if (conn.State == ConnectionState.Open) conn.Close(); } Console.WriteLine("Done"); Console.ReadLine(); } } class Invoice { public int Invoice_id { set; get; } public int Invoice_number { set; get; } public DateTime Invoice_date { set; get; } public Decimal Invoice_total { set; get; } } class Invoicedetails { public int Invoiced_id { set; get; } public Invoice Invoice { set; get; } public string Invoiced_description { set; get; } public int Invoiced_quantity { set; get; } public Decimal Invoiced_amount { set; get; } } }

Este programa asocia una transacción con una conexión abierta.

conn = new NpgsqlConnection(connString);
conn.Open();
transaction = conn.BeginTransaction();
Se crea cada uno de los comandos SQL dentro del alcance de la transacción
using (NpgsqlCommand cmd1 = new NpgsqlCommand(commandText1, conn, transaction))
using (NpgsqlCommand cmd2 = new NpgsqlCommand(commandText2, conn, transaction))
using (NpgsqlCommand cmd3 = new NpgsqlCommand(commandText3, conn, transaction))
using (NpgsqlCommand cmd4 = new NpgsqlCommand(commandText4, conn, transaction)) 
Si todos los comandos se ejecutan correctamente se llama al método Commit() de lo contrario se llama al método Rollback y se cierra la conexión.
  if (success)
            transaction.Commit();
        else
            transaction.Rollback();
        if (conn != null)
            if (conn.State == ConnectionState.Open)
                conn.Close();
Es importante recordar que la transacción queda pendiente hasta que no se confirme (commit) o se cancele (rollback), si se cierra la conexión mediante el método Close se ejecuta un rollback en todas las transacciones pendientes.

martes, 5 de marzo de 2013

Entendiendo Transacciones con PostgreSQL.

Hay operaciones en los sistemas de bases de datos (DBMS) que no pueden expresarse como una única operación SQL sino como el resultado de un conjunto de dos o más operaciones SQL, cuyo éxito depende de que cada una de esas operaciones se ejecute correctamente ya que si una de ellas falla se considera que toda la operación fallo.
El control de transacciones es una característica fundamental de cualquier DBMS (como PostgreSQL,MS SQL Server ú Oracle) esto permite agrupar un conjunto de operaciones o enunciados SQL en una misma unidad de trabajo discreta , cuyo resultado no puede ser divisible ya que solo se considera el total de operaciones completadas, si hay una ejecución parcial el DBMS se encarga de revertir esos cambios para dejar la información consistente.

Una transacción tiene cuatro características esenciales conocidas como el acrónimo ACID:

  • Atomicity(Atomicidad): Una transacción es una unidad atómica o se ejecutan las operaciones múltiples por completo o no se ejecuta absolutamente nada, cualquier cambio parcial es revertido para asegurar la consistencia en la base de datos.
  • Consistency (Consistencia): Cuando finaliza una transacción debe dejar todos los datos sin ningún tipo de inconsistencia, por lo que todas las reglas de integridad deben ser aplicadas a todos los cambios realizados por la transacción, o sea todas las estructuras de datos internas deben de estar en un estado consistente.
  • Isolation (Aislamiento o independencia): Esto significa que los cambios de cada transacción son independientes de los cambios de otras transacciones que se ejecuten en ese instante, o sea que los datos afectados de una transacción no están disponibles para otras transacciones sino hasta que la transacción que los ocupa finalice por completo.
  • Durability (Permanencia): Después de que las transacciones hayan terminado, todos los cambios realizados son permanentes en la base de datos incluso si después hay una caída del DBMS.

Las transacciones en PostgreSQL utilizan las siguientes palabras reservadas:

BEGIN: Empieza la transacción

SAVEPOINT [name]: Le dice al DBMS la localización de un punto de retorno en la transacción si una parte de la transacción es cancelada. El DBMS guarda el estado de la transacción hasta este punto.

COMMIT: Todos los cambios realizados por las transacciones deben ser permanentes y accesibles a las demás operaciones del DBMS.

ROLLBACK [savepoint]: Aborta la actual transacción todos los cambios realizados deben ser revertidos.


Para poner en práctica estos comandos utilizaremos dos tablas relacionadas Invoices y InvoiceDetails.

Agregamos un par de registros, cada uno dentro de una transacción en el primer registro el commit (confirmación) se realiza de forma automática al terminar la transacción con el comando END.

En el segundo registro utilizamos el comando COMMIT de forma explicita para hacer los cambios permanentes.
Ahora insertamos un nuevo registro y eliminamos un par pero en vez de confirmar la transacción con COMMIT deshacemos los cambios y regresamos los registros a su estado original, utilizando ROLLBACK.
Aquí otro ejemplo del uso de ROLLBACK.

En el siguiente bloque de PL/SQL anónimo vamos a utilizar los comandos anteriores además del comando SAVEPOINT el cuál permite deshacer parcialmente los cambios hechos dentro de una transacción y no toda la transacción por completo.
Persistimos entonces solo los cambios antes del SAVEPOINT, los cambios realizados después serán revertidos por el comando ROLLBACK.