I am trying to read blob (image) from an oracle db and display it in html. The image is larger than the buffer size so I have to split it first and then append all the sub-strings. My approach is as below (there will be a loop to go through the blob):
SELECT utl_raw.cast_to_varchar2(dbms_lob.substr(FILE_CONTENTS,2000,1)) as mystring from doc where file_name='test.png'
The problem is that the converted string looks scrambled
I did not specify the char_set for converting, could that be the reason? If so, how can I know which one to use?
Thanks.
Here is a function to convert a BLOB into a Base64 string:
FUNCTION EncodeBASE64(InBlob IN BLOB) RETURN CLOB IS
BlobLen INTEGER := DBMS_LOB.GETLENGTH(InBlob);
read_offset INTEGER := 1;
amount INTEGER := 1440; -- must be a whole multiple of 3
-- size of a whole multiple of 48 is beneficial to get NEW_LINE after each 64 characters
buffer RAW(1440);
res CLOB := EMPTY_CLOB();
BEGIN
IF InBlob IS NULL OR NVL(BlobLen, 0) = 0 THEN
RETURN NULL;
ELSIF BlobLen <= 24000 THEN
RETURN UTL_RAW.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_ENCODE(InBlob));
ELSE
-- UTL_ENCODE.BASE64_ENCODE is limited to 32k, process in chunks if bigger
LOOP
EXIT WHEN read_offset >= BlobLen;
DBMS_LOB.READ(InBlob, amount, read_offset, buffer);
res := res || UTL_RAW.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_ENCODE(buffer));
read_offset := read_offset + amount;
END LOOP;
END IF;
RETURN res;
END EncodeBASE64;
Related
-- this function aim to decode base64 blob to the original value, even thogh I am using a buffer of multiple of 4 bytes or (24 bits) I still get unvalid results.
FUNCTION base64_blob_decode(p_blob BLOB)
RETURN BLOB
IS
v_result_blob BLOB;
v_blob BLOB;
v_temp_blob BLOB;
v_buff binary_integer;
v_raw_buff raw(32676);
v_blob_size INTEGER;
v_start_pos INTEGER := 1;
v_base64_buff INTEGER;
BEGIN
-- preparing the temporary lob and calculating the blob size
v_blob_size := LENGTH(p_blob);
dbms_output.put_line('v_blob_size'||v_blob_size);
dbms_lob.createtemporary(v_blob,false);
dbms_lob.createtemporary(v_temp_blob,false);
v_blob := p_blob;
-- if the blob size is bigger than zero loop to decode it
IF LENGTH(p_blob ) > 0 THEN
WHILE (v_blob_size > 0)
LOOP
-- this part is to adjust the buffer size to the size of the last part
-- the buffer size is multiple of 4
IF v_blob_size < 31992 AND v_blob_size > 0 THEN
v_buff := v_blob_size;
ELSE
v_buff := 31992;
END IF;
read the buffer size in raw
v_raw_buff := DBMS_LOB.SUBSTR (v_blob , v_buff,v_start_pos);
v_raw_buff := UTL_ENCODE.BASE64_DECODE(v_raw_buff);
dbms_lob.writeappend(v_temp_blob, v_base64_buff, v_raw_buff );
v_start_pos :=v_start_pos +v_buff;
v_blob_size := v_blob_size - v_buff;
END LOOP;
-- return the result blob
v_result_blob := v_temp_blob;
-- close the temporary blobs
dbms_lob.freetemporary(v_temp_blob);
dbms_lob.freetemporary(v_blob);
END IF;
RETURN v_result_blob;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('base64_blob_decode- SQLERRM:'||SQLERRM);
RETURN NULL;
END base64_blob_decode;
Try to change this:
dbms_lob.writeappend(v_temp_blob, v_base64_buff, v_raw_buff );
TO
dbms_lob.writeappend(lob_loc => v_temp_blob,
amount => v_blob_size/4,
buffer => v_raw_buff);
Here is the code:
declare
p_arr dbms_sql.Number_Table;
i pls_integer;
procedure do_sort(p_arr in out dbms_sql.Number_Table, p_asc in boolean default null, p_nulls_last in boolean default null) is
x pls_integer;
p_temp number;
begin
for i in 1..p_arr.COUNT-1
loop
for x in 2..p_arr.COUNT
loop
if p_arr(x) < p_arr(x-1)
then
p_temp := p_arr(x-1);
p_arr(x-1) := p_arr(x);
p_arr(x) := p_temp;
end if;
end loop;
end loop;
return;
end;
begin
p_arr(-1) := 0;
p_arr(0) := -2;
p_arr(1) := 10.1;
p_arr(2) := null;
p_arr(3) := 10.1;
p_arr(4) := -1;
do_sort(p_arr);
i := p_arr.first;
while i is not null loop
dbms_output.put_line('arr('||i||') = '||nvl(to_char(p_arr(i)), 'null')||';');
i := p_arr.next(i);
end loop;
end;
It gives me an error on line 12 - "No data found".
Respectively, the procedure "do_sort" on line 29 also fails.
Seems like the problem with nested loop, which I can't figure out for now.
When there is only "first-level" loop with some code in it, such as assigning new values to collection - it performs well.
Sorting block outside of procedure body also works.
Thanks in advance.
You're filling your number table with specific indexes:
p_arr(-1) := 0;
p_arr(0) := -2;
p_arr(1) := 10.1;
p_arr(2) := null;
p_arr(3) := 10.1;
p_arr(4) := -1;
But when you loop you are using index values from 1 to the count of elements, which is 6. There is no element with index 5 or 6, and when you try to refer to p_arr(5) there is no such element - hence the error. And you miss out those with indexes -1 and 0.
It works if you re-index your initial values:
p_arr(1) := 0;
p_arr(2) := -2;
p_arr(3) := 10.1;
p_arr(4) := null;
p_arr(5) := 10.1;
p_arr(6) := -1;
which then gets output:
arr(1) = -2;
arr(2) = 0;
arr(3) = 10.1;
arr(4) = null;
arr(5) = -1;
arr(6) = 10.1;
PL/SQL procedure successfully completed.
Notice what happens with your null value though... you cannot compare null with anything else, so p_arr(x) < p_arr(x-1) is undefined (but not true) when the null element is evaluated on both sides of that comparison. So, it doesn't move. You would need to decide where you want nulls to end up to determine how to modify the code to achieve that.
If you have a specific reason for starting your indexing at -1 instead of 1, you could still do that, and change the references inside your loop to be p_arr(x+2) etc., but it would be more confusing and error-prone. Or you coudl change your loop ranges to handle that instead:
for i in -1..p_arr.COUNT - 1 -- 2 less than previously, on each end of range
loop
for x in 0..p_arr.COUNT - 2 -- 2 less than previously, on each end of range
loop
... which gets the same result, using your original table population starting from index -1. Oracle Live SQL demo.
The indexes used inside the loops have to align with the indexes you use to populate the table, however you do it.
Colleagues, hello. I need to call SOAP web service from Oracle DB where payload consists of file in base64 encoding. The problem is that i have limitation in payload size - 32 000 chars (varchar type). Examples, here and here. So it means that i can not call web service where payload is quite huge. Have anybody examples where payload increases the limitation of varchar2 type. Than you.
To go over the 32k characters limitation, you can try to use a CLOB with utl_http. Here is a procedure to do it :
PROCEDURE write_clob_to_http_request(pc_clob IN OUT NOCOPY CLOB)
IS
ln_offset NUMBER := 1;
ln_i NUMBER := 1;
ln_amount NUMBER := 32767;
ln_clob_length NUMBER := dbms_lob.getlength(pc_clob);
lv_buffer VARCHAR2(32767);
luh_http_request utl_http.req;
BEGIN
luh_http_request := utl_http.begin_request('http://SoapServiceLocation' , 'POST', 'HTTP/1.1');
utl_http.set_header(luh_http_request, 'Content-Type', lv_http_content_type);
utl_http.set_header(luh_http_request, 'Content-Length', LENGTH(pc_content));
utl_http.set_header(luh_http_request, 'SOAPAction', 'http://SoapServiceLocation');
IF dbms_lob.isopen(pc_clob) != 1 THEN
dbms_lob.open(pc_clob, 0);
END IF;
WHILE ( ln_offset < ln_clob_length )
LOOP
dbms_lob.read(pc_clob, ln_amount, ln_offset, lv_buffer);
UTL_HTTP.write_text(luh_http_request, lv_buffer);
ln_offset := ln_offset + ln_amount;
ln_i := ln_i + 1;
END LOOP;
IF dbms_lob.isopen(pc_clob) = 1 THEN
dbms_lob.close(pc_clob);
END IF;
luh_http_response := utl_http.get_response(luh_http_request);
utl_http.read_text(luh_http_response, pc_content);
utl_http.end_response(luh_http_response);
END write_clob_to_http_request;
You can easily create a CLOB by concatenating VARCHAR2 variables:
pc_content CLOB := TO_CLOB('a string') || TO_CLOB('another string)...;
Could you tell me, why this function returns as result an empty blob value?
It don't even close the BFILE type variable after the loop. I can't imaging where is the problem.
FUNCTION f$bfile_to_blob
(I_FID_ID IN INTEGER)
RETURN BLOB IS
bf BFILE;
Amount INTEGER := 32767;
Position integer := 1;
buffer RAW(32767);
bl LONG RAW := '';
bb BLOB;
BEGIN
select fid_bckp into bf
from filedoc
where fid_id = I_FID_ID;
dbms_lob.open(bf, dbms_lob.lob_readonly);
DBMS_LOB.CREATETEMPORARY(bb, TRUE, DBMS_LOB.SESSION);
LOOP
dbms_lob.read(bf, Amount, Position, buffer);
dbms_lob.writeappend(bb,amount,buffer);
Position := Position + Amount;
END LOOP;
dbms_lob.close(bf);
return bb;
END;
I call the function this way
select F$BFILE_TO_BLOB(fid_id) from filedoc where fid_id = 2150;
Your logic is complex, you're mixing many elements that can produce errors: namely CLOB and BFILE. You should try first to isolate which element in your function leads to this abnormal behaviour.
I suggest you run a simple function to make sure that your BLOB logic is fine:
create or replace FUNCTION f$bfile_to_blob
RETURN BLOB IS
bb BLOB;
BEGIN
DBMS_LOB.CREATETEMPORARY(bb, TRUE, DBMS_LOB.SESSION);
bb := hextoraw('FFFFFFFF');
return bb;
END;
select rawtohex(dbms_lob.substr(f$bfile_to_blob, 4000, 1)) from dual;
This should produce FFFFFFFF. Now you know that the problem lies with your reading of the BFILE object. We can see in your code that you have a loop withoug an exit condition. Since the program exits, we deduct that your loop exits with an error.
The documentation explains that the exception is raised by the READ procedure:
The number of bytes or characters actually read is returned in the amount parameter. If the input offset points past the End of LOB, then amount is set to 0, and a NO_DATA_FOUND exception is raised.
The exception is raised and the function exits without having return anything.
As you may know, NO_DATA_FOUND exceptions are not considered errors inside an SQL statement. The error is interpreted as NULL by the SELECT query.
You should modify your function to catch this error and exit the loop gracefully:
FUNCTION f$bfile_to_blob(I_FID_ID IN INTEGER)
RETURN BLOB IS
bf BFILE;
Amount INTEGER := 32767;
Position INTEGER := 1;
buffer RAW(32767);
bl LONG RAW := '';
bb BLOB;
BEGIN
SELECT fid_bckp
INTO bf
FROM filedoc
WHERE fid_id = I_FID_ID;
dbms_lob.open(bf, dbms_lob.lob_readonly);
DBMS_LOB.CREATETEMPORARY(bb, TRUE, DBMS_LOB.SESSION);
LOOP
BEGIN
dbms_lob.read(bf, Amount, Position, buffer);
EXCEPTION
WHEN NO_DATA_FOUND THEN
EXIT;
END;
dbms_lob.writeappend(bb, amount, buffer);
Position := Position + Amount;
END LOOP;
dbms_lob.close(bf);
RETURN bb;
END;
I have been stuck with this issue now all morning. I actually saw this code here and decide to use it for our purpose here.
The issue that I am running into is that when we execute the code, sometimes it writes a file from db to the folder.
Other times, we get "numeric or value error"
Can any expert please help me fix it?
Here is the code I am using:
create or replace
PROCEDURE getfile(pfname VARCHAR2, display_name IN VARCHAR2)
IS
vblob BLOB;
vstart NUMBER := 1;
bytelen NUMBER := 32000;
len NUMBER;
my_vr RAW(32000);
x NUMBER;
v_name VARCHAR2(32760);
lv_str_len NUMBER;
l_output utl_file.file_type;
BEGIN
-- define output directory
--lv_str_len := Length(pfname);
--v_name := display_name||upper(substr(pfname,lv_str_len-3,lv_str_len));
v_name := display_name;
l_output := utl_file.Fopen('My_DIR', v_name, 'w', 32760);
-- get length of blob
SELECT dbms_lob.Getlength(FILENAME)
INTO len
FROM GENERAL.GUBFILE
WHERE gubfile_name = pfname;
-- dbms_output.put_line('Length: '||len);
-- save blob length
x := len;
-- select blob into variable
SELECT BLOBVALUE
INTO vblob
FROM FILES
WHERE filename = pfname;
-- if small enough for a single write
IF len < 32760 THEN
-- dbms_output.put_line('Single write ');
utl_file.Put_raw(l_output, vblob);
utl_file.Fflush(l_output);
ELSE -- write in pieces
-- dbms_output.put_line('multi write '||vstart);
vstart := 1;
WHILE vstart < len LOOP
dbms_lob.READ(vblob, bytelen, vstart, my_vr);
utl_file.Put_raw(l_output, my_vr);
utl_file.Fflush(l_output);
-- set the start position for the next cut
vstart := vstart + bytelen;
-- set the end position if less than 32000 bytes
x := x - bytelen;
IF x < 32000 THEN
bytelen := x;
END IF;
END LOOP;
END IF;
dbms_output.Put_line('End');
utl_file.Fclose(l_output);
END getfile;
The exact error is:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "USER.GETFILE", line 40
ORA-06512: at line 8
The error comes from utl_file.put_raw.
The maximum size of the buffer parameter is 32767 bytes.
You check for IF len < 32760 THEN, however, I see no guarantee in your code, that the len variable actually holds the length of the vblob variable that is the buffer in the put_raw call.
So I suppose the vblob variable's actual length is longer then 32767 and that is the reason for the error.
Hence I suggest to delete this piece of code:
IF len < 32760 THEN
-- dbms_output.put_line('Single write ');
utl_file.Put_raw(l_output, vblob);
utl_file.Fflush(l_output);
ELSE
also the END IF; of course, and always go for the 'write in pieces' branch.
I see now, you've done this based on the Burleson example which is good http://www.dba-oracle.com/t_writing_blob_clob_os_file.htm
but you see, unlike you, Burleson gets the len variable and the vblob variable from the same table and the same field.
-- get length of blob
SELECT dbms_lob.getlength(productblob)
INTO len
FROM products
WHERE id = product_id;
-- save blob length
x := len;
-- select blob into variable
SELECT product_blob
INTO vblob
FROM products
WHERE id = product_id;
EDIT
So an other option would be to fix the select for getting length. This means you'll have to replace this select:
-- get length of blob
SELECT dbms_lob.Getlength(FILENAME)
INTO len
FROM GENERAL.GUBFILE
WHERE gubfile_name = pfname;
with this:
-- get length of blob
SELECT dbms_lob.Getlength(BLOBVALUE)
INTO len
FROM FILES
WHERE filename = pfname;