Les Larges Objects (LOB) sous Oracle 9i

Helyos

Cette article fait le point sur les LOB, les Larges OBjects sous Oracle, vous trouverez des exemples d'utilisation en environnement de production, ainsi que des exemples appliqués au portail WEB.

- Titre : Les Larges Objects (LOB) sous Oracle 9i

- Auteur : Helyos

- Parution : 23 avril 2004

- Licence : Copyright ® 2004 Helyos. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Les CLOBs

- II-A. Introduction aux CLOB

- II-B. Comment faire pour utiliser un CLOB en SQL ?

- II-C. Comment faire pour utiliser un CLOB en PL/SQL

- II-D. Comment faire pour utiliser un CLOB en HTML ?

- III. Les BLOBs

- III-A. Introduction

- III-B. Comment faire pour utiliser un BLOB en PL/SQL

- III-C. Comment faire pour utiliser un BLOB en HTML

- IV. LES BFILES

- IV-A. Introduction

- IV-B. Comment utiliser des BFILES

- IV-C. Comment faire pour afficher le contenu d'un BFILE sur une page web

- V. Annexes

- V-A. Description des procédures et fonctions utilisées

- V-B. Références

- VI. Remerciements

I - Introduction

Qu'est-ce qu'un LOB ?

En 1999, SQL définit trois nouveaux types d'objets destinés aux stockages volumineux il s'agit des LOBs (Large OBject). C'est un nouveau type de donnée qui permet de stocker dans un champ un gros volume d'informations. Le type LOB est similaire au type de données LONG et LONG RAW.

Il existe différents types de LOBs. Ceux qui sont stockés dans la base de données (LOB interne) et ceux qui sont stockés en dehors de la base de données (LOB externe). Les LOBs externe sont référencés dans la base par un pointeur.

On distinguera en LOB interne les types de données suivants :

- - CLOB (Character Large Object) : Dédié pour les chaînes de caractères.

- - BLOB (Binary Large Object): Dédié aux données binaires

- - NCLOB (National Character Large Object) : Dédié pour les chaînes de caractères Unicode.

Et en LOB externe

- - BFILE (Binary File): Dédié aux fichiers de données stockés dans le système de fichier du système d'exploitation.

Pourquoi LOB et pas LONG ou LONG RAW ?

La taille d'un LONG ne peut excéder 2Go alors que la taille d'un LOB peut monter jusqu'à 4Go.

De plus lorsque vous souhaiterez récupérer une valeur de type LONG dans une variable PL/SQL, vous ne pourrez pas récupérer une valeur ayant une taille supérieure à 32760 Bytes (alors qu'une colonne de type LONG supporte jusqu'à 2Go).

Il n'est pas possible d'avoir une table avec plusieurs colonnes de type LONG ou LONG RAW, pour un LOB il n'existe pas cette limitation.

Il n'existe aucune API dédiée aux LONG permettant de travailler sur des types LONG ou LONG RAW (il existe le package DBMS_SQL qui fournit quelques fonctions pour les LONG) alors qu'il existe le package DBMS_LOB (voir l'article sur le DBMS_LOB de SheikYerbouti) pour gérer tout les types de données LOB.

Il faut savoir que les accès à un LONG se font de manière séquentielle (vous êtes dans l'obligation de lire le LONG du début à la fin) alors que pour les LOBs les accès se font de manière directe (d'où un gain de performances).

Il n'est pas possible de passer une valeur LONG à une fonction SQL, et en PL/SQL une variable LONG sera automatiquement convertie en VARCHAR2 (à moins que la taille du VARCHAR2 ne le permette pas, auquel cas la variable sera convertie en LONG).

Lors d'une requête SELECT l'intégralité du LONG vous est retournée alors que pour un LOB seul le pointeur vous sera retourné.

Maintenant que vous avez pu voir les différences entre ces deux types de données, la question qui normalement vous vient à l'esprit est: « Comment faire pour passer d'un LONG à un LOB ? ». Et bien c'est le point suivant.

Comment convertir un LONG ou un LONG RAW en CLOB ou en BLOB ?

Et bien c'est assez simple pour une fois, car Oracle depuis la version 8.1 (8i) a ajouté une fonction TO_LOB qui vous permettra de convertir très facilement un LONG (ou LONG RAW) vers un CLOB (ou un BLOB).

Par exemple :

CREATE TABLE t1(x INT, y LONG);
CREATE TABLE t2(x INT, y CLOB);
INSERT INTO t1 VALUES (1, rpad('*', 4000, '*'));
INSERT INTO t2
SELECT x, to_lob(y) FROM t1;

Il est important de savoir que la fonction TO_LOB ne pourra pas être utilisée en PL/SQL et qu'il faudra l'utiliser à l'intérieur d'un EXECUTE IMMEDIATE par exemple.
Tous les codes que vous trouverez dans cet article ont été testés sur une base de données Oracle 9.2.0.1.0 et avec l'utilisateur SCOTT

II - Les CLOBs

II-A - Introduction aux CLOB

Le type de données CLOB est un type de données qui va permettre de stocker d'importants volumes de données de type « chaîne de caractères ». La taille maximale d'un CLOB est de 4Go.

Il faut savoir que les CLOB conservent les mêmes règles transactionnelles que les types de données tels que VARCHAR2, NUMBER, etc. et qu'ils peuvent être restaurés sans aucunes actions complémentaires.

II-B - Comment faire pour utiliser un CLOB en SQL ?

L'utilisation des CLOB en SQL est très simple.
Voici le code de la table d'exemple :

create table t_test (id_clob number, texte clob);

INSERT INTO t_test VALUES (1,'Hello World');
INSERT INTO t_test VALUES (2,rpad('*',32000,'*'));
COMMIT;

SELECT *
FROM t_test;
ID_CLOB TEXTE
---------- ----------------------------------------------------------------
1 Hello World
2 ****************************************************************

L'insertion et la récupération d'un CLOB en SQL ne sont en rien différentes d'une colonne de type standard. Par contre on rencontrera des difficultés à l'affichage qui sont dues à une limitation de SQL. (On ne pourra afficher que les 4000 premiers caractères)

II-C - Comment faire pour utiliser un CLOB en PL/SQL

Voici un exemple d'insertion en utilisant la fonction empty_clob() qui permet de remplir la colonne de type CLOB avec un pointeur vers un CLOB vide que l'on remplira après avec la procédure DBMS_LOB.WRITE.

CREATE OR REPLACE PROCEDURE insert_test (p_id NUMBER, p_text VARCHAR2)
IS
v_clob CLOB;
BEGIN
-- On insere la ligne avec un CLOB vide
INSERT INTO t_test VALUES (p_id, empty_clob()) returning texte into v_clob;
-- On le rempli avec un contenu
DBMS_LOB.WRITE(v_clob, 1, length(p_text),p_text);
COMMIT;
END;
/

Ou bien de manière plus simple

CREATE OR REPLACE PROCEDURE insert_test (p_id NUMBER, p_text CLOB)
IS
BEGIN
INSERT INTO t_test VALUES (p_id, p_text);
COMMIT;
END;
/

II-D - Comment faire pour utiliser un CLOB en HTML ?

Avant toute choses nous allons devoir configurer notre passerelle mod_plsql pour pouvoir utiliser nos fonctions et procédure (la méthode est identique pour une base de données 8i ou 9i).

Par défaut quand vous installez Oracle, celui-ci installe un serveur Apache.

La première étape va être de le démarrer pour pouvoir accéder à l'url suivante (page de configuration de la passerelle PL/SQL).

http://127.0.0.1/pls/simpledad/admin_/gateway.htm

Attention : Nous n'aborderons pas ici les problèmes de sécurité, ni la configuration avancée de la passerelle.

Vous arrivez sur la page d'administration suivante

Ensuite cliquez sur Paramètres des DAD de la passerelle.

Puis cliquez sur Ajout d'un descripteur par défaut
Voici les informations à saisir (nous allons utiliser le compte de notre bon vieux Scott).

Nom du descripteur d'accès à la base de données = test

Schema Name = scott

Nom d'utilisateur Oracle = scott

Mot de passe Oracle = tiger

Chaîne de connexion Oracle = <host string>

Puis cliquez sur ok
Nous serons alors maintenant capable d'appeler des procédures PL/SQL (de notre utilisateur scott) au travers de notre browser web favori en utilisant des urls du type

- http://127.0.0.1/pls/test/<nom package>.<procedure>

Ou bien

- http://127.0.0.1/pls/test/<procedure>

Pour les insertions nous allons créer un formulaire qui appellera une procédure d'insertion de notre CLOB.
Une fois notre ligne créee, cette procédure nous redirigera automatiquement vers une page qui affichera le contenu de notre table.

CREATE OR REPLACE PACKAGE html_clob IS

-- Affichage du contenu de la table
PROCEDURE display_table_content;

-- Insertion dans la table
PROCEDURE add_clob(p_id NUMBER, p_text CLOB);

-- Affichage du formulaire d'ajout
PROCEDURE display_add_clob;

END html_clob;
/
CREATE OR REPLACE PACKAGE BODY html_clob IS

PROCEDURE display_table_content IS
BEGIN
htp.print('<html><head></head><body>');
htp.print('<TABLE BORDER="1" width="100%">');
htp.print('<TR><TD width="10%">ID</TD><TD width="90%">TEXTE</TD></TR>');
FOR rec IN (SELECT * FROM t_test ORDER BY id_clob) LOOP
htp.print('<TR><TD width="10%">' || rec.id_clob ||
'</TD><TD width="90%">' || rec.texte || '</TD></TR>');
END LOOP;
htp.print('</TABLE>');
htp.print('</body></html>');
END;

PROCEDURE add_clob(p_id NUMBER, p_text CLOB) IS
BEGIN
INSERT INTO t_test VALUES (p_id, p_text);
COMMIT;
owa_util.redirect_url(curl => 'scott.html_clob.display_table_content');
END;

PROCEDURE display_add_clob IS
BEGIN
htp.print('<html><head></head><body>');
htp.formopen(curl => 'scott.html_clob.add_clob', cmethod => 'POST');
htp.print('<TABLE BORDER="1" WIDTH="100%"><TR><TD width="20%">Id</TD><TD>');
htp.formtext(cname => 'p_id', csize => 10, cattributes => 'style="width: 100%"');
htp.print('</TD></TR><TR><TD>Texte</TD><TD>');
htp.formtextareaopen(cname => 'p_text', nrows => 10, ncolumns => 50, cattributes => 'style="width: 100%"');
htp.formtextareaclose;
htp.print('</TD></TR><TR><TD colspan="2" align="center">');
htp.formsubmit;
htp.print('</TD></TR></TABLE>');
htp.formclose;
htp.print('</body></html>');
END;

END html_clob;
/

grant execute on html_clob to public;

Ensuite il vous suffit d'appeler l'url suivante pour ajouter du contenu à la table

- http://127.0.0.1/pls/test/scott.html_clob.display_add_clob

Et vous pourrez aussi appeler l'url suivante pour voir le contenu de la table

- http://127.0.0.1/pls/test/scott.html_clob.display_table_content

Vous pourrez ainsi voir que contrairement à SQL*Plus la page HTML affiche l'intégralité du contenu de la colonne TEXTE.

III - Les BLOBs

III-A - Introduction

Le type de données BLOB va permettre de stocker d'important volume de données de type binaire. La taille maximale d'un BLOB ne peut excéder 4Go. Ce type de données nous permettra de stocker n'importe quel type de données dans la base (images, pdf, mp3, doc, etc.).

Il faut savoir que les BLOB conservent les mêmes règles transactionnelles que les types de données tels que VARCHAR2, NUMBER, etc. et qu'ils peuvent être restaurés sans aucunes actions complémentaires.

III-B - Comment faire pour utiliser un BLOB en PL/SQL

Il est à noter qu'il n'est pas possible d'ajouter un BLOB à une table avec un ordre INSERT basique, nous serons donc dans l'obligation d'utiliser un bloc PL/SQL.

Dans cet exemple nous allons voir comment stocker dans la base des images (la méthode serait identique pour tout autre type de fichier).

La première étape est de créer un répertoire qui nous servira à placer les images qui devront être uploader. Ce répertoire doit obligatoirement être accessible par le serveur. Celui-ci se servira uniquement de ce répertoire pour accéder à nos images.

On se connecte en tant que DBA pour créer le DIRECTORY et on donnera les droits à notre utilisateur.

CONNECT system / < pass >
CREATE directory blobdir AS 'c:\temp\';
GRANT READ ON directory blobdir TO scott;

On crée une table qui contiendra nos images

CREATE TABLE t_blob (id number, image blob);

Ensuite on crée notre package

CREATE OR REPLACE PACKAGE sql_blob IS

-- Procédure servant à ajouter un BLOB à notre table.
-- p_id correspond à l'id
-- p_name correspond au nom du fichier à insérer.
PROCEDURE add_blob(p_id NUMBER, p_name VARCHAR2);

END sql_blob;
/
CREATE OR REPLACE PACKAGE BODY sql_blob IS

PROCEDURE add_blob(p_id NUMBER, p_name VARCHAR2) IS
v_blob  BLOB;
v_bfile BFILE;
BEGIN
-- On insère la ligne avec un blob vide dont on récupère le pointeur
INSERT INTO t_blob
VALUES
(p_id, empty_blob())
RETURNING image INTO v_blob;
-- On déclare un pointeur vers notre fichier
v_bfile := bfilename(directory => 'BLOBDIR', filename => p_name);
-- On ouvre ce fichier
dbms_lob.fileopen(v_bfile);
-- On rempli l'emplacement du BLOB vide dans la table avec le contenu de notre fichier
dbms_lob.loadfromfile(v_blob, v_bfile, dbms_lob.getlength(v_bfile));
-- On ferme le fichier
dbms_lob.fileclose(v_bfile);
END;
END sql_blob;
/

Pour rajouter une image dans notre table il nous suffira de lancer la procédure add_blob de la manière suivante :

exec sql_blob.add_blob(1,'helyos.bmp');

Maintenant que nous avons stocké notre image dans la base, comment faire pour régénérer un fichier à partir de notre BLOB ?

Et bien nous allons rajouter une nouvelle procédure à notre package.

Attention: Cette procédure bien que fonctionnant ne donnera pas le résultat escompté car le package UTL_FILE est buggé (il remplace les chaînes hexadécimale 0A par 0D 0A, cette erreur correspond au bug <Bug:2546782> UTL_FILE.PUT_RAW WRITES WRONG NUMBER OF BYTES qui ne sera fixé que en 10G et pas en 9i)

Voici la procédure en elle-même :

PROCEDURE write_blob(p_id NUMBER, p_name VARCHAR2) IS
v_file       utl_file.file_type;
v_repertoire VARCHAR2(512) := 'c:\temp\';
v_fichier    VARCHAR2(256) := p_name;
v_buffer     RAW(32000);
v_offset     PLS_INTEGER DEFAULT 1;
v_taille     PLS_INTEGER;
v_longueur   PLS_INTEGER;
v_chunk      PLS_INTEGER;
v_blob       BLOB;
BEGIN
-- On récupére le BLOB
SELECT image INTO v_blob FROM t_blob WHERE id = 1;
-- On l'ouvre en lecture afin de pouvoir le parser plus facilement
dbms_lob.OPEN(v_blob, dbms_lob.lob_readonly);
-- On regarde la taille de Chunk idéale
v_chunk := dbms_lob.getchunksize(v_blob);
-- On regarde sa longueur
v_longueur := dbms_lob.getlength(v_blob);
-- On crée le fichier sur le disque dur
v_file     := utl_file.fopen(v_repertoire, v_fichier, 'w', 32767);
-- On ecrit dans le fichier tant que l'on a pas fait tout le BLOB
WHILE v_offset < v_longueur LOOP
IF v_longueur - (v_offset - 1) > v_chunk THEN
v_taille := v_chunk;
ELSE
v_taille := v_longueur - (v_offset - 1);
END IF;
v_buffer := NULL;
-- On lit la partie du BLOB qui nous interesse
dbms_lob.READ(v_blob, v_taille, v_offset, v_buffer);
-- On ecrit cette partie dans le fichier
utl_file.put(file => v_file, buffer => utl_raw.cast_to_varchar2(v_buffer));
utl_file.fflush(file => v_file);
v_offset := v_offset + v_taille;
END LOOP;
-- On ferme le BLOB
dbms_lob.CLOSE(v_blob);
-- On ferme le fichier
utl_file.fclose(v_file);EXCEPTION
WHEN OTHERS THEN
IF dbms_lob.ISOPEN(v_blob) = 1 THEN
dbms_lob.CLOSE(v_blob);
END IF;
IF utl_file.is_open(file => v_file) THEN
utl_file.fclose(file => v_file);
END IF;
END;

Vous pouvez tester cette procédure mais le fichier résultant aura des caractères en trop. Pour cela Metalink conseille d'utiliser une procédure utilisant du java pour générer des fichiers à partir de BLOB contenu dans la base (How To Unload LOBs to a File Using PL/SQL Java Stored <Note:247546.1>.)

III-C - Comment faire pour utiliser un BLOB en HTML

Il est toutefois possible d'afficher un BLOB au travers d'une page web et ce quel que soit la méthode qui ait été utilisée pour l'ajouter à la base de données.

L'Upload et le Download de fichier au travers de MOD_PLSQL vont nécessiter quelques petites modifications de notre DAD. La table de référence devra être différente afin de contenir un certain nombre d'informations supplémentaire.

CREATE TABLE t_html_blob (
NAME               VARCHAR(128)   UNIQUE NOT NULL,
MIME_TYPE          VARCHAR(128),
DOC_SIZE           NUMBER,
DAD_CHARSET        VARCHAR(128),
LAST_UPDATED       DATE,
CONTENT_TYPE       VARCHAR(128),
CONTENT            LONG RAW,
BLOB_CONTENT       BLOB) ;

Voici les différentes étapes à suivre sur une configuration de base.
Ouvrir l'url http://127.0.0.1/pls/simpledad/admin_/gateway.htm

Ensuite cliquez sur Paramètres des DAD de la passerelle

Puis cliquez sur Ajout d'un descripteur par défaut

Voici les informations à saisir

Nom du descripteur d'accès à la base de données = test

Schema Name = scott

Nom d'utilisateur Oracle = scott

Mot de passe Oracle = tiger

Chaîne de connexion Oracle = <host string>

Table de documents = scott.t_html_blob

Chemin d'accès au document = docs

Procédure d'accès au document = scott.html_blob.process_download

Puis cliquez sur ok

Maintenant que nous avons configuré le DAD pour pouvoir Uploader nos documents. Nous allons créer une table de correspondances entre le BLOB et l'id que nous avons saisi.

CREATE TABLE t_images (id number, filename varchar2(128));

Il ne vous reste plus qu'a créer le petit package suivant

CREATE OR REPLACE PACKAGE html_blob IS

-- Insertion dans la table des correspondances
PROCEDURE add_blob(p_id NUMBER, p_name VARCHAR2);

-- Formulaire d'upload
PROCEDURE display_add_blob;

END html_blob;
/
CREATE OR REPLACE PACKAGE BODY html_blob IS

PROCEDURE add_blob(p_id NUMBER, p_name VARCHAR2) IS
BEGIN
-- L'image ayant été ajouté par mod_plsql dans notre table t_html_blob nous avons juste à
-- enregistrer la correspondance entre notre id et le nouveau nom du fichier dans la base
INSERT INTO t_images VALUES (p_id, p_name);
COMMIT;
htp.print('Votre image à bien été uploadée ');
END;

PROCEDURE display_add_blob IS
BEGIN
htp.print('<HTML><BODY>');
-- Il est important de ne pas oublier le cenctype => 'multipart/form-data'
-- car c'est lui qui demande au DAD d'uploader notre image dans notre table par défaut.
htp.formopen(curl => 'scott.html_blob.add_blob', cmethod => 'POST', cenctype => 'multipart/form-data');
htp.print('<TABLE BORDER="1" WIDTH="100%">');
htp.print('<TR><TD width="20%">Id</TD><TD>');
htp.formtext(cname => 'p_id', csize => '50', cattributes => 'style="width: 100%"');
htp.print('</TD></TR>');
htp.print('<TR><TD width="20%">File</TD><TD>');
htp.print('<INPUT type="file" name="p_name" style="width: 100%">');
htp.print('</TD></TR>');
htp.print('<TR><TD colspan="2" align="center">');
htp.formsubmit;
htp.print('</TD></TR></TABLE>');
htp.formclose;
htp.print('</BODY></HTML>');
END;

END html_blob;
/

Une fois ce package crée il ne vous reste plus qu'à appeler l'url
http://127.0.0.1/pls/test/scott.html_blob.display_add_blob

Saisissez alors un id puis le fichier que vous souhaitez Uploader. Cliquez sur submit et le tour est joué.

Il faut savoir qu'il n'y a pas de procédure ou de méthode particulière pour insérer ce BLOB dans notre table t_blob, c'est la passerelle PL/SQL qui va se charger automatiquement de cela, en analysant les caractéristiques du fichier envoyé.

Il est intéressant de voir que la passerelle détecte automatiquement le mime/type du fichier, ce qui rend cette méthode beaucoup plus dynamique vis-à-vis des différents formats de fichiers.

Nous allons voir comment faire pour afficher le contenu de notre BLOB sur une page HTML

Tout d'abord ajoutons la procédure suivante à notre package html_blob.

PROCEDURE display_blob(p_id NUMBER) IS
rec   t_html_blob%ROWTYPE;
v_amt NUMBER DEFAULT 4096;
v_off NUMBER DEFAULT 1;
v_raw RAW(4096);
BEGIN
-- On récupere les infos relatives à notre BLOB
SELECT *
INTO   rec
FROM   t_html_blob
WHERE  NAME = (SELECT filename FROM t_images WHERE id = p_id);

-- On défini le header en fonction de l'objet à afficher
owa_util.mime_header(rec.mime_type);
BEGIN
LOOP
-- On lit la partie du  BLOB qui nous interesse
dbms_lob.READ(rec.blob_content, v_amt, v_off, v_raw);
-- On l'affiche ATTENTION le HTP.PRN est OBLIGATOIRE pour éviter les retours à la ligne intempestif
htp.prn(utl_raw.cast_to_varchar2(v_raw));
v_off := v_off + v_amt;
v_amt := 4096;
END LOOP;
dbms_lob.CLOSE(rec.blob_content);
EXCEPTION
WHEN no_data_found THEN
NULL;
END;
END;

Pour afficher notre BLOB voici l'url qui faudra lancer.
http://127.0.0.1/pls/test/scott.html_blob.display_blob?p_id=1

Vous ne pourrez pas afficher autre chose sur cette page, car cette page n'est en fait constituée que du contenu de notre BLOB. Si vous voulez utiliser ce BLOB comme une image vous devrez créer une autre page web avec une balise image. <IMG SRC=« http://127.0.0.1/pls/test/scott.html_blob.display_blob?p_id=1 »>

IV - LES BFILES

IV-A - Introduction

Le type de données BFILE vous permet de stocker des objets de types binaires en dehors de la base de données.

Le type BFILE est en fait un pointeur vers le fichier binaire (ce pointeur contient le path complet vers un fichier système).

Les BFILE sont de types Read-only et ne peuvent donc être modifiés par le serveur. Leur taille, dépendante du système, ne pourra pas dépasser la taille de 2^32-1 Bytes.

L'intégrité des données n'est plus assurée par Oracle mais par le système d'exploitation.

Ne faisant pas partie de la base de données, les BFILEs ne participent pas aux transactions, ne sont pas récupérables sans actions de sauvegarde complémentaire.

Le nombre maximum de BFILES ouverts est déterminé par le paramètre SESSION_MAX_OPEN_FILES (qui lui aussi dépend du système d'exploitation).

IV-B - Comment utiliser des BFILES

Comme le serveur a besoin d'accéder physiquement aux fichiers nous allons devoir utiliser un DIRECTORY.

Dans cet exemple nous allons utiliser notre DIRECTORY blobdir.

Nous allons ensuite créer notre table qui nous servira à contenir nos pointeurs de type BFILE.

CREATE TABLE t_bfile (id number, filename bfile);

Ensuite voici la commande qui va permettre de rajouter une ligne avec un pointeur vers un fichier toto.txt (contenant hello world).

INSERT INTO t_bfile VALUES (1, bfilename('BLOBDIR', 'toto.txt'));
COMMIT;

Nous venons donc d'insérer une nouvelle ligne contenant un pointeur vers notre fichier. Attention : Il n'est pas possible d'afficher le contenu de notre colonne BFILE sous SQL*plus à l'aide d'une commande SELECT. Le seul moyen est d'utiliser une fonction PL/SQL qui nous permettra d'aller lire le contenu du ficher et de nous ramener les 4000 premiers caractères (à cause de la limitation de SQL).

Voici cette fonction

CREATE OR REPLACE FUNCTION blob_to_char(p_file IN BFILE) RETURN VARCHAR2 AS
v_raw   RAW(4000);
v_bfile BFILE DEFAULT p_file;
BEGIN
-- On ouvre notre fichier désigné par notre pointeur
dbms_lob.fileopen(v_bfile);
-- On récupère les 4000 premiers caractères
v_raw := dbms_lob.substr(v_bfile, 4000, 1);
-- On fermer notre fichier
dbms_lob.fileclose(v_bfile);
-- On converti notre buffer en VARCHAR2
RETURN utl_raw.cast_to_varchar2(v_raw);
END;
/

Ensuite nous pourrons donc afficher le contenu de notre fichier grâce à une requête SQL.

SQL> SELECT blob_to_char(filename)
2  FROM t_bfile
3  WHERE id=1;

BLOB_TO_CHAR(FILENAME)
--------------------------------------------------------------------------

hello world

Cet exemple était basé sur un fichier texte, nous allons maintenant refaire un essai avec une image et nous tenterons de l'afficher sur une page web.

Placez une image dans le répertoire de notre DIRECTORY (dans mon exemple helyos.bmp)

On ajoute le pointeur à notre table

INSERT INTO t_bfile VALUES (2, bfilename('BLOBDIR', 'helyos.bmp'));
COMMIT;

Si nous retentons d'afficher le contenu de notre BFILE à l'aide d'une requête nous arrivons à un affichage binaire du contenu de notre image. C'est donc illisible.

IV-C - Comment faire pour afficher le contenu d'un BFILE sur une page web

Pour cela nous allons utiliser une petite procédure qui va se charger d'aller lire le contenu du fichier et de l'afficher. Il n'y a que très peut de différence entre cette procédure et la procédure qui affichait le contenu d'un BLOB.

CREATE OR REPLACE PROCEDURE display_bfile(p_id NUMBER) IS
v_amt   NUMBER DEFAULT 4096;
v_off   NUMBER DEFAULT 1;
v_raw   RAW(4096);
v_bfile BFILE;
BEGIN
-- On récupère le pointeur vers le fichier
SELECT filename INTO v_bfile FROM t_bfile WHERE id = p_id;
-- On ouvre le fichier
dbms_lob.fileopen(v_bfile);
-- On défini de manière arbitraire un mime/type
owa_util.mime_header('image/gif');
-- On affiche le contenu de notre fichier
BEGIN
LOOP
dbms_lob.READ(v_bfile, v_amt, v_off, v_raw);
htp.print(utl_raw.cast_to_varchar2(v_raw));
v_off := v_off + v_amt;
v_amt := 4096;
END LOOP;
EXCEPTION
WHEN no_data_found THEN
NULL;
END;
-- On ferme notre fichier
dbms_lob.fileclose(v_bfile);
END;
/

Ensuite il suffira d'appeler l'url :
http://127.0.0.1/pls/test/scott.display_bfile?p_id=2 pour afficher le contenu de notre BFILE

V - Annexes

V-A - Description des procédures et fonctions utilisées

Pour pouvoir consulter ces documents vous devez avoir un compte sur OTN (Oracle Technology Network) valide.
Vous pouvez en créer un gratuitement en suivant ce lien

DBMS_LOB
Un article sur le PL/SQL avec un chapitre sur le DBMS_LOB par SheikYerbouti
http://download-west.oracle.com/docs/cd/A91202_01/901_doc/appdev.901/a89852/dbms_lob.htm

WRITE
Cette procédure sert à écrire le contenu d'un buffer dans un LOB à un emplacement donné
http://download-west.oracle.com/docs/cd/A91202_01/901_doc/appdev.901/a89852/dbms_25b.htm#1009075

LOADFROMFILE
Cette procédure sert à lire le contenu d'un BFILE dans un LOB
http://download-west.oracle.com/docs/cd/A91202_01/901_doc/appdev.901/a89852/dbms_20b.htm#1009007

FILEOPEN
Cette procédure sert à ouvrir un fichier donné.
http://download-west.oracle.com/docs/cd/A91202_01/901_doc/appdev.901/a89852/dbms_13b.htm#1009025

FILECLOSE
Cette procédure sert à fermer un fichier donné.
http://download-west.oracle.com/docs/cd/A91202_01/901_doc/appdev.901/a89852/dbms_lo8.htm#1009038

GETCHUNKSIZE
Cette fonction renvoie l'espace utilisé dans le LOB Chunk pour stocker le LOB
http://download-west.oracle.com/docs/cd/A91202_01/901_doc/appdev.901/a89852/dbms_15b.htm#1008992

GETLENGTH
Cette fonction renvoie la taille d'un LOB
http://download-west.oracle.com/docs/cd/A91202_01/901_doc/appdev.901/a89852/dbms_16b.htm#1008995

READ
Cette procédure sert à lire le contenu d'un LOB en partant d'un offset spécifié
http://download-west.oracle.com/docs/cd/A91202_01/901_doc/appdev.901/a89852/dbms_22b.htm#1009010

ISOPEN
Cette fonction renvoie TRUE si le LOB est bien ouvert actuellement
http://download-west.oracle.com/docs/cd/A91202_01/901_doc/appdev.901/a89852/dbms_18b.htm#1009004

CLOSE
Cette procédure sert à fermer l'accès à un LOB précédemment ouvert.
http://download-west.oracle.com/docs/cd/A91202_01/901_doc/appdev.901/a89852/dbms_lo3.htm#1009050

OPEN
Cette procédure sert à ouvrir un LOB dans un mode donnée.
http://download-west.oracle.com/docs/cd/A91202_01/901_doc/appdev.901/a89852/dbms_21b.htm#1009016

SUBSTR
Cette fonction permet de renvoyer une partie définie d'un LOB
http://download-west.oracle.com/docs/cd/A91202_01/901_doc/appdev.901/a89852/dbms_23b.htm#1009072

UTL_FILE
http://download-west.oracle.com/docs/cd/A91202_01/901_doc/appdev.901/a89852/utl_file.htm

FOPEN
Cette fonction sert à ouvrir un fichier dans un emplacement donné.
http://download-west.oracle.com/docs/cd/A91202_01/901_doc/appdev.901/a89852/utl_fil3.htm#1002215

PUT
Cette procédure sert à écrire le contenu d'un buffer dans le fichier ouvert.
http://download-west.oracle.com/docs/cd/A91202_01/901_doc/appdev.901/a89852/utl_fi11.htm#1001590

FFLUSH
Cette procédure écris physiquement toutes les sorties en attente dans un fichier.
http://download-west.oracle.com/docs/cd/A91202_01/901_doc/appdev.901/a89852/utl_fi18.htm#1000726

FCLOSE
Cette procédure ferme un fichier donné.
http://download-west.oracle.com/docs/cd/A91202_01/901_doc/appdev.901/a89852/utl_fil7.htm#1000741

UTL_RAW
http://download-west.oracle.com/docs/cd/A91202_01/901_doc/appdev.901/a89852/utl_raw.htm

CAST_TO_VARCHAR2
Cette fonction convertie une variable de type RAW en type VARCHAR2
http://download-west.oracle.com/docs/cd/A91202_01/901_doc/appdev.901/a89852/utl_raw7.htm#1000586

BFILENAME
Cette fonction sert à construire un pointeur associé à un fichier physique
http://download-west.oracle.com/docs/cd/A91202_01/901_doc/server.901/a90125/functions12.htm#SQLRF00610

EMPTY_BLOB
Cette fonction renvoie un pointeur vers un LOB vide qui peut être utilisé pour initialiser une variable ou autre.
http://download-west.oracle.com/docs/cd/A91202_01/901_doc/server.901/a90125/functions38.htm#SQLRF00636

V-B - Références

Tahiti.oracle.com (non pas le lieu de vacances )
Asktom.oracle.com
http://download-west.oracle.com/docs/cd/A97329_03/web.902/a90855/feature.htm#1005870(configuration mod_plsql)
Et retrouvez d'autres informations sur le PL/SQL dans l'article suivant
https://sheikyerbouti.developpez.com/pl_sql/

VI - Remerciements

Merci à Pomalaix,SheikYerbouti et Demco pour leurs avis et suggestions de correction ainsi qu'à Gaël pour la mise en forme de mon article.