"La Gaceta de Linux...haciendo Linux un poco más divertido!"


Combinando Perl y PostgreSQL

Por Mark Nielsen

Traducción al español por Jose Manuel González
el día 30 de Noviembre de 2003, para La Gaceta de Linux


  1. Introducción
  2. Descargando e instalando Perl.
  3. Descargando e instalando PostgreSQL con Perl.
  4. Ejemplos de órdenes perl/sql.
  5. Configurando las tablas, procedimientos pl/perl para Insertar, Actualizar y procedimientos pl/pgsql para Eliminar
  6. Procedimiento pl/pgsql Insertar
  7. Procedimiento pl/pgsql Actualizar
  8. Procedimiento pl/pgsql Eliminar
  9. Consideraciones a tener en cuenta.
  10. Conclusión
  11. Referencias

Introducción

PostgreSQL ha recorrido un largo camino con su versión 7.1. He estado esperando por un mejor manejo de objetos grandes. En las versiones iniciales el tamaño de los campos de las tablas estaba limitado a 32 kilobytes. De otro modo tenías que emplear formas engorrosas para manipular grandes objetos.

Finalmente decidí conseguir Perl instalado en PostgreSQL porque PostgreSQL tiene todas las características que me gustan:

  1. Un verdadero lenguaje para procedimientos almacenados (PL/pgSQL).
  2. Cómodo manejo de grandes objetos.
  3. Órdenes Perl incrustadas.
  4. Similar a Oracle en diversos aspectos, haciendo así razonable la transición desde Oracle hasta PostgreSQL o viceversa.
  5. Tienen muchas características avanzadas que me parecen deseables para un servidor de base de datos.
  6. Tiene un libro web gratis. Me encanta la documentación libre y gratuita.

Todo el proceso fue un dolor porque había que hacer ajustes aquí y allí. Estos son los pasos básicos:

  1. Instalar Perl 5.6.1. Usar todas las opciones por defecto excepto por dos cambios.
  2. Instalar PostgreSQL después de haber instalado Perl.
  3. Instalar Perl dentro de PostgreSQL y realizar un ajuste.

Descargando e instalando Perl.

Asegúrate de instalar Perl antes que PostgreSQL. Desconozco si las últimas versiones RedHat 7.1 o Debian tienen libperl como un módulo compartido.
cd /usr/local/src
lynx --source http://www.tcu-inc.com/perl5.6.1.tgz > perl-5.6.1.tgz
tar -zxvf perl-5.6.1.tgz
cd perl-5.6.1
rm -f config.sh Policy.sh
sh Configure
Cambia el prefijo por defecto a "/usr" en vez de "/usr/local". Además, cuando pregunte "Build a shared libperl.so (y/n) [n] ", responde y [sí]. Presiona enter para cualquier otra pregunta para aceptar las opciones por defecto.
make
make install

Descarga e Instalación de PostgreSQL con Perl.

Cuando descargué PostgreSQL, también intenté instalar interfaces para tcl, c, python, y obdc. No lo hice para JAVA, pero también es una opción. Además si vas a emplear Perl con PostgreSQL te recomiendo descargar e instalar DBI and DBD:Pg desde cpan.perl.com.

Tcl y Perl son opciones como lenguajes procesales. En realidad puedes ejecutar Perl y Tcl dentro de comandos sql. También obtienes el lenguaje procedural estándar PL/pgSQL (el cual es similar a pl/sql). Estos son los pasos que empleé para instalar PostgreSQL con Perl. Este es un archivo de texto con la misma información.


### Antes de nada, tienes que compilar Perl como un módulo dinámico. 
### Si no has hecho esto podrás instalar postgresql,
### pero no tendrás el interface the plperl. 

cd /usr/local/src
lynx --source ftp://postgresql.readysetnet.com/pub/postgresql/v7.1.1/postgresql-7.1.1.tar.gz > postgresql-7.1.1.tar.gz
tar -zxvf postgresql-7.1.1.tar.gz
cd postgresql-7.1.1

### Necesitamos establecer algunas variables de entorno -- las cuales deben ser puestas 
#### dentro de ~/.profile para el usuario para el futuro.

PATH=/usr/local/pg711/bin:$PATH
export PATH
export LD_LIBRARY_PATH=/usr/local/pg711/lib
export PGDATA=/usr/local/pg711/data
export PGLIB=/usr/local/pg711/lib
export POSTGRES_HOME=/usr/local/pg711

  ### Este script está configurado para borrar cualquier instalación previa. 
  ### Hice esto de modo que pudiera depurarlo si no funcionaba la primera vez.

  #### Ignora cualquier mensaje de error diciendo que el servidor de bases de datos no está corriendo.
  ### Probablemente no tengas ninguno.
su -c '/usr/local/pg711/bin/initdb -D /usr/local/pg711/data -l logfile stop' postgres
  ### Ignora cualquier mensaje de error diciendo que este usuario ya existe.
adduser postgres
rm -rvf /usr/local/pg711

  ### Ahora creamos el directorio destino y le asignamos como propietario el usuario postgres. 
mkdir /usr/local/pg711 
chown postgres /usr/local/pg711

  ### No hagas caso de ningún mensaje de error al hacer make clean aquí.
make clean
  ### Compila e instala postgresql.
./configure --prefix=/usr/local/pg711 --with-perl --with-tcl --with-CXX --with-python --enable-odbc 
make
make install

  ### Ahora necesitamos instalar la interfaz perl para postgresql.
gmake -C src/interfaces/perl5 install
cd /usr/local/src/postgresql-7.1.1/src/interfaces/perl5
perl Makefile.PL
make 
  ### Descomenta la siguiente línea si quieres testearlo. 
##  su -c 'make test' postgres
make install

  ### Cambia a postgres el propietario para todos los ficheros.
chown -R postgres /usr/local/pg711

  ### Inicializa la base de datos. 
su -c '/usr/local/pg711/bin/initdb -D /usr/local/pg711/data' postgres

  ### Arranca el servidor de base de datos. 
su -c '/usr/local/pg711/bin/pg_ctl -D /usr/local/pg711/data -l logfile start' postgres

  ### Las interfaces para perl, tcl, y pl/pgsql deberían haber sido creados. 
  ### Ahora se añaden. 
  
su -c 'createlang plpgsql template1' postgres
su -c 'createlang pltcl template1' postgres

### Ahora se asume que tienes instalado perl 5.6.1 correctamente.
rm -f /usr/local/pg711/lib/libperl.so
ln -s /usr/lib/perl5/5.6.1/i686-linux/CORE/libperl.so \
  /usr/local/pg711/lib/libperl.so
su -c 'createlang plperl template1' postgres

  ### Si se ha resuelto todo correctamente, cualquier base de datos nueva se copiará desde 
  ### template1 y tendrás perl, tcl, y pl/pgsql. 

  ### Ahora material adicional.
su -c 'createdb postgres' postgres

En el directorio home del usuario postgres, crea un fichero llamado ".profile" con el siguiente contenido.
#!/usr/bin

PATH=/usr/local/pg711/bin:$PATH
export PATH
export LD_LIBRARY_PATH=/usr/local/pg711/lib
export PGDATA=/usr/local/pg711/data
export PGLIB=/usr/local/pg711/lib
export POSTGRES_HOME=/usr/local/pg711
Entonces ejecuta esta orden,
chmod 755 .profile

Ejemplo de comandos perl/sql.

Ejecuta los comandos disponibles en http://www.ca.postgresql.org/users-lounge/docs/7.1/programmer/plperl-use.html

Ya que te hice crear la base de datos "postgres", todo lo que tienes que hacer es introducir estas dos órdenes como usuario "root" para entrar en el interfaz psql.

su -l postgres
psql
Esto asume además que has configurado correctamente .profile para el usuario postgres. Si no lo hiciste entonces sigue estas órdenes:
su -l postgres
PATH=/usr/local/pg711/bin:$PATH
export PATH
export LD_LIBRARY_PATH=/usr/local/pg711/lib
export PGDATA=/usr/local/pg711/data
export PGLIB=/usr/local/pg711/lib
export POSTGRES_HOME=/usr/local/pg711
psql

La siguiente función te permite buscar datos y devuelve una copia del nombre si el nombre contiene el texto que buscas con una opción para distinguir o no mayúsculas de minúsculas.

drop function search_name(employee,text,integer);
CREATE FUNCTION search_name(employee,text,integer) RETURNS text AS '
    my $emp = shift;
    my $Text = shift;
    my $Case = shift;

    if (($Case > 0) && ($emp->{''name''} =~ /\\Q$Text\\E/i)) 
      { return $emp->{''name''}; }
    elsif ($Case > 0) {return "";}
    elsif ($emp->{''name''} =~ /\\Q$Text\\E/) 
       {    return $emp->{''name''}; }
    else { return "";}
' LANGUAGE 'plperl';

insert into EMPLOYEE values ('John Doe',10000,1);
insert into EMPLOYEE values ('Jane Doe',10000,1);
insert into EMPLOYEE values ('Giny Majiny',10000,1);

select name,search_name(employee,'j',0) from employee;
select name,search_name(employee,'j',1) from employee;

select name from employee where search_name(employee,'j',1) = name;
select name from employee where search_name(employee,'j',0) = name;
Obviamente, la función es bastante ridícula. Esta solamente debería devolver 0 para falso o 1 para verdadero. Por motivos estéticos le hago que devuelva una copia del nombre.

Configurando las tablas, procedimientos pl/perl para la Inserción, Actualización y procedimientos pl/pgsql para Eliminar

Puedes obtener una copia de las órdenes SQL para esta sección aquí: SQL_setup.txt. Hay varias cosas que quiero lograr:
  1. Crear procedimientos para insertar, actualizar y eliminar datos que guarden todos los cambios en una tabla historial o en una tabla de respaldo. Esto guardará todo cuanto ocurra. Se requiere una comprobación razonable de errores. Podríamos hacer más chequeos de errores pero los procedimientos que he creado podrían hacerse demasiado extensos.
  2. Usar procedimientos Perl para limpiar las entradas que son puestas en las tablas. Lo reconozco, podría emplear comandos sql pero las órdenes perl son más legibles para mi.
No me creo que sea posible conseguir que los procedimientos perl ejecuten las órdenes insertar, actualizar, borrar o seleccionar. La única cosa que he conseguido hacer con Perl es aceptar valores y devolver uno solo. De todas maneras no deberías necesitar nunca Perl para ejecutar sql. No estás empleando Perl para ejecutar órdenes sino para modificar datos, a modo de filtro, o comprobación de errores. Se Usa pl/pgsql para manejar todas las instrucciones sql. Se emplea Perl para manipular datos y no hacerlo directamente en la base de datos.

Más abajo tengo tres tablas: jobs, jobs_backup, y contact. Únicamente creo los procedimientos para la tabla 'jobs'. Los dos procedimientos perl únicamente pretenden verificar que tenemos datos válidos en la entrada, filtrar los caracteres no imprimibles y eliminar los espacios en blando. Usamos pl/pgsql para realizar las verdaderas órdenes de inserción, actualización y eliminación.

Utilizando este método básico de manejo de datos puedes copiarlo para cualquier otra tabla que tengas.

Algunas cosas con las que tengo que tener cuidado es con el hecho de que quiero nombres únicos para 'jobs'. No quiero que dos 'jobs' tengan el mismo nombre de un único reclutador. Esto se hace un poco difícil, pero funciona bien.

Además, podría utilizar una clave externa bloqueante que impidiera tener contact_id en 'jobs' sin existir también en 'contact'. El único problema con esto es que podríamos eliminar accidentalmente contact_ids de contact y entonces todo se estropearía del mismo modo. La mejor solución es añadir una columna "de activación" a las tablas "jobs" y "contact" con las cuales activas o desactivas los objetos. De este modo nunca se borra un único id.

 --- Crear la tabla jobs. 
 --- un buena sugerencia sería tener una clave externa restrictiva 
 --- con la tabla contact.
create sequence Job_Sequence;
drop table jobs;
create table jobs (
job_id int4 unique DEFAULT nextval('Job_Sequence'),
contact_id int4,
job_no int4,
job_name  text,
job_location text 
);
CREATE UNIQUE INDEX job_index ON jobs (job_name, contact_id);

-- Esta es realmente una tabla auxiliar. 
-- Cada vez que ocurre un cambio lo inserta en esta tabla. 
-- Esto no se cumple para las eliminaciones sino para las inserciones y las actualizaciones. 
-- Esto es también una tabla historial no sólo auxiliar.
-- Siempre guardamos la salida final. 
create sequence Backup_Job_Sequence;
drop table jobs_backup;
create table jobs_backup (
backup_id int4 unique DEFAULT nextval('Backup_Job_Sequence'),
action text CHECK (action in ('insert','update','delete','')),
error_code int4,
job_id int4,
contact_id int4,
job_no int4,
job_name  text,
job_location text
);

create sequence Contact_Sequence;
drop table contact;
create table contact (
contact_id int4  UNIQUE DEFAULT nextval('Contact_Sequence'),
name text unique,
phone text,
website text
);

 --- Inserta dos valores en contacts.
 --- No estoy creando procedimientos para esta tabla, únicamente para la tabla jobs. 
insert into contact (name,phone,website) 
  values ('Mark Nielsen','(408) 891-6485','http://www.gnujobs.com');
insert into contact (name,phone,website)
  values ('Joe Shmoe','(1234) 111-1111','http://www.gnujobs.net');
insert into contact (name,phone,website)
  values ('Lolix.org','(12345) 111-1111','http://www.lolix.org');


 --- Selecciona info de contact para ver si existe.
select * from contact;

 --- Usaremos la función perl create (la cual probablemente no es necesaria)
 --- que verificará que los datos de entrada no están vacíos. 

drop function job_values_verify (int4,text,text);
CREATE FUNCTION  job_values_verify (int4,text,text) RETURNS int4 AS '
    my $Contact_Id = shift;
    my $Job_Name = shift;
    my $Job_Description = shift;
    my $Error = 0;
    if ($Contact_Id < 1) {$Error = -100;}
    if (!($Job_Name =~ /[a-z0-9]/i)) {$Error = -101;}
    if (!($Job_Description =~ /[a-z0-9]/i)) {$Error = -102;}
  return $Error;
' LANGUAGE 'plperl';

drop function clean_text (text);
CREATE FUNCTION  clean_text (text) RETURNS text AS '
  my $Text = shift;
    # Se eliminan los espacios en blanco del principio. 
  $Text =~ s/^\\s+//;
    # Se eliminan los espacios en blanco del final. 
  $Text =~ s/\\s+$//;
    # Se elimina cualquier cosa que no sea texto.
  $Text =~ s/[^ a-z0-9\\/\\`\\~\\!\\@\\#\\$\\%\\^\\&\\*\\(\\)\\-\\_\\=\\+\\\\\\|\\[\\{\\]\\}\\;\\:\\''\\"\\,\\<\\.\\>\\?\\t\\n]//gi;
    # Reemplaza todos los espacios múltiples por un único espacio. 
  $Text =~ s/\\s+/ /g;
  return $Text;
' LANGUAGE 'plperl';
 -- Simplemente muestra lo que esta función limpia. 
select clean_text ('       ,./<>?aaa aa      !@#$%^&*()_+| ');
--


Procedimiento pl/pgsql Insertar

Puedes conseguir una copia de las órdenes SQL para esta sección aquí: SQL_insert.txt.

drop function insert_job (int4,text,text);
CREATE FUNCTION insert_job (int4,text,text) RETURNS int2 AS '
DECLARE
    c_id_ins int4; j_name_ins text;  l_ins text; 
    job_id1 int4; oid1 int4; test_id int4 := 0; j_no_ins int4 := 0;
    record1 RECORD; record2 RECORD; record3 RECORD; record4 RECORD;
BEGIN
   j_name_ins := $2; l_ins  := $3; c_id_ins := $1;

     -- Ejecutamos algunos procedimientos Perl ahora. Estos son ejemplos 
     -- de procedimientos Perl.
     -- Limpia el nombre de job.
   SELECT INTO record4 clean_text(j_name_ins) as text1;
   j_name_ins = record4.text1;
     -- Limpia la localización de job.
   SELECT INTO record4 clean_text(l_ins) as text1;
   l_ins = record4.text1;
     -- Verifica que los valores que introducimos son correctos.
   SELECT INTO record4 job_values_verify (c_id_ins, j_name_ins, l_ins) as no;
   IF record4.no < 0 THEN return (record3.no); END IF;

     -- Mira si tenemos nombres únicos, de otro modo devuelve 0.
   FOR record1 IN SELECT job_id FROM jobs  
      where contact_id = c_id_ins and job_name = j_name_ins
      LOOP
      test_id := record1.job_id;
   END LOOP;
     -- Si job_id es nulo, genial, de otro modo suspende y devuelve -1;
   IF test_id > 0 THEN return (-1); END IF;

   FOR record3 IN SELECT max(job_no) from jobs_backup where contact_id = c_id_ins
      LOOP
      IF record3.max IS NULL THEN j_no_ins := 0; END IF;
      IF record3.max > -1 THEN j_no_ins = record3.max + 1; END IF;
   END LOOP;

     -- Se insertan los valores. Deja a la secuencia determinar job_id.
   insert into jobs (contact_id, job_no, job_name, job_location)
        values (c_id_ins, j_no_ins, j_name_ins, l_ins);
     -- Obtener el oid único de la fila que se acaba de insertar.
   GET DIAGNOSTICS oid1 = RESULT_OID;
     -- Obtener el id de job. No usar SELECT INTO, ya que record2 necesita ser asignado.
   FOR record2 IN SELECT job_id FROM jobs where oid = oid1
      LOOP
      job_id1 := record2.job_id;
   END LOOP;
   
     -- Si job_id1 es NULO, la inserción falló o algo va mal.
   IF job_id1 is NULL THEN return (-2); END IF;
     -- Debería también ser mayor que 0, de otro modo algo va mal.
   IF job_id1 < 1 THEN return (-3); END IF;

     -- Todo ha terminado, devuelve job_id1 como job_id.
   insert into jobs_backup (contact_id, job_no, job_name, job_location, action, error_code)
        values (c_id_ins, j_no_ins, j_name_ins, l_ins, ''insert'', job_id1);
   return (job_id1);
END;
' LANGUAGE 'plpgsql';
select insert_job (1,'Job Title 1','Boston, MA');
select insert_job (1,'Job Title 2','San Jose, CA');
select insert_job (2,'Job Title 1','Columbus, Ohio');
select insert_job (2,'Job Title 2','Houston, TX');
select insert_job (3,'Job Title 1','Denver, CO');
select insert_job (3,'Job Title 2','New York, NT');
select * from jobs;

Procedimiento pl/pgsql Actualizar

Puedes obtener una copia de las instrucciones SQL empleadas en esta sección aquí: SQL_update.txt. El procedimiento para actualizar tiene que comprobar si existe job que tenga el mismo nombre que el actual que queramos cambiar. Si existe, no queremos hacer ningún cambio (a menos que job_id sea el mismo). ¿Recordabas que había una restricción para para el nombre del reclutador?

drop function update_job (int4,text,text,int4);
CREATE FUNCTION update_job (int4,text,text,int4) RETURNS int2 AS '
DECLARE
    c_id_ins int4; j_name_ins text;  l_ins text; 
    job_id1 ALIAS FOR $4; oid1 int4; test_id int4 := 0;
    record1 RECORD; record2 RECORD; record3 RECORD; record4 RECORD; record5 RECORD;  
    return_int4 int4 := 0; job_no1 int4 := 0;
BEGIN
   j_name_ins := $2; l_ins  := $3; c_id_ins := $1;

     -- Algunos procedimientos Perl. 
     -- Limpiar el nombre de job.
   SELECT INTO record4 clean_text(j_name_ins) as text1;
   j_name_ins = record4.text1;
     -- Limpiar la localización de job. 
   SELECT INTO record5 clean_text(l_ins) as text1;
   l_ins = record5.text1;
     -- Verificar que los valores que insertamos son correctos. 
   SELECT INTO record3 job_values_verify (c_id_ins, j_name_ins, l_ins) as no;
   IF record3.no < 0 THEN return (record3.no); END IF;

     -- Mira si existe un nombre duplicado de job para ese contacto.
   FOR record1 IN SELECT job_id FROM jobs  
      where contact_id = c_id_ins and job_name = j_name_ins
        and job_id != job_id1
      LOOP
      test_id := record1.job_id;
   END LOOP;
     -- Si job_id es nulo, genial, de otro modo suspender y devolver -1;
   IF test_id > 0 THEN return (-1); END IF;

     -- Mirar si job existe, de otro modo devolver -2.
   FOR record1 IN SELECT * FROM jobs where job_id = job_id1  
      LOOP
      update jobs set contact_id = c_id_ins,  
        job_name = j_name_ins, job_location = l_ins
	where job_id = job_id1;
      GET DIAGNOSTICS return_int4 = ROW_COUNT;
      test_id := 1;
      job_no1 := record1.job_no;
   END LOOP;

     -- Si no existe, ¿qué estamos actualizando? devolver error. 
   IF test_id = 0 THEN return (-2); END IF;

     -- Todo ha terminado, devolver return_int4.
   insert into jobs_backup (contact_id, job_no, job_name, job_location, action, error_code, job_id)
        values (c_id_ins, job_no1, j_name_ins, l_ins, ''update'', return_int4, job_id1);
   return (return_int4);
END;
' LANGUAGE 'plpgsql';
select update_job (3,'Changing title and owner.','Boston, MA',1);
select * from jobs;
  -- Deberías obtener un error con eso porque estás duplicando el nombre 
  -- y el id de contact. 
select update_job (3,'Changing title and owner.','Boston, MA',1);

Procedimiento pl/pgsql Eliminar

Puedes conseguir una copia de los comandos commands de esta sección aquí: SQL_delete.txt.

drop function delete_job (int4);
CREATE FUNCTION delete_job (int4) RETURNS int2 AS '
DECLARE
    job_id1 ALIAS FOR $1;
    job_exists int4 := 0;
    job_backup_exists int4 := 0;
    record1 RECORD; 
    return_int4 int4 :=0;
BEGIN
     -- Si job_id1 no es mayor que 0, devuelve error.
   IF job_id1 < 1 THEN return -1; END IF;

     -- Si we find the job, delete it, record we found it, and back it up. 
     -- No me gusta usar LOOP para una fila, pero lo utilizo por una razón.
   FOR record1 IN SELECT * FROM jobs where job_id = job_id1
      LOOP
      delete from jobs where job_id = job_id1;  
      GET DIAGNOSTICS return_int4 = ROW_COUNT;       
      job_exists := 1;
      insert into jobs_backup (contact_id, job_no, job_name, job_location, action, error_code, job_id)
        values (record1.contact_id, record1.job_no, record1.job_name, 
	  record1.job_location, ''delete'', return_int4, record1.job_id);
   END LOOP;

     -- Si job_exists == 0, devolver error.
     -- Significa que nunca existió. 
   IF job_exists = 0 THEN return (-1); END IF;

     -- Llegamos tan lejos, significa que debe ser verdadero, devolver ROW_COUNT.   
   return (return_int4);
END;
' LANGUAGE 'plpgsql';
select delete_job (1);
select * from jobs;
  --- Ya lo borramos, deberíamos obtener un error esta vez. 
select delete_job (1);

Consideraciones a tener en cuenta.

Si instalas perl 5.6.1, comprueba lo que pasa con mod_perl o con cualquiera de los otros módulos perl que hubieses instalado anteriormente por tu cuenta. Instalar perl 5.6.1 puede corromper módulos que estuvieras utilizando anteriormente con un versión diferente de Perl. No estoy seguro pero ten cuidado. Deberías tener que recompilar los módulos.

Aquí tienes una lista de asuntos que deberías considerar:

  1. Crea una clave externa restrictiva de modo que contact_id de jobs exista en la tabla contact.
  2. Nunca elimines una fila en las tablas 'contact' y 'job'. En cambio añade una columna para inactivarlas. Establece el nombre de la columna para ser 'activa' donde 0 significa inactivo y 1 activo.
  3. Puedes combinar los procedimientos insertar/actualizar en un único procedimiento. Si tus datos son siempre 100% exactos, entonces siempre se tendrá en cuenta el procedimiento para actualizar, y si este no encuentra job, simplemente lo inserta para ti. En algunas condiciones esto puede ser útil.
  4. En cualquier momento puedes añadir más comprobaciones para errores, hazlo. Aunque mis comprobaciones de errores sean razonables se pueden hacer más.
  5. Olvídate de utilizar procedimientos Perl para ejecutar directamente órdenes sql. Símplemente úsalos para manipular los datos.
  6. Utiliza pl/pgsql para combinar procedimientos perl con comandos sql.
  7. Deberías configurar los procedimientos para retroceder en caso de que una inserción, actualización, o eliminación no pueda funcionar debido a un motivo desconocido.
  8. Desconozco cuánta memoria es usada con los procedimientos perl ni cómo es ésta liberada cuando terminan su ejecución. Además, no conozco en profundidad la ejecución de los procedimientos perl. Para mis objetivos, los procedimientos pl/pgsql van a ser siempre más rápidos que la ejecución manual de comandos sql usando scripts en Perl del lado del servidor. Ya que me dirijo de todos modos en la dirección correcta, estoy dispuesto a vivir pasando por encima de los procedimientos perl. Además probablemente pueda coger órdenes de sql muy complejas y reducirlas a unas pocas líneas de código Perl. Si equilibro el apropiadamente el uso de pl/pgsql, sql estándar y pl/perl veo beneficios significativos y pocos inconvenientes.

Liberando permisos en Perl

Esto que voy a hacer es muy malo. Esto relaja algunas cuestiones de la seguridad en Perl así que podrías hacer más cosas sin restricciones.

Primero de todo, añade este método directamente debajo del método "permit" en Safe.pm. Mi Safe.pm se encontraba en /usr/local/src/perl-5.6.1/lib/Safe.pm. El cambio de un módulo que tú no creaste significa que si actualizas alguna vez este módulo, los cambios serán descartados. Una vez más: PUEDES ESTROPEAR EL PROGRAMA DE ALGUNO DE TUS AMIGOS QUE ESTÉ PROGRAMANDO TAMBIÉN EN ESE ORDENADOR. Otra vez, voy a hacer travesuras que tú no deberías hacer.

sub permit_all {
    my $obj = shift;
    $obj->{Mask} = invert_opset full_opset;
}
Segundo, apaga tu servidor de bases de datos.

Tercero, recompila plperl con algunos cambios.

Haz estos cambios en el fichero plperl.c. De aquí


                "require Safe; SPI::bootstrap();"
                "sub ::mksafefunc { my $x = new Safe; $x->permit_only(':default');$x->permit(':base_math');"
                "$x->share(qw[&elog &DEBUG &NOTICE &NOIND &ERROR]);"
                " return $x->reval(qq[sub { $_[0] }]); }"

Para hacer esto (puedes obtenerlo de este archivo New_plperl.txt )

                "require Safe; SPI::bootstrap();"
                "sub ::mksafefunc { my $x = new Safe; $x->permit_only(':default');$x->permit(':base_math');"
 "$x->permit_all('');"
                "$x->share(qw[&elog &DEBUG &NOTICE &NOIND &ERROR]);"
                " return $x->reval(qq[sub { $_[0] }]); }"

Ahora recompila plperl e instálalo.
cd /usr/local/src/postgresql-7.1.1/src/pl/plperl
rm -f *.o
make 
make install
Cuarto, reinciar el servidor de bases de datos postgresql.

Mira si puedes escapar a un shell,

drop function ls_bad ();
CREATE FUNCTION  ls_bad () RETURNS text AS '
my @Temp = `ls /tmp`;
my $List = "@Temp";
$List =~ s/\n/ /g;
  return $List;
' LANGUAGE 'plperl';
select ls_bad();
Si consigues el contenido de tu directorio "/tmp" entonces puedes escaparte a un shell sin ningún problema. Esto es muy peligroso.

Durante un día entero traté de averiguar cómo obtener DynaLoader para trabajar en pl/perl. Basicamente, leí documentación sobre cómo introducir Perl en C, y no era tan duro de hacer. Existe hasta un manual de ello. Lo mantuve funcionando con los problemas. Finalmente, traté de no utilizar el módulo Safe.pm totalmente, pero no llegué muy lejos. Estaba cerca de compilar Dynaloader en plperl, pero me rendí. Después de perder un día, que lo intente otro.

Si puedes conseguir DynaLoader para trabajar correctamente con plperl, o con más exactitud, si encuentras un modo de hacerlo para poder cargar cualquier módulo que quiera con plperl, entonces hazmelo saber. Yo alcancé el punto donde podía cargar módulos pr puros pero no los que tenían componentes en c. Me gustaría ser capaz de cargar cualquier módulo en absoluto. Creo que tendríamos que dejar de usar Safe.pm para hacerlo más fácil. Por favor envíame un correo electrónico a articles@gnujobs.com. ¡Estaría muy interesado si tuvieras éxito!

Por favor, no hagas esto. Únicamente te lo enseño para que puedas tener unas nociones de normas de seguridad por si realmente las necesitases.

Conclusion

La combinación Perl con PL/PGSQL es algo REALMENTE CHULO CHULO. ¿Por qué?
  1. Me gustar usar Perl (o cualquier otro lenguaje -- ¿debería alguien hacer un interface Python?) para manipular datos, porque SQL es como un dolor de cabeza algunas veces (en la manipulación de datos).
  2. La combinación de Perl y PL/PGSQL puede hacer que la mayor parte del trabajo esté hecho en el lado de la base de datos, lo cual significa que puedes hacer menos código en el lado del cliente. Por ejemplo, suponemos que tienes un servidor web que se conecta a un servidor de bases de datos. Si el servidor de datos está manejando mucha carga de perl, tus scripts perl en el servidor web no serán tan grandes. Esto es cierto para procedimientos en cualquier caso general. ¿No es más agradable ejecutar un procedimiento de los anteriores que tener scripts Perl en el servidor web ejecutando todos estos pasos (el procedimiento los hace por ti)?
  3. Si puedes hacer todo el código imaginable en perl del lado de la base de datos, entonces tendrás menos trabajo con cualquier otro lenguaje que decidas usar para conectar a la base de datos. De este modo, tus procedimientos se convierten en objetos que tus programadores web simplemente tendrán que entender el modo de uso pero no cómo fueron creados. Esto es muy agrable.
  4. Poco a poco voy a utilizar más procedimientos perl (donde sean apropiados) y testear la estabilidad de pl/perl.
  5. Me gustaría ser capaz de cargar cualquier módulo en plperl sólo por diversión. Si encontraste cómo hacerlo envíame por favor un correo electrónico a articles@gnujobs.com.
PostgreSQL es con mucho el servidor de base de datos más chulo con el que yo haya trabajado alguna vez. MySQL está en un cercano segundo puesto. Realmente nunca he disfrutado trabajando con ningún servidor comercial. Veo mayor potencial con PostgreSQL, en realidad veo que los servidores de bases de datos comerciales siguen algunas de las cosas que PostgreSQL hará. Estoy impaciente por ver si alguien desarrolla un interface Python para crear procedimientos Python. El capítulo 20 del último libro "Programming Python" (ISBN: 0-596-00085-5) trata acerca de incrustar Python en C. Cuando tenga tiempo libre, como el año que viene, podría intentarlo... ¡A menos que alguien me dé una paliza! Creo que la capacidad de tener procedimientos escritos en diferentes lenguajes de programación será muy valorada en el futuro. También estoy impaciente por ver si podemos obtener procedimientos que devuelvan más de un único valor. Es muy frustrante que sólo podamos devolver un único valor. Intenté definir una función con más de un valor de retorno pero no funcionó. Traté de conseguir un procedimiento que devolviese un REGISTRO pero no logré llegar muy lejos.

Mark Nielsen

Mark trabaja como consultor independiente dedicando tiempo a causas como GNUJobs.com, escribir artículos, crear software libre y trabaja como voluntario en eastmont.net.


Copyright © 2001, Mark Nielsen.
Licencia de copia http://www.linuxgazette.com/copying.html
Publicado en el número 67 de Linux Gazette, Junio 2001
>