在oracle 8i的sql*plus中如何利用lob字段存取操作系统二进制文件
广东省岭澳核电有限公司工程控制处管理信息科(518124) 黄福同
oracle 8i数据库系统功能比前面版本更加完善,尤其是出现了blob,clob,nclob,
bfile这些lob(大型对象)类型来取代功能有限的long、longraw类型。blob字段最
大长度为4g(4,294,967,295)字节,而且不再象longraw那样每个表中只是限制有一
个字段是longraw(最长2g)型的。blob,clob,nclob为内部blob(数据通常在数据
库中存放),bfile为外部lob(所存储的只是指向外部操作系统文件的指针),用户可
以使用pl/sql的包dbms_lob来处理lob数据,但是遗憾的是,dbms_lob包只能将二进
制操作系统文件写入到blob字段中,却无法将blob字段中的二进制操作系统文件取回
到操作系统中,估计将来会有所改善。本文将就如何在sql*plus将word文件存入取出
oracle中作详细解释说明,供各位同行参考。
实验的软件环境如下:windows 2000 advanced server,oracle 8.1.7,vc++6.0+sp5
硬件环境如下:双piii866 cpu,768m内存
在internal这个用户下给scott用户授权如下:
sql>grant create any directory to scott;
sql>grant create any library to scott;
在scott这个用户下执行下述语句:
sql>create table bfile_tab (bfile_column bfile);
sql>create table utl_lob_test (blob_column blob);
sql>create or replace directory utllobdir as 'c:/dds/extproc';
sql>set serveroutput on
然后执行下面语句就将c:/dds/extproc目录下的word文件com.doc存入到utl_lob_test
表中的blob_column字段中了。
declare
a_blob blob;
a_bfile bfile := bfilename('utllobdir','com.doc'); --用来指向外部操作系统
文件
begin
insert into bfile_tab values (a_bfile)
returning bfile_column into a_bfile;
insert into utl_lob_test values (empty_blob())
returning blob_column into a_blob;
dbms_lob.fileopen(a_bfile);
dbms_lob.loadfromfile(a_blob, a_bfile, dbms_lob.getlength(a_bfile));
dbms_lob.fileclose(a_bfile);
commit;
end;
/
sql>show errors
此时可以使用dbms_lob包的getlength这个procedure来检测是否已经将该word文件存入
到blob字段中了。如:
sql> select dbms_lob.getlength(blob_column) from utl_lob_test;
结果如下:
dbms_lob.getlength(blob_column)
-------------------------------
83968
说明该word文件已经存入到blob字段中去了。
下面将就如何取出该word文件到操作系统下作详细解释:
oracle8.1.7只能用pro*c与oci来实现该任务,所以oracle服务器端必须支持pro*c
以及外部library,oracle8.1.7数据库默认安装为支持pro*c以及外部procedure,
用户可以自己检查一下listener.ora 和 tnsnames.ora这两个文件。
listener.ora中包含如下语句:
sid_list_listener =
(sid_list =
(sid_desc =
(sid_name = plsextproc)
(oracle_home = d:/oracle/ora81)
(program = extproc)
)
(sid_desc =
(global_dbname = hft)
(oracle_home = d:/oracle/ora81)
(sid_name = hft)
)
)
tnsnames.ora中包含如下语句:
extproc_connection_data =
(description =
(address_list =
(address = (protocol = ipc)(key = extproc0))
)
(connect_data =
(sid = plsextproc)
(presentation = ro)
)
)
下面这个文件为lob2file.c,具体作用是将blob中的二进制文件倒出到操作系统中。
/*begin of lob2file.c*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <oci.h>
#include <ociextp.h>
#define default_chunk_size 1024
static int logging;
static char logfile[512];
static file *logfilep = null;
int lob2file ( ociloblocator *a_lob, /* the lob */
short lbind, /* lob indicator */
char *path, /* file to write */
short pind, /* file indicator */
int plen, /* filename length */
char *lpath, /* logfile name */
short lpind, /* logfile indicator */
int lplen, /* logfile name length */
int logit, /* logging enabled? */
ociextproccontext *ctxt /* oci context */
)
{
sword errnum = 0;
ocienv *envhp = null;
ocisvcctx *svchp = null;
ocierror *errhp = null;
char lobfile[512];
file *lobfilep = null;
/*
* if required, open the log file for writing
* use the user provided logfile name if possible
* otherwise, default the logfile to lob2file.log
*/
logging = logit;
if (logging)
{
if (lpind == -1 || lplen == 0 || lplen >= 512)
{
strcpy(logfile, "lob2file.log");
}
else
{
strncpy(logfile, lpath, lplen);
logfile[lplen] = '/0';
}
logfilep = fopen(logfile, "w");
if (logfilep == null)
{
if ((logfilep = fopen("lob2file.log", "w")) != null)
{
fprintf(logfilep, "error: unable to open logfile %s/n",
logfile);
fprintf(logfilep, "error: errno = %d/n", errno);
}
}
}
/*
* retrieve the environment, service context, and error handles
*/
if ((errnum = ociextprocgetenv(ctxt, &envhp,
&svchp, &errhp)) != ociextproc_success)
{
if (logging && logfilep != null)
{
fprintf(logfilep, "error: call to ociextprocgetenv failed/n");
fprintf(logfilep, "error: ociextprocgetenv returned %d/n",
errnum);
fclose(logfilep);
return -1;
}
}
/*
* verify that the user has provided a name for the output file
*/
if (pind == -1 || plen == 0)
{
char *errmsg = "pathname is null or empty string";
if (logging && logfilep != null)
{
fprintf(logfilep, "error: %s/n", errmsg);
fclose(logfilep);
}
errnum = 20001;
ociextprocraiseexcpwithmsg(ctxt, errnum, (text *)errmsg,
strlen(errmsg));
return -1;
}
else /* use the provided name */
{
strncpy(lobfile, path, plen);
lobfile[plen] = '/0';
}
/*
* verify that the user has provided a valid lob locator
*/
if (lbind == -1)
{
char *errmsg = "lob locator is null";
if (logging && logfilep != null)
{
fprintf(logfilep, "error: %s/n", errmsg);
fclose(logfilep);
}
errnum = 20002;
ociextprocraiseexcpwithmsg(ctxt, errnum, (text *)errmsg,
strlen(errmsg));
return -1;
}
if (logging && logfilep != null)
fprintf(logfilep, "opening os file in write mode/n");
/*
* open the output file for writing
*/
if ((lobfilep = fopen(lobfile, "wb")) != null)
{
dvoid *chunk;
ub4 cksz = 0, totsz = 0;
if (logging && logfilep != null)
fprintf(logfilep, "getting total size for lob/n");
if (checkerr(ctxt, errhp,
ocilobgetlength(svchp, errhp, a_lob, &totsz)) != 0)
return -1;
/*
* for 8.0.x the ociloggetchunksize will not have been called.
* in this case, reset the chunk size to 1k.
*/
if (cksz == 0) cksz = default_chunk_size;
if (logging && logfilep != null)
fprintf(logfilep,
"allocating %d bytes of memory for lob chunks/n",
(int) cksz );
/*
* dynamically allocate enough memory to hold a single chunk
*/
if ((chunk = ociextprocalloccallmemory(ctxt, (size_t) cksz)) != null)
{
int cnt = 1;
ub4 amt = cksz, offset = 1;
/*
* read data from the lob and write it to the file while
* more data remains.
*/
while (offset < (int)totsz)
{
if (logging && logfilep != null)
fprintf(logfilep,
"reading chunk %d starting at %d for max %d
bytes/n",
cnt, (int) offset, (int) amt);
errnum = ocilobread(svchp, errhp, a_lob, &amt, offset,
chunk, cksz, (dvoid *) 0,
(sb4 (*)(dvoid *, dvoid *, ub4, ub1)) 0,
(ub2) 0, (ub1)sqlcs_implicit);
if (checkerr(ctxt, errhp, errnum) != 0) return -1;
if (logging && logfilep != null)
fprintf(logfilep,
"successfully read chunk containing %d bytes/n",
(int) amt);
if (logging && logfilep != null)
fprintf(logfilep,
"writing %d bytes of chunk %d to file %s/n",
(int) amt, cnt, lobfile);
if (fwrite((void *)chunk, (size_t)1, (size_t)amt, lobfilep) ==
amt)
{
if (logging && logfilep != null)
fprintf(logfilep, "successfully wrote %d bytes to file
%s/n",
(int) amt, lobfile);
}
else
{
char *errmsg = "write to os file failed";
if (logging && logfilep != null)
{
fprintf(logfilep, "error: %s/n", errmsg);
fprintf(logfilep, "error: errno = %d/n", errno);
}
errnum = 20003;
ociextprocraiseexcpwithmsg(ctxt, errnum,
(text *)errmsg, strlen(errmsg));
return -1;
}
cnt++;
offset += amt;
}
if (logfilep != null) fclose(logfilep);
fclose(lobfilep);
return 0;
}
else
{
if (logging && logfilep != null)
{
fprintf(logfilep, "error: unable to allocate memory/n");
fclose(logfilep);
}
return -1;
}
}
else
{
char *errmsg = "unable to open file";
if (logging && logfilep != null)
{
fprintf(logfilep, "error: %s %s/n", errmsg, lobfile);
fprintf(logfilep, "error: errno = %d/n", errno);
fclose(logfilep);
}
errnum = 20003;
ociextprocraiseexcpwithmsg(ctxt, errnum,
(text *)errmsg, strlen(errmsg));
return -1;
}
}
int checkerr(ociextproccontext *ctxt, ocierror *errhp, sword status)
{
sword errnum = 0;
text errbuf[512];
switch (status)
{
case oci_success_with_info:
errnum = 20004;
strcpy((char *)errbuf, "error: oci_success_with_info");
break;
case oci_no_data:
errnum = 20005;
strcpy((char *)errbuf, "error: oci_no_data");
break;
case oci_need_data:
errnum = 20006;
strcpy((char *)errbuf, "error: oci_need_data");
break;
case oci_invalid_handle:
errnum = 20007;
strcpy((char *)errbuf, "error: oci_invalid_handle");
break;
case oci_still_executing:
errnum = 20008;
strcpy((char *)errbuf, "error: oci_still_executing");
break;
case oci_continue:
errnum = 20009;
strcpy((char *)errbuf, "error: oci_continue");
break;
case oci_error:
(void)ocierrorget((dvoid *) errhp, (ub4) 1, (text *) null,
(sb4 *) &errnum, (text *) errbuf,
(ub4) sizeof(errbuf), oci_htype_error);
break;
default:
break;
}
if (errnum != 0)
{
if (logging && logfilep != null)
{
fprintf(logfilep, "error: %d %s/n", errnum, errbuf);
fclose(logfilep);
}
(void)ociextprocraiseexcpwithmsg(ctxt, errnum, errbuf,
strlen(errbuf));
}
return errnum;
}
/*end of file lob2file.c*/
将文件lob2file.c放到d:/oracle/ora81/plsql/demo目录下:然后在dos下执行下述编
译
语句将该文件编译成lob2file.dll文件,make.bat文件包含如下:
@echo off
cl -id:/oracle/ora81/oci/include -d_dll -d_mt /ld -zi lob2file.c /link
d:/oracle/ora81/oci/lib/msvc/oci.lib msvcrt.lib /nod:libcmt /dll
/export:lob2file /export:checkerr
进入d:/oracle/ora81/plsql/demo目录(dos状态)执行make就可以将lob2file.c编
译成lob2file.dll文件了。
然后用scott连到sql*plus中,执行
sql>create or replace library utlloblib
as 'd:/oracle/ora81/plsql/demo/lob2file.dll'
/
sql>grant execute on utlloblib to public
然后执行下述代码:
create or replace package utl_lob is
procedure setlogging(which boolean, a_log varchar2);
procedure unloadtofile(a_lob blob, a_file varchar2, status out number);
end utl_lob;
/
show errors
create or replace package body utl_lob is
logsetting boolean := false;
logfilename varchar2(512) := null;
procedure setlogging(which boolean, a_log varchar2) is
begin
logsetting := which;
if (logsetting = true) then
logfilename := a_log;
else
logfilename := null;
end if;
end;
function lobtofile(a_lob blob, a_file varchar2,a_log varchar2, logging
boolean)
return binary_integer as external
name "lob2file"
library utlloblib
language c
with context
parameters ( a_lob ociloblocator,
a_lob indicator short,
a_file string,
a_file indicator short,
a_file length int,
a_log string,
a_log indicator short,
a_log length int,
logging int,
context,
return );
procedure unloadtofile(a_lob blob, a_file varchar2, status out number) is
begin
status := lobtofile(a_lob, a_file, logfilename, logsetting);
end;
end utl_lob;
/
show errors
grant execute on utl_lob to public;
该代码创建package utl_lob,而utl_lob调用library utlloblib,我们的测试程序调
用
package utl_lob中的procedure setlogging和unloadtofile。在scott用户下执行如下
脚本,就可以将先前保存的com.doc取出放到c:/dds/extproc/test.doc这个文件中,当
然
c:/dds/extproc这个目录必须存在。脚本执行完毕后生成两个文件test.log与test.doc
,
test.log纪录了取出的详细信息,test.doc是com.doc的复制品,取出82k大小的文件大
约用了4秒。
--以下为测试脚本
set serveroutput on
declare
a_blob blob;
status number;
begin
select blob_column into a_blob from utl_lob_test;
utl_lob.setlogging(true, 'c:/dds/extproc/test.log');
utl_lob.unloadtofile(a_blob, 'c:/dds/extproc/test.doc', status);
dbms_output.put_line('exit status = ' || status);
end;
/
大家对上面测试脚本稍微改动一下,形成一个带参数的procedure供应用程序调用就可
以了。