Al buscar como insertar un archivo en un campo BLOB de PostgreSQL con PHP, no encontré muchos lugares donde expliquen de una manera clara la forma de hacerlo, los sitios en los que encontré son foros, es así que publico este artículo con el fin de aportar un poco a la utilización de este magífico O-RDBMS.

El desarrollo de este script está basado en lo que hizo mi friend Braulio Soncco, en sus artículos explica como insertar y recuperar imágenes en/de un campo BLOB pero utilizando MySQL.

Requerimientos

Para el desarrollo de este script es necesario tener activado la extensión de PostgreSQL en PHP, para lo cual, si se trabaja en linux se tiene que configurar de acuerdo a la distribución que se tiene(normalemente tiene el nombre pgsql.so), en windows es php_pgsql.dll. También es necesario tener configurado el servidor de base de datos para que acepte conexiones TCP/IP.

Desarrollo del script

PostgreSQL dá la posibilidad de almacenar archivos de gran tamaño en columnas tipo oid, bytea y muchos otros, este script utiliza ambos tipos para insertar el archivo.

El formulario de upload y el script de inserción está en una sola página, con el nombre index.php:

<?php
	include_once "sitedefs.php";
	# Muestra el mensaje de confirmación
	$msg="";
	# Verificamos que el formulario no ha sido enviado aun
	$postback = (isset($_POST["enviar"])) ? true : false;
	# Concexión a la base de datos
	$link = pg_connect("host=$dbhost user=$dbuser password=$dbpwd dbname=$dbname") or die(pg_last_error($link));
	if($postback){		
		# Variables del archivo
		$type = $_FILES["archivo"]["type"];
		$tmp_name = $_FILES["archivo"]["tmp_name"];
		$size = $_FILES["archivo"]["size"];
		$nombre = basename($_FILES["archivo"]["name"]);
		# Contenido del archivo
	  $fp = fopen($tmp_name, "rb");
  	$buffer = fread($fp, filesize($tmp_name));
		fclose($fp);
		# Descripción de la foto
		$desc = $_POST["desc"];
		$isoid=$_POST['tipo']=='oid'?true:false;
		if(!$isoid){
			# Escapa el contenido del archivo para ingresarlo como bytea
			$buffer=pg_escape_bytea($buffer);
			$sql = "INSERT INTO foo(nombre, descripcion, archivo_bytea, mime, size)
							VALUES ('$nombre', '$desc', '$buffer', '$type', $size)";
		}
		else{
			# Inicia una transacción
			pg_query($link, "begin");
			# Crea un objeto blob y retorna el oid
			$oid=pg_lo_create($link);
			$sql = "INSERT INTO foo(nombre, descripcion, archivo_oid, mime, size)
			VALUES ('$nombre', '$desc', $oid, '$type', $size)";
		}
		# Ejecuta la sentencia SQL
		pg_query($link, $sql) or die(pg_last_error($link));
		if($isoid){
			# Abre el objeto blob
			$blob=pg_lo_open($link,$oid,"w");
			# Escribe el contenido del archivo
			pg_lo_write($blob,$buffer);
			# Cierra el objeto
			pg_lo_close($blob);
			# Compromete la transacción
			pg_query($link, "commit");
		}		
		$msg="Archivo guardado";
	}
	# Lista los archivos subidos a la base de datos
	$sql = "select id, nombre, descripcion, coalesce(archivo_oid,-1) as archivo_oid, 
		coalesce(archivo_bytea,'-1') as archivo_bytea from foo;";
	$result = pg_query($link, $sql);
	$lista = "";
	if($result){
		while ($row=pg_fetch_array($result)){
			$lista .= "<tr>";
			$lista .= "<td>$row[nombre]</td>";
			$lista .= "<td>$row[descripcion]</td>";
			$lista .= "<td><a href="download.php?id=$row[id]" title="Intentará mostrar el contenido del archivo">Ver</a> | 
			<a href="download.php?id=$row[id]&f=1" title="Baja el archivo">Bajar</a></td>";
			$lista .= "</tr>";
		} 
	}
	pg_free_result($result);	
	pg_close($link);
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="es">
<head>
<title>Insertar archivos en un campo blob de PostgreSQL</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" type="text/css" media="screen" href="main.css" />
</head>
<body>
<div id="contenedor">
	<div id="cabecera"><h1>Insertar y recuperar un campo BLOB con PHP y PostgreSQL</h1></div>
	<span class="msg"><?=$msg?></span>
	Subir archivo:
	<div id="postform">
		<form name="frmblob" id="frmblob" method="post" 
			enctype="multipart/form-data" action="">
			<fieldset>
				<label for="desc" accesskey="e">D<span class="key">e</span>scripción</label><br />
				<input type="text" id="desc" name="desc" size="55" title="Descripción del archivo" /><br />
				<label for="archivo" accesskey="r">A<span class="key">r</span>chivo</label><br />
				<input type="file" id="archivo" name="archivo" title="Archivo a subir" size="40" /><br />
				<label for="tipo" accesskey="i">T<span class="key">i</span>po</label><br />
				<select name="tipo" id="tipo" title="Tipo de dato del campo en que se guardará el archivo">
					<option value="bytea" title="bytea" selected>bytea</option>
					<option value="oid" title="oid">oid</option>					
				</select><br />
				<input type="submit" name="enviar" id="enviar" value="Guardar" />
			</fieldset>
		</form>
	</div>
	<div id="files">
		<table>
		<tr>
			<th>Nombre</th>
			<th>Descripción</th>
			<th>Mostrar</th>
		</tr>
			<?=$lista?>
		</table>
	</div>
	<div id="pie">
		<a href="http://www.buayacorp.com" title="Programación y Diseño">BuayaCorp</a> © 2005<br />
		<a href="http://creativecommons.org/licenses/by/2.0/">
			<img src="ccsomerights.gif" alt="Licencia de Creative Commons" border="0">
		</a>
	</div>
</div>
</body>
</html>

En las primeras líneas lo que se hace es recuperar los datos del archivo que se está subiendo al servidor, luego se guarda el contenido de dicho archivo en un buffer.

A continuación dependiendo del tipo de dato seleccionado en el formulario se asigna la variable $isoid, que indica que se insertará en el campo de tipo oid o no. Las diferencias que se presentan al utilizar estos tipos de datos es para ingresar un registro de tipo bytea es necesario ingresar en el formato correcto (tiene que estar en formato octal,
134 = 92 = contra slash), en cambio para trabajar con oid’s es necesario utilizar transacciones y funciones especiales para la escritura de objetos BLOB.

El resto del código es para listar los objetos que ya fueron insertados en la base de datos.

Ahora pasemos al código que se encarga de recuperar el contenido de dichos archivos de la base de datos:

<?
	# El parámetro f=1 indica que se va a forzar a bajar el archivo
	$f=isset($_REQUEST['f'])?$_REQUEST['f']:0;
	# Recupera el id pasado como parámetro
	$id=isset($_REQUEST['id'])?$_REQUEST['id']:0;
	include_once "sitedefs.php";
	# Conexión a la base de datos
	$link = pg_connect("host=$dbhost user=$dbuser password=$dbpwd dbname=$dbname") or die(pg_last_error($link));

	# Recupera el archivo en base al ID
	$sql = "select id, nombre, descripcion, mime, size, coalesce(archivo_oid,-1) as archivo_oid, 
	coalesce(archivo_bytea,'-1') as archivo_bytea from foo where id=$id";
	$result=pg_query($link, $sql);
	# Si no existe, redirecciona a la página principal
	if(!$result || pg_num_rows($result)<1){
		header("Location: index.php");
		exit();
	}
	# Recupera los atributos del archivo
	$row=pg_fetch_array($result,0);
	pg_free_result($result);
	# Para determinar si archivo a bajar fue ingresado al campo archivo_oid (es de tipo "oid")
	$isoid=false;
	if($row['archivo_bytea']==-1) $isoid=true;
	if($row['archivo_oid']==-1) $isoid=true;
	if($row['archivo_bytea']==-1 && $row['archivo_oid']==-1) die('No existe el archivo para mostrar o bajar');
	if($isoid){
		# Inicia la transacción
		pg_query($link, "begin");
		# Abre el objeto blob
		$file=pg_lo_open($link, $row['archivo_oid'], "r");
	}
	else{
		# Hace el proceso inverso a pg_escape_bytea, para que el archivo esté en su estado original
		$file=pg_unescape_bytea($row['archivo_bytea']);
	}
	# Envío de cabeceras
	header("Cache-control: private");
	header("Content-type: $row[mime]");
	if($f==1)
		header("Content-Disposition: attachment; filename="$row[nombre]"");
	header("Content-length: $row[size]");
	header("Expires: ".gmdate("D, d M Y H:i:s", mktime(date("H")+2, date("i"), date("s"), date("m"), date("d"), date("Y")))." GMT");
	header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
	header("Cache-Control: no-cache, must-revalidate");
	header("Pragma: no-cache");
	
	if($isoid){
		# Imprime el contenido del objeto blob
		pg_lo_read_all($file);
		# Cierra el objeto
		pg_lo_close($file);
		# Compromete la transacción
		pg_query($link, "commit");
	}
	else{
		# Imprime el contenido del archivo
		print $file;
	}
	pg_close($link);	
?>

El código se parece al anterior en cuanto al tipo de dato en el que el archivo fué enviado, lo que se hace es recuperar el archivo en base a id seleccionado y determinar qué tipo de dato tiene, luego simplemente se imprime el contenido del archivo que es recuperado de la base de datos.

La estructura de la tabla es la siguiente:

create table foo(
	id serial primary key not null,
	nombre varchar(100),
	descripcion varchar(500),
	archivo_bytea bytea,
	archivo_oid oid,
	mime varchar(100),
	size float
);

Elementos Utilizados