Mostrando entradas con la etiqueta PostgreSQL. Mostrar todas las entradas
Mostrando entradas con la etiqueta PostgreSQL. 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.

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.

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.

domingo, 22 de septiembre de 2013

Regular Expressions (expresiones regulares) en PostgreSQL

Cuando las búsquedas con el operador Like no son lo suficientemente flexibles para localizar los registros que necesitamos, PostgreSQL permite las búsquedas de registos mediante el uso de regular expressions (expresiones regulares).

En este par de posts: Regular Expressions en PL/SQL Oracle, parte I y parte II coloque unos ejemplos de la utilización de expresiones regulares en Oracle.

Ahora utilizaré los mismos ejercicios de esas entradas, pero ahora para mostrar las regular expressions en PostgreSQL.

Creamos una tabla llamada membership:

Ahora insertamos algunos registros:

El operador ~

Como primer ejemplo utilizamos el operador ~ (LIKE versión regular expression) en la consulta para buscar en la columna MEMBERSHIP_LASTNAME aquellos registros que empiecen con la letra D.

Aquí un segundo ejemplo en donde buscamos en la columna MEMBERSHIP_LASTNAME aquellos registros que comiencen con las letras de la A a la F.

El operador inverso !

Ahora mostraré los ejemplos anteriores utilizando el operador inverso ! o la negación del operador ~ para el primer ejemplo, esta consulta busca en la columna MEMBERSHIP_LASTNAME aquellos registros que no empiecen con la letra D.

El mismo operador de negación para el segundo ejemplo, esta consulta busca en la columna MEMBERSHIP_LASTNAME aquellos registros que no empiecen con las letras de la A a la F.

El operador case-insensitive *

Tanto ~ como ! son case-sensitive, si necesitas que ambos operadores sean case-insensitive se combinan con el operador * , como en el ejemplo siguiente donde buscamos en la columna MEMBERSHIP_LASTNAME aquellos registros que después del primer caracter tenga la letra O mayúscula repetida una o más veces.

Esta búsqueda no dio resultados porque excepto la primera letra de los registros en la columna MEMBERSHIP_LASTNAME el resto se encuentran en letras minúsculas, entonces para que la consulta encuentre resultados utilizamos el operador *.

Ahora si la consulta anterior mostro los resultados.

domingo, 21 de abril de 2013

Usando el control RadComboBox de Telerik con PostgreSQL

Aunque ASP.NET ofrece una extensa colección de Web Controls que proporcionan al desarrollador una programación fácil, estandarizada y orientada a objetos, también existe como parte del ecosistema de ASP.NET la suite de controles Telerik que complementan y ofrecen más funcionalidad que los Web Controls predeterminados incluidos en el conjunto estándar de ASP.NET.

Del conjunto de controles de la suite Telerik, mostraré brevemente el uso del control RadComboBox de Telerik, el cual extiende la funcionalidad proporcionada por el control DropDownList de ASP.NET al tener no solamente las características de este control sino capacidades adicionales como:la carga de datos de diferentes colecciones de datos, API con funciones de servidor y de cliente, el uso de templates para mejorar la aparencia visual y la propiedad de cargar items bajo demanda para mejorar el rendimiento.

A continuación un sencillo ejemplo para mostrar dos características básicas de este control:

  • La carga de items bajo demanda (Load on Demand)
  • El uso de templates para la aparencia visual de los items.
El ejemplo consiste de una página ASP.NET que consulta una tabla de products que contiene una lista de productos clínicos de una base de datos myinvoices, la tabla tiene el siguiente esquema:

Consultamos la lista de productos

El ejemplo esta compuesto por 6 clases: DataManager, Logger, PostgreSQLCommand,PostgreSQLDataBase, Product, ProductFilters y dos páginas ASP.NET: Default.aspx y Default2.aspx.

El código de la clase DataManager

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Npgsql;
using System.Data;
using System.Text;

namespace TestRadComboBox
{
public class DataManager
{
public List GetProducts(ProductFilters filters) {
    StringBuilder commandText = new StringBuilder("SELECT product_id, ")
    .Append("product_code, ")
    .Append("product_name ")
    .Append("FROM Products ")
    .AppendFormat("WHERE product_name Like '%{0}%'",filters.ProductName.ToUpper());
    List productList = null;
    Product p = null;
    using(NpgsqlDataReader reader = PostgreSQLCommand.GetReader(commandText.ToString(),
        null,
        CommandType.Text)){
        productList = new List();
        while (reader.Read()) {
            p = new Product();
            p.Product_ID = Convert.ToInt32(reader["product_id"]);
            p.ProductCode = reader["product_code"].ToString();
            p.ProductName = reader["product_name"].ToString();
            productList.Add(p);
        } 
    }
    return productList;
}
}
}

El código de la clase Logger

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace TestRadComboBox
{
public class Logger
{
public static void LogWriteError(string s)
{
using (FileStream stream = new FileStream("log.txt", 
    FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
    StreamWriter sw = new StreamWriter(stream);
    sw.BaseStream.Seek(0, SeekOrigin.End);
    sw.Write(s);
    sw.Flush();
    sw.Close();
}

}
}
}

El código de la clase PostgreSQLCommand

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using Npgsql;

namespace TestRadComboBox
{
internal sealed class PostgreSQLCommand
{

internal static NpgsqlDataReader GetReader(string commandText,
NpgsqlParameter[] parameters,System.Data.CommandType cmdtype){
NpgsqlDataReader resp = null;
try{
 NpgsqlConnection conn = PostgreSQLDataBase.GetInstance().GetConnection();
 using (NpgsqlCommand cmd = new NpgsqlCommand(commandText, conn))
 {
            if (parameters != null)
                cmd.Parameters.AddRange(parameters);
  resp = cmd.ExecuteReader(CommandBehavior.CloseConnection);
 }
 return resp;
}catch(Exception ex){
 Logger.LogWriteError(ex.Message);
 throw ex;
}
}
}
}

El código de la clase PostgreSQLDataBase

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Configuration;
using Npgsql;
using NpgsqlTypes;

namespace TestRadComboBox
{
internal sealed class PostgreSQLDataBase
{
static NpgsqlConnection _conn = null;
static PostgreSQLDataBase Instance = null;
string ConnectionString = null;
private PostgreSQLDataBase()
{
    try
    {
        ConnectionString = ConfigurationManager.
            ConnectionStrings["myinvoices"].ConnectionString;
    }
    catch (Exception ex)
    {
        Logger.LogWriteError(ex.Message);
        throw ex;
    }
}
private static void CreateInstance()
{
    if (Instance == null)
    { Instance = new PostgreSQLDataBase(); }
}
public static PostgreSQLDataBase GetInstance()
{
    if (Instance == null)
        CreateInstance();
    return Instance;
}
public NpgsqlConnection GetConnection()
{
    try
    {
        _conn = new NpgsqlConnection(ConnectionString);
        _conn.Open();
        return _conn;
    }
    catch (Exception ex)
    {
        Logger.LogWriteError(ex.Message);
        throw ex;
    }
}
}
}

El código de la clase Product

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace TestRadComboBox
{
public class Product
{
    public int Product_ID { set; get; }
    public string ProductCode { set; get; }
    public string ProductName { set; get; }
}
}

El código de la clase ProductFilters

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace TestRadComboBox
{
public struct ProductFilters
{
    public string ProductName { set; get; }

}
}

El código de la página ASP.NET Default.aspx

<%@ Page Language="C#" AutoEventWireup="true" %>
<%@ Import Namespace="TestRadComboBox" %>
<%@ Register assembly="Telerik.Web.UI" 
namespace="Telerik.Web.UI" 
tagprefix="telerik" %>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title></title>
</head>
<script runat="server">

    protected void cmbProducts_OnItemsRequested(object sender,
        RadComboBoxItemsRequestedEventArgs args) {
        DataManager dm = new DataManager();
        ProductFilters filter = new ProductFilters { 
            ProductName = args.Text
        };

        if (cmbProducts.DataSource == null)
        {
            cmbProducts.DataSource = dm.GetProducts(filter);
            cmbProducts.DataValueField = "Product_ID";
            cmbProducts.DataTextField = "ProductName";
            cmbProducts.DataBind();
        }
    }
</script>
<body>
<form id="form1" runat="server">
<telerik:RadScriptManager ID="RadScriptManager1" runat="server">
    <Scripts>
<asp:ScriptReference Assembly="Telerik.Web.UI" Name="Telerik.Web.UI.Common.Core.js">
</asp:ScriptReference>
<asp:ScriptReference Assembly="Telerik.Web.UI" Name="Telerik.Web.UI.Common.jQuery.js">
</asp:ScriptReference>
<asp:ScriptReference Assembly="Telerik.Web.UI" Name="Telerik.Web.UI.Common.jQueryInclude.js">
</asp:ScriptReference>
</Scripts>
</telerik:RadScriptManager>
<div>
Please select a product:
    <telerik:RadComboBox ID="cmbProducts" 
    DropDownWidth="333px"
    OnItemsRequested="cmbProducts_OnItemsRequested"
    EnableLoadOnDemand="true"
    Runat="server">
    </telerik:RadComboBox>
</div>
</form>
</body>
</html>


La Carga de items bajo demanda
Una de las capacidades más utilizadas del control RadComboBox es la capacidad de cargar las opciones o items bajo demanda es decir construir la consulta en base a las coincidencias de los items y lo teclado por el usuario, esta capacidad se programa con el metódo cmbProducts_OnItemsRequested

protected void cmbProducts_OnItemsRequested(object sender,
        RadComboBoxItemsRequestedEventArgs args) {
        DataManager dm = new DataManager();
        ProductFilters filter = new ProductFilters { 
            ProductName = args.Text
        };

        if (cmbProducts.DataSource == null)
        {
            cmbProducts.DataSource = dm.GetProducts(filter);
            cmbProducts.DataValueField = "Product_ID";
            cmbProducts.DataTextField = "ProductName";
            cmbProducts.DataBind();
        }
    }

En este metódo se contruye una clase ProductFilter que contine el texto tecleado por el usuario en el RadComboBox, una vez creada esta clase se pasa como argumento al metódo GetProducts de la clase DataManager dentro de este metódo se construye la consulta SQL que filtra mediante un comando LIKE la consulta de la tabla Products hacia la lista (List) que sirve de DataSource para el RadComboBox.

//Here we past the filter in order to build the SQL Query
public List GetProducts(ProductFilters filters) {
    StringBuilder commandText = new StringBuilder("SELECT product_id, ")
    .Append("product_code, ")
    .Append("product_name ")
    .Append("FROM Products ")
    .AppendFormat("WHERE product_name Like '%{0}%'",filters.ProductName.ToUpper());
    List productList = null;
    Product p = null;
    using(NpgsqlDataReader reader = PostgreSQLCommand.GetReader(commandText.ToString(),
        null,
        CommandType.Text)){
        productList = new List();
        while (reader.Read()) {
            p = new Product();
            p.Product_ID = Convert.ToInt32(reader["product_id"]);
            p.ProductCode = reader["product_code"].ToString();
            p.ProductName = reader["product_name"].ToString();
            productList.Add(p);
        } 
    }
    return productList;
}

Al ejecutar la página vemos el resultado como en las siguientes imágenes:

Se realiza la búsqueda de las coincidencias entre lo tecleado por el usuario y el listado de productos

Es característica mejora el rendimiento de los formularios.



Uso de un template para los items.

Uno de los aspectos visuales más llamativos de este control Telerik es el utilizar un template para personalizar la aparencia de la lista de elementos seleccionables, como ejemplo en la siguiente página Default2.aspx utilizamos la misma funcionalidad de la página Default.aspx con la diferencia de utilizar una tabla como template para los items.

El código de la página ASP.NET Default2.aspx que muestra el uso de un template para los items.

<%@ Page Language="C#" AutoEventWireup="true" %>
<%@ Import Namespace="TestRadComboBox" %>
<%@ Register assembly="Telerik.Web.UI" namespace="Telerik.Web.UI" 
tagprefix="telerik" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title></title>
</head>
<script runat="server">


protected void cmbProducts_OnItemsRequested(object sender,
    RadComboBoxItemsRequestedEventArgs args) {
    DataManager dm = new DataManager();
    ProductFilters filter = new ProductFilters { 
        ProductName = args.Text
    };

    if (cmbProducts.DataSource == null)
    {
        cmbProducts.DataSource = dm.GetProducts(filter);
        cmbProducts.DataValueField = "Product_ID";
        cmbProducts.DataTextField = "ProductName";
        cmbProducts.DataBind();
    }
}
</script>
<body>
<form id="form1" runat="server">
<telerik:RadScriptManager ID="RadScriptManager1" runat="server">
<Scripts>
<asp:ScriptReference Assembly="Telerik.Web.UI" Name="Telerik.Web.UI.Common.Core.js">
</asp:ScriptReference>
<asp:ScriptReference Assembly="Telerik.Web.UI" Name="Telerik.Web.UI.Common.jQuery.js">
</asp:ScriptReference>
<asp:ScriptReference Assembly="Telerik.Web.UI" Name="Telerik.Web.UI.Common.jQueryInclude.js">
</asp:ScriptReference>
</Scripts>
</telerik:RadScriptManager>
<div>
Please select a product:
<telerik:RadComboBox ID="cmbProducts" 
DropDownWidth="333px"
OnItemsRequested="cmbProducts_OnItemsRequested"
EnableLoadOnDemand="true"
HighlightTemplatedItems="true"
Runat="server">
<HeaderTemplate>
<table cellspacing="3" width="99%">
<tr>
<td width="44%">Code</td>
<td>Name</td>
</tr>
</table>
</HeaderTemplate>
<ItemTemplate>
<table cellspacing="3" width="99%">
<tr>
<td width="44%"><%#DataBinder.Eval(Container.DataItem, "ProductCode")%></td>
<td><%#DataBinder.Eval(Container.DataItem, "ProductName")%></td>
</tr>
</table>
</ItemTemplate>
</telerik:RadComboBox>
</div>
</form>
</body>
</html>

Al ejecutar la página Default2.aspx, veremos el resultado como en la siguiente imágen:

Por último el fragmento de código del archivo Web.conf del ejemplo, en donde se pone la cadena de conexión.

  <connectionStrings>
    <add connectionString="Server=127.0.0.1;Port=5432;Database=myinvoices;User ID=postgres;Password=Pa$$W0rd" name="myinvoices" />
  </connectionStrings>

lunes, 8 de abril de 2013

Utilizando refcursors (cursores de referencia) en PostgreSQL

Esta una entrada complementaria a un post anterior acerca del uso de cursores en PostgreSQL.

Después de que se han obtenido los registros de un cursor abierto con el comando FETCH entonces ya podemos asignar los valores de esos registro a ciertos tipos de variables, los tipos de variable que se utilizan para el trabajo con cursores son:

  1. Mismo tipo de datos que cada una de las columnas obtenidas por el cursor.
  2. TYPE
  3. RECORD
  4. ROWTYPE
Como ejemplo para el primer tipo de variable programaremos una función PL/SQL con refcursors,en PostgreSQL un refcursor (cursor de referencia) es un cursor que se construye en runtime(tiempo de ejecucción), como su nombre indica es la referencia a un cursor.
Usaremos el siguiente schema como parte del ejemplo:

Este schema representa una estructura básica para la contabilidad de pagos y documentos, en donde cada pago recibido debe ser aplicado a un documento y reducir su monto.

Consultamos la tabla invoices

Consultamos los pagos

La aplicación de un pago hacia un documento debe generar un registro en la tabla payments_docs así que el próposito del código PL/SQL como ejemplo es asociar un pago aplicado a una factura y actualizar su monto. Esta funcionalidad la creamos con el siguiente código PL/SQL, que consiste de dos funciones de apoyo getinvoice(), getpayment() y una función principal setpaymentdoc(p_inv_id numeric,p_pay_id numeric). Con esta función regresamos un cursor de referencia con los datos de una factura de la tabla invoices.

Similar a la función anterior pero ahora regresamos un pago de la tabla payments.

Ahora el código PL/SQL de la función principal la cuál establece la relación entre el pago y la factura y actualiza su balance.

Al ejecutar las funciones getinvoice() y getpayment(), ambas regresan un cursor de referencia.

Antes de ejecutar el procedimiento consultamos el pago y la factura de las cuales se obtendrán los datos para crear el registro en la tabla de relación payments_docs, como resultado de la ejecucción del procedimiento.

En la primera función getinvoice() observamos como el tipo de valor devuelto es un cursor de referencia, esto mediante la sentencia RETURNS refcursor AS

CREATE FUNCTION getinvoice(p_inv_id numeric) RETURNS refcursor AS 

Declaramos la variable con el tipo refcursor(cursor de referencia) para contener el cursor devuelto por la función

 v_cursor_inv refcursor;

El cursor devuelto lleva asociado las columnas de la consulta a la tabla invoices, la cuál se encuentra asociada al cursor.

OPEN v_cursor_inv FOR SELECT invoice_id,invoice_total FROM invoices 
 WHERE invoice_id = p_inv_id;

finalmente devolvemos la variable a la cual le asignamos el cursor.

return v_cursor_inv;

La función getpayment() es similar a la función getinvoice() lo que cambia es la consulta asociada a su cursor de referencia. En la función principal setpaymentdoc(p_inv_id numeric,p_pay_id numeric) declaramos las variables que almacenarán los cursores de referencia y las variables que almacenarán a las columnas que cada cursor tenga asociadas como parte de su consulta SQL.

DECLARE

resp int;
 v_payment refcursor;
 v_invoice refcursor;
 v_invoice_id int;
 v_invoice_total numeric(9,2);
 v_pay_id int;
 v_pay_amount numeric(9,2);
 v_balance numeric(11,2);

Ahora almacenamos los cursores devueltos por las funciones getinvoice() y getpayment() en las variables v_invoice y v_payment respectivamente.


v_invoice := getinvoice(p_inv_id);
v_payment := getpayment(p_pay_id);

Una vez guardado el cursor en la variable, podemos extraer los valores de las columnas y almacenarlos en su variable correspondiente, esto lo hacemos con el comando FETCH INTO.

FETCH [cursor name] INTO [variable1, variable2, ….n].

FETCH v_invoice INTO v_invoice_id,v_invoice_total;
 CLOSE v_invoice;

Es obligatorio que la variable que guarda el valor de la columna sea del mismo tipo de dato que la columna extraída del cursor. Por último insertamos la relación en la tabla utilizando los valores guardados en las variables, y regresando el número de secuencia.

v_balance := v_invoice_total - v_pay_amount;
 raise NOTICE 'balance : %',v_balance;
 INSERT INTO payments_docs(pay_id,inv_id,balance,date_created)
 VALUES(v_pay_id,v_invoice_id,v_balance,now());
 resp := currval('payments_docs_payments_docs_id_seq');

Al ejecutar el procedimiento con todos sus argumentos se mostrará el resultado como se ve en la siguiente imagen:

Ahora consultamos la tabla de relación, para ver el registro creado, resultado de la ejecución del procedimiento.

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.

lunes, 18 de febrero de 2013

Trabajando con Binary Large Object (BLOB) en PostgreSQL con GTK# y MonoDevelop II

En el número 20 de la revista Atix se publicó acerca de como leer y escribir datos binarios en PostgreSQL con C#, ahora en este documento complementamos la información anterior mostrando un programa en GTK# que hace uso de los mismos conceptos salvo que ahora los integra con las capacidades ofrecidas por Monodevelop para la creación de formularios GTK#.

Fig. 1 Monodevelop proyecto GTK#

Fig. 2 Diseño del formulario

Fig. 3 Muestra de la solución

Fig. 4 La aplicación ejecutándose

Fig. 5 Consulta de un registro

Fig. 6 Alta de un registro

domingo, 27 de enero de 2013

Entendiendo cursors (cursores) en PostgreSQL.

Para el manejo de grandes cantidades de datos tanto en PostgreSQL como en otras bases de datos relacionales existe el concepto de cursors (cursores) los cuales representan un resultset (conjunto de datos) que son asociados a una variable, una variable de tipo cursor. Esta variable representa un apuntador hacia una tabla virtual representada por una consulta y su respectivo comando SELECT asociado.

La diferencia entre un comando SELECT no asociado a un cursor y uno asociado, es que en el primero la consulta regresará todos los registros a la vez y si queremos limitar la cantidad de registros para procesar debemos correr la consulta nuevamente agregando WHERE, BETWEEN o cualquier otra instrucción para filtrar los resultados. En el caso del comando asociado a un cursor este nos permite desplazarnos y limitar la cantidad de registros para procesar dentro del resultset sin necesidad de un nuevo comando SELECT.

Como ejemplo tenemos una tabla Books dentro de una base de datos llamada MyBooks, aqui esta el script para su creación.

CREATE TABLE books
(
  bookid serial NOT NULL,
  title character varying(512),
  publishyear smallint,
  isbn character varying(13),
  created date DEFAULT ('now'::text)::date,
  CONSTRAINT books_pkey PRIMARY KEY (bookid)
)
Para mostrar la diferencia entre una consulta sin cursor y una consulta asociada a un cursor, ejecutamos la siguiente consulta.

SELECT  bookid, title, publishyear, isbn, created FROM Books.
Esta consulta nos devuelve el siguiente resultado.

Ahora mostramos los comandos que pueden utilizarse al asociar una consulta a un cursor. Para el trabajo con cursores es necesario que estos se encuentren dentro del ámbito de una transacción. Empezamos con una transacción.

BEGIN;
Declaramos el cursor como una variable de tipo cursor y le asociamos una consulta SQL.
DECLARE mycursor CURSOR FOR SELECT * FROM Books;
Ahora podemos navegar entre los registros del resultset, con el comando FETCH, de este comando la sintaxis es:
FETCH [FORWARD | BACKWARD |  ALL | NEXT]
A continuación unos ejemplos de su utilización.

Obtenemos un par de registros.
FETCH 2 FROM MyCursor;

Obtenemos los siguientes 4 registros y nos desplazamos:
FETCH 4 FROM MyCursor;

Obtenemos 3 registros hacia atrás a partir del último registro.
FETCH BACKWARD 3 FROM MyCursor;

Avanzamos de nuevo y obtenemos los próximos 6 registros:
FETCH FORWARD 6 FROM MyCursor;
Obtenemos el próximo registro

FETCH NEXT FROM MyCursor;
También podemos avanzar de forma negativa con FORWARD -[n] lo que es equivalente a BACKWARD [n].
FETCH FORWARD -10 FROM MyCursor;
Obtenemos todos los registros del cursor:
FETCH ALL FROM MyCursor;
Por último cerramos el cursor y confirmamos (o abortamos) la transacción.
CLOSE MyCursor;
COMMIT;

viernes, 11 de enero de 2013

Trabajando con Binary Large Object (BLOB) en PostgreSQL con MonoDevelop

En la actualidad existen aplicaciones que además de almacenar y procesar datos basados en caracteres, requieren también almacenar y procesar archivos de gran tamaño de tipo binario o texto, tales archivos como los multimedia (gráficas, imágenes, audio, vídeo) o los generados por programas de oficina (documentos, presentaciones, hojas de calculo), para estos casos en el estándar SQL están definidos dos tipos de datos para guardar grandes cantidades de información: el CLOB (CHARACTER LARGE OBJECT) que se utiliza para información de tipo texto y el BLOB (BINARY LARGE OBJECT) que se utiliza para información de tipo binario.

En el último número de la revista Atix dedicada a divulgar acerca del software libre, escribí un breve tutorial acerca de como escribir y leer este tipo de datos binarios en PostgreSQL mediante un programa C# construido con Monodevelop.

En la página web de la revista Atix, están los enlaces para descargar la revista en PDF. Abajo pongo el enlace del código fuente para el ejemplo del tutorial.

sábado, 29 de diciembre de 2012

Trabajando con tipos binarios BLOB (Binary Large Object ) en PostgreSQL.

En el estándar SQL están definidos dos tipos de datos para guardar grandes cantidades de información, el CLOB (CHARACTER LARGE OBJECT) que se utiliza para información de tipo texto y el BLOB (BINARY LARGE OBJECT).

El tipo de datos CLOB se representa en la base de datos como un registro con una gran cantidad de información de tipo carácter, estos caracteres pertenecen al conjunto de caracteres definidos por la base de datos (generalmente ASCII) siendo de un solo byte por lo que se utiliza para guardar archivos de texto como por ejemplo los archivos XML. Por su parte el tipo de datos BLOB se representa como un registro de una gran cantidad de información binaria sin estructura distribuida de forma aleatoria por lo que es ideal para archivos binarios como los mapas, las gráficas, y todos los archivos multimedia.

Trabajando tipos de datos BLOB con funciones predeterminadas de PostgreSQL

PostgreSQL proporciona el soporte para tipos de datos BLOB mediante las funciones predeterminadas: lo_import() y lo_export(), las cuales importan y exportan archivos desde el sistema de archivos hacia la base de datos y viceversa. Para ejemplificar el uso de estas funciones tenemos una tabla llamada Books con el siguiente script SQL:

Es esta tabla la columna picture tiene un tipo de dato oid el cual es un identificador que se utiliza como referencia hacia la tabla de sistema pg_largeobject la cual almacena el objeto binario en una o más filas, por lo que la columna picture almacena únicamente el apuntador hacia la información del objeto binario no el objeto binario en si. Como ejemplo agregamos un registro a la tabla, importando la imagen desde el sistema de archivos hacia la base de datos con la función: lo_import()

Ahora recuperamos el registro obteniendo la imagen desde la base de datos hacia el sistema de archivos con la función lo_export().

Las funciones lo_import() y lo_export() son ejecutadas directamente por el servidor de PostgreSQL por lo que la ruta completa del archivo debe tener los permisos correspondientes para ser accesibles desde el servidor, es necesario que el usuario de PostgreSQL tenga el rol de superuser de PostgreSQL (no confundir con el root de Linux).

martes, 2 de octubre de 2012

Utilizando secuencias (sequences) en PostgreSQL con C# .

Con frecuencia en un buen diseño de bases de datos es necesario que las tablas tengan al menos un identificador único para poderlas relacionar con otras, siguiendo las recomendaciones de las conocidas reglas de Codd para el modelo relacional de bases de datos.Para cumplir con este requerimiento podemos fijarnos en la entidad que representa cada tabla y seguir la especificación del identificador único que le correspondería según nos dicten las reglas del negocio o el tipo de sistema que vamos a construir.

Hay casos en donde la tabla no tiene una entidad definida sino sirve únicamente como tabla de soporte para una relación muchos a muchos, como un catálogo o un listado de parámetros para el sistema, en estos casos donde no se tiene una especificación definida para asignar un identificador único lo más recomendado es utilizar una serie o un contador como valor para ese identificador.

PostgreSQL proporciona unos objetos llamados secuencias (sequences) que sirven para crear contadores o series, las secuencias son objetos de bases de datos al mismo nivel que las tablas, vistas, triggers o funciones.

Aunque pueden crearse contadores y series de forma manual esto no será tan eficiente como los objetos sequence que nos proporciona PostgreSQL ya que mejoran el desempeño de la base de datos sobretodo en sistemas multiusuario. La forma automática de crear una secuencia es utilizar el tipo de dato serial en una columna, como se muestra a continuación con el siguiente script para una tabla llamada Publishers.

Al ejecutar este script se crean dos objetos: la secuencia (sequence) y la tabla (en ese orden), como lo muestra la pestaña messages de pgadmin al finalizar la ejecución del script.

Ahora creamos una función plpgsql con la que agregaremos los registros a la tabla, en esta función establecemos los valores mediante parámetros para cada una de las columnas, excepto claro el identificador (columna publisherid), ya que de ese valor se encargará la secuencia.

Ahora con el siguiente programa en C# probaremos la secuencia agregando algunos registros e imprimiendo sus identificadores en la consola.

Compilamos el programa con el siguiente comando.

$ gmcs -r:/home/martin/lib/Npgsql2.0.11.94/Mono2.0/bin/Npgsql.dll,System.Data Main.cs

Al ejecutar el programa, se mostrarán los registros agregados y los valores que les asigno la secuencia como identificador.

También podemos crear una secuencia (sequence) de forma manual siguiendo la sintaxis:

create sequence [name] start with [number] increment by [number]

De manera predeterminada las secuencias comienzan en el número 1 al menos no se indique con el comando start with. Por ejemplo creamos una secuencia que se incremente de 6 en 6

Test=# CREATE SEQUENCE my_first_sequence_seq INCREMENT BY 6;

Para acceder a los valores de la secuencia lo hacemos con un SELECT y la función nextval() como se muestra a continuación:

Observamos que la secuencia comienza en 1 e incrementa de 6 en 6.
Para acceder al valor actual de la secuencia lo hacemos con la función currval() como se muestra a continuación:

Para establecer un nuevo valor en la secuencia utilizamos la función setval()

Por último podemos eliminar la secuencia creada con el comando:

DROP SEQUENCE [name]

Como se muestra a continuación, en la siguiente imagen: