Reproducir la funcionalidad auto_increment en PostgreSQL (II)

Un equivalente al procedimiento «manual» que se exponía en este otro post está soportado por PostgreSQL a través de la definición de un campo SERIAL de la siguiente manera:

CREATE TABLE people (
    people_id SERIAL,
    name VARCHAR(16),
    surname VARCHAR(16)
);

Al campo (o a «los campos», pues puede haber varios) definido como SERIAL se le pueden añadir restricciones adicionales como PRIMARY KEY. Téngase en cuenta que la definición como SERIAL sólo tiene efecto si el valor asignado en un INSERT es DEFAULT.

Aparte de SERIAL (4 bytes, con valores entre 1 y 2147483647), existen las variantes SMALLSERIAL (2 bytes, con valores de 1 a 32767) y BIGSERIAL (8 bytes: ¡una enormidad!), recogidas en la documentación oficial.

Publicado en Flashes | Etiquetado , , | 1 comentario

ORM: Minimizar la escritura de código

Uno de los principales inconvenientes (?) del mapper (es decir, el componente que traduce las clases a tablas y los miembros a columnas: la M en ORM) de SQLObject es la limitación que impone a ciertos nombres y estructuras de la base de datos subyacente, en particular:

  • La obligatoriedad de disponer de un campo PRIMARY KEY de tipo INT y auto-generado (AUTO INCREMENT para MySQL, SERIAL para PostgreSQL), y
  • la nomenclatura de dicho campo, que en general es preferible que se llame id, o como mucho tabla_id, si no queremos vernos obligados a especificar a SQLObject algún parámetro no demasiado intuitivo (ver más adelante la solución).

La primera consecuencia es que las tablas que manejemos no pueden tener clave primaria compuesta por varias columnas (por ejemplo, en el caso de SQLAlchemy encontramos mucha mayor permisividad a costa de una configuración bastante más prolija, por lo que es un ORM preferible si tenemos que lidiar con bases de datos heredadas -y mal diseñadas). El segundo punto, al menos en el caso de utilizar como base de datos PostgreSQL, es totalmente irrelevante, puesto que podemos crear una vista de la tabla con los campos renombrados a gusto del mapper, redirigiendo mediante RULEs todas las instrucciones DML (INSERT, UPDATE y DELETE) a la tabla original.

En caso de que nos limitemos a la funcionalidad que SQLObject entiende como «natural», la definición de las clases puede reducirse (como se explica en la documentación oficial, en la sección Automatic Class Generation) a la siguiente, válida para recorrer la tabla joomla_users donde el conocido CMS Joomla! guarda sus usuarios:

class JoomlaUsers(SQLObject):
    class sqlmeta:
        fromDatabase = True

users = JoomlaUsers.select()
for u in list(users):
    print u.username, u.email, u.registerDate

La clave está en el uso de la clase interna sqlmeta que modifica las propiedades del mapper, en este caso la auto-generación de la clase. Por ejemplo, también puede utilizarse, estableciendo la propiedad style = DefaultStyle(longID=True), para que el nombre de la columna clave sea tabla_id. Las posibilidades de su uso se encuentran relacionadas en la documentación oficial.

Publicado en Tutoriales | Etiquetado , , | Deja un comentario

ORM: Convertir las tablas en clases (II)

Hasta ahora, SQLObject no supone un gran avance sobre un ORM confeccionado «a mano». Sin embargo, a medida que las cosas empiezan a complicarse, también se incrementa la cantidad de trabajo que nos ahorra. El siguiente paso es poner la «R» de «RDBMS»: las relaciones entre clases tablas. Por ejemplo, una relación típica «uno a varios» entre una cuenta bancaria y sus operaciones, implementada mediante una FOREIGN KEY

CREATE TABLE cuenta (
    id INT PRIMARY KEY,
    ccc VARCHAR(20),
    saldo DECIMAL(8,2));

CREATE TABLE movimiento (
    id INT PRIMARY KEY,
    fecha DATE,
    importe DECIMAL(8,2),
    concepto VARCHAR(32),
    cuenta_id INT CONSTRAINT fk_movimiento REFERENCES cuenta(id)
)

se traduce de modo directo en la definición de clases de SQLObject, traduciendo los tipos de datos SQL por los tipos correspondientes de SQLObject:

class Cuenta(SQLObject):
    ccc = StringCol()
    saldo = FloatCol()

class Movimiento(SQLObject):
    fecha = DateCol()
    importe = FloatCol()
    concepto = StringCol()
    cuenta = ForeignKey('Cuenta')

Nótese como, al haber elegido campos identificadores llamados id, no es necesario especificar nada referente a ellos en la definición de las clases. Ahora, para cada objeto Movimiento tendremos un miembro cuenta que a su vez es un objeto de tipo Cuenta, como en el siguiente bucle que mostrará todos los movimientos, con el saldo (actual) de la cuenta sobre la que se ha operado:

movimientos = Movimiento.select()
for m in list(movimientos):
    print m.fecha, m.cuenta.saldo, m.importe
Publicado en Tutoriales | Etiquetado , | Deja un comentario

ORM: Convertir las tablas en clases (I)

Al cabo de unos días desarrollando software que acceda a una base de datos, es casi inevitable acabar diseñando clases de este estilo, llamado patrón Active Record (este ejemplo en concreto utiliza C++ por razones de sencillez y didácticas):

class Evento {
private:
  string Lugar;
  date Fecha;
public:
  Evento(string l, date f);
  string getLugar();
  void setLugar(string l);
  date getFecha();
  void setFecha(date f);
  int guarda();
  int borra();
};

y una implementación de esa clase rellena con las operaciones de conexión y desconexión a la base de datos en el constructor y con las instrucciones SELECT para las funciones get(), INSERT y UPDATE para guarda() y DELETE para borra(). Probablemente la conexión a la base de datos se implemente utilizando un patrón singleton y, si somos muy hábiles con el lenguaje, si éste se acomoda bien y si este estilo de correspondencia directa nos es suficiente, utilicemos una clase base que investigue por sí misma las columnas de la tabla para construir sus miembros. Puliendo más los detalles, las funciones set() comprobarán la validez de los datos suministrados para que correspondan con los tipos de datos definidos en la base de datos.

A medida que el diseño de la base de datos se complica, la implementación de estas clases se vuelve poco manejable: gestionar restricciones de unicidad y de NOT NULL, restricciones FOREIGN KEY, relaciones uno-a-uno, uno-a-varios, varios-a-varios, transacciones con su COMMIT y su ROLLBACK añade mucha complejidad, especialmente si queremos mantener una cierta independencia de la base de datos subyacente.

Un ORM (Object-Relational Manager o Mapping) es un componente software (paquete, librería…) que permite unificar y simplificar ese diseño, haciéndose cargo de todas las operaciones relativas a la base de datos. Entre los más conocidos se encuentran Hibernate (JAVA), DBIx::Class y Rose::DB::Object (Perl). Para Python existen dos elecciones obvias: SQLAlchemy y SQLObject (breve comparativa entre ambas soluciones). Un ejemplo básico pero completamente funcional nos ilustra sobre lo sencillo que es trabajar con estas herramientas:

#!/usr/bin/python
#-*- coding: utf-8 -*-

from sqlobject import *

sqlhub.processConnection = connectionForURI('sqlite:/:memory:')

class Persona(SQLObject):
    nombre = StringCol()
    primerApellido = StringCol()
    segundoApellido = StringCol()
Persona.createTable()
p = Persona(nombre="Nicolás",
            primerApellido="Salmerón",
            segundoApellido="Alonso")
print p.nombre

En concreto, este ejemplo creará la tabla requerida persona, con cuatro campos: los tres especificados en la clase (con los nombres ligeramente modificados: nombre, primer_apellido y segundo_apellido) y uno más llamado id generado mediante AUTO INCREMENT para guardar el identificador de la fila.

Al contrario que otros ORM, SQLObject lanzará la instrucción INSERT en el momento de la creación del objeto, por lo que no llamamos a ningún método save().

Publicado en Tutoriales | Etiquetado , , | Deja un comentario

Alfresco 4 : añadir tipos MIME

Crear un archivo XML acabado en «-map.xml» y guardarlo en /opt/alfresco/tomcat/shared/classes/alfresco/extension/mimetype , como

# cat custom-mimetypes-map.xml 
<alfresco-config area="mimetype-map">

   <config evaluator="string-compare" condition="Mimetype Map">
      <mimetypes>
         <mimetype mimetype="text/plain" display="D19 file">
            <extension>d19</extension>
         </mimetype>
         <mimetype mimetype="text/plain" display="T61 file">
            <extension>t61</extension>
         </mimetype>
         <mimetype mimetype="text/plain" display="Q19 file">
            <extension>q19</extension>
         </mimetype>
      </mimetypes>
   </config>

</alfresco-config>
Publicado en Flashes | Deja un comentario

Alfresco 4 : configuración e-mail saliente

La configuración se guarda en
$ALFRESCO_WEB/WEB-INF/classes/alfresco/subsystems/email/OutboundSMTP/outboundSMTP.properties
(sobreescribir en $TOMCAT_HOME/shared/classes/alfresco-global.properties como se detalla en http://wiki.alfresco.com/wiki/Outbound_E-mail_Configuration )
y
$ALFRESCO_WEB/WEB-INF/classes/alfresco/web-client-config.xml
(sobreescribir en $TOMCAT_HOME/shared/classes/alfresco/extension/web-client-config-custom.xml como se detalla en http://wiki.alfresco.com/wiki/Web_Client_Configuration_Guide).

Publicado en Flashes | Deja un comentario

Control de acceso a base de datos a través de LDAP

La mejor solución para mantener una gestión centralizada de contraseñas de usuario es utilizar un servidor LDAP (como OpenLDAP) e indicarle al servidor (o a los servidores) cómo tiene que realizar la identificación de usuarios. (Aún así, todavía habrá que crear las cuentas de usuario y conceder los privilegios en la base de datos.) Lo único que tenemos que hacer es retocar el fichero pg_hba.conf añadiéndole una línea parecida a ésta:

host all all 127.0.0.1/32 ldap\
   ldap://servidor ldap/base dn;prefijo;sufijo"

El base dn es la base sobre la que se hace la búsqueda. El prefijo es lo que se añadirá a la izquierda del nombre de usuario para crear el nombre cualificado con el que se hará el login. Normalmente será cn=. Y el sufijo es lo que se añade por la derecha, incluyendo una coma de separación si fuera necesario. Así, si nuestro sevidor es ldap.ejemplo.com y los usuarios autorizados a desarrollar en la base de datos testing se ubican en ou=Developers,dc=ejemplo,dc=com, la línea necesaria será

host testing all 192.168.1.0/24 ldap \
    ldap://ldap.ejemplo.com/ou=Developers,dc=ejemplo,dc=com;cn=;\
    ,ou=Developers,dc=ejemplo,dc=com"

Para cada usuario autorizado habrá que lanzar el comando CREATE USER y los GRANT que correspondan.

Publicado en Uncategorized | Etiquetado , , , , | 1 comentario

Reproducir la funcionalidad auto_increment en PostgreSQL

Actualización: Reproducir la funcionalidad auto_increment en PostgreSQL (II).

Una característica muy utilizada de MySQL es la existencia de campos AUTO_INCREMENT. Un campo AUTO_INCREMENT es una columna numérica de una tabla que genera automáticamente un valor secuencial ascendente para los registros que se insertan sin especificar otro valor. En PostgreSQL podemos imitar ese tipo de campos mediante secuencias:

CREATE SEQUENCE people_id_seq;

CREATE TABLE people (
people_id BIGINT PRIMARY KEY DEFAULT NEXTVAL('people_id_seq'),
name VARCHAR(16),
surname VARCHAR(16)
);

INSERT INTO people(name, surname) VALUES ('Bruce', 'Momjian');
INSERT INTO people(name, surname) VALUES ('Larry', 'Wall');

SELECT * FROM people;

 people_id | name  | surname
-----------+-------+---------
         1 | Bruce | Momjian
         2 | Larry | Wall
(2 rows)

No funciona del todo bien, porque la secuencia puede pisar valores de la clave primaria, como por ejemplo así:

INSERT INTO people(people_id, name, surname)
    VALUES (4, 'Guido', 'van Rossum');
INSERT INTO people(name, surname)
    VALUES ('Zeev', 'Suraski'); -- people_id=3
INSERT INTO people(name, surname)
    VALUES ('Andy', 'Gutmans'); -- people_id=4

Una posible solución es:

CREATE RULE insert_people_rule AS
    ON INSERT TO people DO ALSO
        SELECT SETVAL('people_id_seq', (SELECT MAX(people_id) FROM people));

Que funciona bien porque el SELECT SETVAL(… se realiza antes que el INSERT

INSERT INTO people(people_id, name, surname)
    VALUES (4, 'Guido', 'van Rossum');
INSERT INTO people(name, surname)
    VALUES ('Zeev', 'Suraski');
INSERT INTO people(name, surname)
    VALUES ('Andy', 'Gutmans');

SELECT * FROM people;
 people_id | name  |  surname
-----------+-------+------------
         1 | Bruce | Momjian
         2 | Larry | Wall
         4 | Guido | van Rossum
         5 | Zeev  | Suraski
         6 | Andy  | Gutmans
(5 rows)

Aún hay alguna diferencia con respecto al AUTO_INCREMENT de MySQL:

Al hacer un TRUNCATE TABLE no se reinicializa la secuencia. Podemos hacerlo manualmente con

SELECT SETVAL('people_id_seq', 1, FALSE);

o

ALTER SEQUENCE people_id_seq RESTART WITH 1;

Podemos aplicar este proceso a tantas columnas de la tabla como queramos, mientras que MySQL obliga a que las columnas AUTO_INCREMENT sean parte de una PRIMARY KEY, y limita a una columna AUTO_INCREMENT por tabla. (¿Es realmente esto una ventaja?).

Podemos utilizar una secuencia con valores negativos y con el incremento que queramos. En MySQL estamos limitados a poder almacenar (explícitamente) un valor menor que 1 y en cada momento el incremento está determinado por la variable de sistema auto_increment_increment (común para todos los campos AUTO_INCREMENT).

Publicado en Uncategorized | Etiquetado , , , , | 2 comentarios

Instalar PostgreSQL en 6 pasos (UNIX, GNU/Linux, …) desde cero (a partir del código fuente)

Una de las características más interesantes del software libre es la posibilidad de compilar uno mismo su propio software. Así que, aunque normalmente podemos instalar PostgreSQL a partir de binarios precompilados o incluso paquetes específicos para nuestro sistema, puede que queramos la ultimísima versión o características especiales que requieran compilarlo desde el código fuente.

Necesitaremos un entorno de desarrollo con versiones suficientemente recientes de gcc, make y todas esas cosas que hacen falta para compilar software estándar. La mayoría de distribuciones tienen algún tipo de meta-paquete que instala todo esto de un golpe.

  1. Descargar el código fuente de alguno de los mirrors. El fichero que buscamos está en latest, y se llamará algo como postgresql-X.Y.Z.tar.gz. En este momento la última versión es 8.3.5, así que ésos serán los números que utilizaré.
  2. Descomprimir el código fuente. Un simple tar -xzvf postgresql-8.3.5.tar.gz debería hacerlo sin dificultades.
  3. Entrar en el directorio creado al descomprimir, y compilar con ./configure && make. Si no le decimos nada intentará instalarlo todo en /usr/local/pgsql, así que el usuario que lo haga debe tener permisos para crear ese directorio. Otra alternativa es instalarlo dentro del directorio HOME de uno mismo: ./configure –prefix=$HOME/pgsql.
  4. Instalar el software compilado: make install.
  5. Inicializar un directorio para almacenar la base de datos: $HOME/pgsql/bin/initdb –encoding=utf-8 $HOME/pgsql-8.5.3/data.
  6. Arrancar la base de datos, indicándole el directorio de datos y un fichero para almacenar los logs: $HOME/pgsql/bin/pg_ctl -D $HOME/pgsql-8.5.3/data -l $HOME/pgsql-8.5.3/postgresql.log start… y probarla: $HOME/pgsql-8.5.3/bin/psql template1.

Y ya está. Si todo ha ido bien, podemos ejecutar consultas que se refieran las tablas que PostgreSQL trae incorporadas, como SELECT * FROM pg_user;.

Publicado en Uncategorized | Etiquetado , , | Deja un comentario