I have a large BLOB object which I need to convert to CLOB in base64 with encoded characters, I have tried to use utl_encode.base64_encode but it does not escape special characters like ={}+#~ etc. does anyone know how to escape those special characters with utl_url.escape? my code that I have:
PROCEDURE base64encode ( i_blob in blob, io_clob in out nocopy clob) IS
l_step pls_integer := 22500;
l_converted VARCHAR2(32767);
l_buffer_size_approx pls_integer := 1048576;
l_buffer CLOB;
BEGIN
dbms_lob.createtemporary(l_buffer, TRUE, dbms_lob.call);
FOR i IN 0 .. trunc((dbms_lob.getlength(i_blob) - 1 )/l_step)
LOOP
l_converted := utl_raw.cast_to_varchar2(utl_encode.base64_encode(dbms_lob.substr(i_blob, l_step, i * l_step + 1)));
dbms_lob.writeappend(l_buffer, length(l_converted), l_converted);
IF dbms_lob.getlength(l_buffer) >= l_buffer_size_approx THEN
dbms_lob.append(io_clob, l_buffer);
dbms_lob.trim(l_buffer, 0);
END IF;
END LOOP;
dbms_lob.append(io_clob, l_buffer);
dbms_lob.freetemporary(l_buffer);
END base64encode;
and if I try to convert BLOB to base64 it gives me output like:
8J5G7Ty8t3Pn7T+9ce/+w/c//nn/Jd1VDTKhadyLoNKDDx/Cl+4/SHN7jFjSVFrj <- not full base64 just single line,
But I need the output like this, with escaped characters:
8J5G7Ty8t3Pn7T%2B9ce%2F%2Bw%2Fc%2F%2Fnn%2FJd1VDTKhadyLoNKDDx%2FCl%2B4%2FSHN7jFjSVFrj
the result is large CLOB text, it does not fit in varchar2 variable.
APEX_UTIL.URL_ENCODE might do the trick.
SELECT APEX_UTIL.URL_ENCODE('8J5G7Ty8t3Pn7T+9ce/+w/c//nn/Jd1VDTKhadyLoNKDDx/Cl+4/SHN7jFjSVFrj')
FROM dual;
Output:
8J5G7Ty8t3Pn7T%2B9ce%2F%2Bw%2Fc%2F%2Fnn%2FJd1VDTKhadyLoNKDDx%2FCl%2B4%2FSHN7jFjSVFrj
So I have came up with a working solution, this code decodes base64 clob and escapes reserved chars. It is a bit slow, but it does the work. first, you need to call base64encode function from my main question, then pass the clob to this function. I hope it will help others too.
FUNCTION encode64_clob(in_clob CLOB) RETURN CLOB IS
temp_chunk VARCHAR(4000);
l_enter NUMBER := 1;
l_old_ent NUMBER := 1;
l_ent_cnt NUMBER := 1;
l_out CLOB;
BEGIN
LOOP
l_enter := instr(in_clob, chr(10), 1, l_ent_cnt);
IF l_enter = 0 THEN
temp_chunk := regexp_replace(substr(in_clob, l_old_ent, length(in_clob)), '[[:space:]]+', '');
l_out := l_out || temp_chunk;
EXIT;
END IF;
temp_chunk := regexp_replace(substr(in_clob, l_old_ent, l_enter - l_old_ent), '[[:space:]]+', '');
temp_chunk := utl_url.escape(url => temp_chunk, escape_reserved_chars => TRUE, url_charset => 'UTF-8');
l_out := l_out || temp_chunk;
l_ent_cnt := l_ent_cnt + 1;
l_old_ent := l_enter;
END LOOP;
RETURN l_out;
END;
Related
Is there any way to return a clob as text (or file) without splitting into smaller pieces first?
I tried creating a GET handler with PL/SQL source that would just do this:
declare
txt clob;
begin
...-- set clob
htp.p(txt);
end;
But then I was getting the error ORA-06502: PL/SQL: numeric or value error.
I then tried cutting the clob in smaller segments and calling htp.p multiple times, which worked, but I was wondering if there was a way to send the whole thing in one go.
The doc states that htp.p and htp.prn take only VARCHAR2 so you're limited by the max size of a varchar2 and if the clob length exceeds that it will throw an error. This is what you can do:
Loop through the clob in 4k chunks and output using htp.prn. Avoid using htp.p in a loop because that generates a newline characters which could mess up the output, for example if you're generating json. It's also good practice to let the browser know what he's getting by setting the mime header.
DECLARE
l_clob CLOB;
l_amt INTEGER := 4000;
l_pos INTEGER := 1;
l_buf VARCHAR2(4000);
BEGIN
owa_util.mime_header ('text/html', true);
l_clob := '....';
LOOP
BEGIN
dbms_lob.read(
l_clob,
l_amt,
l_pos,
l_buf
);
l_pos := l_pos + l_amt;
-- need htp.prn since htp.p generates a newline char at the end.
htp.prn(l_buf);
EXCEPTION
WHEN no_data_found THEN
EXIT;
END;
END LOOP;
END;
I'm going in debug in a procedure PL/SQL on SQL Developer that make a big query and store it in a CLOB. When i take the value of that CLOB, the value is truncated. Can anyone know any method to take a full value of CLOB in debug mode?
This procedure should help you, if I understood correctly you want to print the full value of clob?
PROCEDURE PRINT_CLOB(par_clob IN CLOB) IS
ln_offset NUMBER DEFAULT 1;
BEGIN
LOOP
EXIT WHEN ln_offset > dbms_lob.getlength(par_clob);
dbms_output.put_line(dbms_lob.substr(par_clob, 255, ln_offset));
ln_offset := ln_offset + 255;
END LOOP;
END PRINT_CLOB;
May be this might not be the answer you are looking for but in my application I have created a dummy table with column data type as clob. So whenever I want to get the value I will insert into this temporary table.
Thanks to #gregorianisch answer, I made my own version, which simply takes into account a carriage return to calculate the next chunk, to not cut any message (I needed to print query statements)
PROCEDURE print_clob(
input_string IN CLOB
)
IS
out_string_in CLOB default input_string;
clob_length NUMBER := DBMS_LOB.GETLENGTH(out_string_in);
v_offset NUMBER default 0;
v_chunk_size NUMBER := 32767;
v_substr CLOB;
BEGIN
DBMS_OUTPUT.ENABLE(1000000);
WHILE v_offset < clob_length
LOOP
v_chunk_size := 32767;
v_substr := SUBSTR(out_string_in, v_offset + 1, v_chunk_size);
v_chunk_size := CASE WHEN INSTR(v_substr, CHR(10), -1) > 0 THEN INSTR(v_substr, CHR(10), -1) ELSE 32767 END;
v_substr := SUBSTR(out_string_in, v_offset + 1, v_chunk_size);
DBMS_OUTPUT.PUT_LINE(v_substr);
v_offset := v_offset + v_chunk_size;
END LOOP;
END print_clob;
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)...;
I am using the following function to convert a large base64 encoded file(image or voice) into a blob file and store it in the Oracle database (Oracle Database 11g Enterprise Edition Release 11.1.0.7.0 - Production).
I am able to store it and retrieve it but the image is getting corrupted. Only a potion of the image is getting retrieved. I tried using small images(11KB size) and it is working fine. But for larger images(88KB to 700KB) only a portion of the image is retrieved.
The problem is with the base-64 decoding. Earlier I was not able to get even the smaller image due to corruption, but when I increased the buffer size, it came fine. Now the buffer size is at its maximum at 32767 as its the maximum for varchar2 and raw.
Can anyone provide a suitable workaround or solution.
function decode_base64(p_clob_in in clob) return blob is
v_blob blob;
v_result blob;
v_offset integer;
v_buffer_size binary_integer := 32767; -- 24, 48, 3072
v_buffer_varchar varchar2(32767);
v_buffer_raw raw(32767);
begin
if p_clob_in is null then
return null;
end if;
dbms_lob.createtemporary(v_blob, true);
v_offset := 1;
for i in 1 .. ceil(dbms_lob.getlength(p_clob_in) / v_buffer_size)
loop
dbms_lob.read(p_clob_in, v_buffer_size, v_offset, v_buffer_varchar);
v_buffer_raw := utl_raw.cast_to_raw(v_buffer_varchar);
v_buffer_raw := utl_encode.base64_decode(v_buffer_raw);
dbms_lob.writeappend(v_blob, utl_raw.length(v_buffer_raw), v_buffer_raw);
v_offset := v_offset + v_buffer_size;
end loop;
v_result := v_blob;
dbms_lob.freetemporary(v_blob);
return v_result;
end decode_base64;
The code that i use to call the function and insert the blob into the table is given below...
PROCEDURE create_notes (
p_task_id IN NUMBER
,p_note_title IN VARCHAR2
,p_note_detail IN VARCHAR2
,p_attach_name IN VARCHAR2
,p_attachment IN CLOB
,p_attach_type IN VARCHAR2
,x_return_code OUT VARCHAR2
,x_return_message OUT VARCHAR2
)
IS
l_blob_data BLOB;
BEGIN
.
.
.
IF p_attachment IS NOT NULL THEN
SELECT incident_id INTO l_pk1_value FROM csf_ct_tasks where task_id = p_task_id;
l_blob_data := xx_utl_base64.decode_base64(p_attachment);
INSERT INTO fnd_lobs
(file_id, file_name, file_content_type, upload_date,
expiration_date, program_name, program_tag, file_data,
LANGUAGE, oracle_charset, file_format
)
VALUES (l_media_id, p_attach_name,p_attach_type, -- 'audio/mpeg','application/pdf','image/jpeg'
SYSDATE,
NULL, 'FNDATTCH', NULL, l_blob_data, --l_blob_data,EMPTY_BLOB ()
'US', 'UTF8', 'binary'
)
RETURNING file_data
INTO x_blob;
COMMIT;
END IF:
Attaching the original picture and its decoded version, below.
I got the below code from net. It worked like a charm. Dont know whats the problem with my old code though.
FUNCTION base64decode(p_clob CLOB)
RETURN BLOB
IS
l_blob BLOB;
l_raw RAW(32767);
l_amt NUMBER := 7700;
l_offset NUMBER := 1;
l_temp VARCHAR2(32767);
BEGIN
BEGIN
DBMS_LOB.createtemporary (l_blob, FALSE, DBMS_LOB.CALL);
LOOP
DBMS_LOB.read(p_clob, l_amt, l_offset, l_temp);
l_offset := l_offset + l_amt;
l_raw := UTL_ENCODE.base64_decode(UTL_RAW.cast_to_raw(l_temp));
DBMS_LOB.append (l_blob, TO_BLOB(l_raw));
END LOOP;
EXCEPTION
WHEN NO_DATA_FOUND THEN
NULL;
END;
RETURN l_blob;
END;
I tried your function with a v_buffer_size of 8192 and it worked fine. I've tried several numbers smaller than 32767 and they all worked fine, so try something less than that.
For those who's still looking for a correct solution - you need to decode input data in multiples of 4. In case input contains non-base64 symbols (which are ignored by built-in function utl_encode.base64_decode), it might lead to incorrect results on large files.
I've found a lot of samples on the web which do not correctly decode, posting my code below
FUNCTION base64_decode(p_content CLOB) RETURN BLOB
IS
C_CHUNK_SIZE CONSTANT INTEGER := 12000; -- should be a multiple of 4
C_NON_BASE64_SYM_PATTERN CONSTANT VARCHAR2(20) := '[^A-Za-z0-9+/]';
l_chunk_buf VARCHAR2(12000);
l_chunk_b64_buf RAW(9000);
l_chunk_offset INTEGER := 1;
l_chunk_size INTEGER;
l_res BLOB;
FUNCTION get_next_full_base64_chunk(l_data CLOB, p_cur_pos IN OUT INTEGER, p_desired_size INTEGER, p_cur_size IN OUT INTEGER) RETURN VARCHAR2 IS
l_res VARCHAR2(12000);
l_tail_desired_size INTEGER;
BEGIN
l_res := dbms_lob.substr(l_data, p_desired_size, p_cur_pos);
p_cur_pos := p_cur_pos + p_desired_size;
IF l_res IS NULL THEN
RETURN NULL;
END IF;
l_res := regexp_replace(l_res, C_NON_BASE64_SYM_PATTERN, '');
p_cur_size := p_cur_size + length(l_res);
l_tail_desired_size := 4 - mod(p_cur_size, 4);
IF l_tail_desired_size = 4 THEN
RETURN l_res;
ELSE
RETURN l_res || get_next_full_base64_chunk(l_data, p_cur_pos, l_tail_desired_size, p_cur_size);
END IF;
END;
BEGIN
dbms_lob.createtemporary(l_res, false);
WHILE true
LOOP
l_chunk_size := 0;
l_chunk_buf := get_next_full_base64_chunk(p_content, l_chunk_offset, C_CHUNK_SIZE, l_chunk_size);
EXIT WHEN l_chunk_buf IS NULL;
l_chunk_b64_buf := utl_encode.base64_decode(utl_raw.cast_to_raw(l_chunk_buf));
dbms_lob.writeappend(l_res, utl_raw.length(l_chunk_b64_buf), l_chunk_b64_buf);
END LOOP;
RETURN l_res;
END;
I am working on an Oracle procedure that calls another procedure within it. One of my parameters (parm1) can contain one or more values in a comma separated list. How can I loop through these values to pass them one at a time to another procedure?
Here is an example of what I would like it to do:
When Parm1 = 123,312
callProcedure2(123)
callProcedure2(321)
-or-
When Parm1 123
callProcedure2(123)
I think this can be accomplished using a loop but I can't figure out how to get it to use each value as a separated call within the loop.
Any help would be appreciated!
Thanks!
CURSOR V_CUR IS
select regexp_substr(Parm1 ,'[^,]+', 1, level) As str from dual
connect by regexp_substr(Parm1, '[^,]+', 1, level) is not null;
This curor will give you result like this
123
321
Now iterate the cursor and call the procedure in loop.
For i IN V_CUR
LOOP
callProdcedure2(i.str);
END LOOP;
Just loop through substrings:
declare
parm1 varchar2(1000) := '123,234,345,456,567,789,890';
vStartIdx binary_integer;
vEndIdx binary_integer;
vCurValue varchar2(1000);
begin
vStartIdx := 0;
vEndIdx := instr(parm1, ',');
while(vEndIdx > 0) loop
vCurValue := substr(parm1, vStartIdx+1, vEndIdx - vStartIdx - 1);
-- call proc here
dbms_output.put_line('->'||vCurValue||'<-');
vStartIdx := vEndIdx;
vEndIdx := instr(parm1, ',', vStartIdx + 1);
end loop;
-- Call proc here for last part (or in case of single element)
vCurValue := substr(parm1, vStartIdx+1);
dbms_output.put_line('->'||vCurValue||'<-');
end;
There is a utility procedure COMMA_TO_TABLE and array type DBMS_UTILITY.UNCL_ARRAY dedicated for this task. Since Oracle 10g.
It is well document here.
Here is a sample solution:
SET SERVEROUTPUT ON
DECLARE
csvListElm VARCHAR2(4000) := 'elm1, elm2,elm3 ,elm4 , elm5';
csvListTable DBMS_UTILITY.UNCL_ARRAY;
csvListLen BINARY_INTEGER;
currTableName VARCHAR2(222);
BEGIN
DBMS_UTILITY.COMMA_TO_TABLE(csvListElm, csvListLen, csvListTable);
FOR csvElm IN 1..(csvListTable.COUNT - 1) LOOP
dbms_output.put_line('-- CSV element : <'||csvListTable(csvElm)||'>');
dbms_output.put_line('-- Trimmed CSV element: <'||trim(csvListTable(csvElm))||'>');
END LOOP;
END;
/
Sample output:
-- CSV element : <elm1>;
-- Trimmed CSV element: <elm1>;
-- CSV element : < elm2>;
-- Trimmed CSV element: <elm2>;
-- CSV element : <elm3 >;
-- Trimmed CSV element: <elm3>;
-- CSV element : <elm4 >;
-- Trimmed CSV element: <elm4>;
-- CSV element : < elm5>;
-- Trimmed CSV element: <elm5>;
It is possible to use a function that you can use in a for loop (without regexp for ThinkJet):
Create a type and function
CREATE OR REPLACE TYPE t_my_list AS TABLE OF VARCHAR2(100);
CREATE OR REPLACE
FUNCTION cto_table(p_sep in Varchar2, p_list IN VARCHAR2)
RETURN t_my_list
AS
l_string VARCHAR2(32767) := p_list || p_sep;
l_sep_index PLS_INTEGER;
l_index PLS_INTEGER := 1;
l_tab t_my_list := t_my_list();
BEGIN
LOOP
l_sep_index := INSTR(l_string, p_sep, l_index);
EXIT
WHEN l_sep_index = 0;
l_tab.EXTEND;
l_tab(l_tab.COUNT) := TRIM(SUBSTR(l_string,l_index,l_sep_index - l_index));
l_index := l_sep_index + 1;
END LOOP;
RETURN l_tab;
END cto_table;
/
Then how to call it in the for loop:
DECLARE
parm1 varchar2(4000) := '123,234,345,456,567,789,890';
BEGIN
FOR x IN (select * from (table(cto_table(',', parm1)) ) )
LOOP
dbms_output.put_line('callProdcedure2 called with ' || x.COLUMN_VALUE);
callProdcedure2(x.COLUMN_VALUE);
END LOOP;
END;
/
Notice the default name COLUMN_VALUE given by Oracle, which is necessary for the use I want to make of the result.
Result as expected:
callProdcedure2 called with 123
callProdcedure2 called with 234
...