Finalmente decidí conseguir Perl instalado en PostgreSQL porque PostgreSQL tiene todas las características que me gustan:
Todo el proceso fue un dolor porque había que hacer ajustes aquí y allí. Estos son los pasos básicos:
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 ConfigureCambia 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
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' postgresEn 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/pg711Entonces ejecuta esta orden,
chmod 755 .profile
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 psqlEsto 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.
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 !@#$%^&*()_+| ');
--
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;
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);
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);
Aquí tienes una lista de asuntos que deberías considerar:
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 installCuarto, 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.
Mark Nielsen