Can I convert an oracle BLOB to Base64 CLOB in One go?
like:
CREATE TABLE test
(
image BLOB,
imageBase64 CLOB
);
INSERT INTO test(image)
VALUES (LOAD_FILE('/full/path/to/new/image.jpg'));
UPDATE test SET imageBase64 = UTL_ENCODE.base64_encode(image);
commit;
I know I can add functions/Stored proc to do the work. Performance aspect is very important,so I am asking if there is a way to overcome the 32K limitation by directly pushing the data into a CLOB.
This function got from here should do the job.
CREATE OR REPLACE FUNCTION base64encode(p_blob IN BLOB)
RETURN CLOB
-- -----------------------------------------------------------------------------------
-- File Name : http://oracle-base.com/dba/miscellaneous/base64encode.sql
-- Author : Tim Hall
-- Description : Encodes a BLOB into a Base64 CLOB.
-- Last Modified: 09/11/2011
-- -----------------------------------------------------------------------------------
IS
l_clob CLOB;
l_step PLS_INTEGER := 12000; -- make sure you set a multiple of 3 not higher than 24573
BEGIN
FOR i IN 0 .. TRUNC((DBMS_LOB.getlength(p_blob) - 1 )/l_step) LOOP
l_clob := l_clob || UTL_RAW.cast_to_varchar2(UTL_ENCODE.base64_encode(DBMS_LOB.substr(p_blob, l_step, i * l_step + 1)));
END LOOP;
RETURN l_clob;
END;
/
Then the update can look like
UPDATE test SET imageBase64 = base64encode(image);
Note that maybe the function should be optimized with the function DBMS_LOB.APPEND instead of that concatenation operator. Try that if you have performance problems.
Provided that stored procs would despite be a viable alternative for you, here's one possible solution to your problem ...
First, let's make that nice base64encode() function of Tim Hall's into a procedure ...
create or replace procedure base64encode
( i_blob in blob
, io_clob in out nocopy clob )
is
l_step pls_integer := 22500; -- make sure you set a multiple of 3 not higher than 24573
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;
The "trick" here is to directly use the persistent LOB locators in calls to procedures/functions. Why "persistent"? Because if you create a function that returns a LOB, then there's a temporary LOB created in background and this means some TEMP disk/memory usage and LOB content copying involved. For large LOBs this may imply a performance hit. In order to satisfy your requirement of making this the most performing possible, you should avoid this TEMP space usage. Hence, for this approach, a stored procedure instead of a function must be used.
Then, of course, the procedure must be fed with persistent LOB locators. You have to do that, again, with a stored procedure, where you e.g. insert an empty LOB (effectively creating a new LOB locator) to a table first, and then supplying that newly created LOB locator to the base64 encoding routine ...
create or replace procedure load_and_encode_image
( i_file_name in varchar2 )
is
l_input_bfile bfile := bfilename('DIR_ANYTHING', i_file_name);
l_image_base64_lob test.imageBase64%type;
l_image_raw test.image%type;
begin
insert into test(image, imageBase64)
values (empty_blob(), empty_clob())
returning image, imageBase64
into l_image_raw, l_image_base64_lob;
begin
dbms_lob.fileopen(l_input_bfile);
dbms_lob.loadfromfile(
dest_lob => l_image_raw,
src_lob => l_input_bfile,
amount => dbms_lob.getlength(l_input_bfile)
);
dbms_lob.fileclose(l_input_bfile);
exception
when others then
if dbms_lob.fileisopen(l_input_bfile) = 1 then
dbms_lob.fileclose(l_input_bfile);
end if;
raise;
end;
base64encode(
i_blob => l_image_raw,
io_clob => l_image_base64_lob
);
end;
Note: Of course, if you base64-encode only small files (the actual size depends on your PGA settings, I guess; a question for a DBA, this is), then the function-based approach may be equally performing than this procedure-based one. Base64-encoding a 200MB file on my laptop took 55 seconds with the function+update approach, 14 seconds with the procedure approach. Not exactly a speed demon, so choose what suits your needs.
Note: I believe this procedure-based approach may be further speeded up by reading the file to inmemory chunks in loop, base64-encoding the chunks to another inmemory chunks and appending them both to the target persistent LOBs. That way you should make the workload even easier by avoiding re-reading the full test.image LOB contents by the base64encode() procedure.
I solved this same problem at work by using a Java stored procedure. There is no chunking/contatenation of VARCHAR2s involved in such an approach, since the ability to encode/decode base64 is natively built into Java, simply writing an Oracle function that thinly wraps the Java method works well and is high-performance since as soon as you've executed it a few times, the HotSpot JVM compiles the Java proc into low-level code (high performance just like a C stored function). I'll edit this answer later and add the details about that Java code.
But to step back just one step, question why are you storing this data as both a BLOB and base64 encoded (CLOB)? Is it because you have clients that want to consume the data in the latter format? I'd really prefer to only store the BLOB format. One reason why is that the base64 encoded version can be double the size of the original binary BLOB, so storing them both means possibly 3x the storage.
One solution, the one I implemented at work, to create your own Java stored function base64_encode() that encodes binary --> base64 and then use that function to encode base64 on the fly at query time (it's not expensive). From the application/client side, you would query something like SELECT base64_encode(image) FROM test WHERE ...
If the application code can't be touched (ie COTS application) or if your developers aren't thrilled about using a function, you could abstract this for them (since you are using 11g+) by using a VIRTUAL (computed) column on the table which contains the computed base64_encode(image). It would function like a view, in that it wouldn't physically store the encoded CLOBs, but would generate them at query time. To any client, they would not be able to tell they are not reading a physical column. The other benefit is that if you ever update the jpg (BLOB), the virtual CLOB is immediately and automatically updated. If you ever have to insert/update/delete a huge batch of BLOBs, you'd save 66% of the redo/archivelog volume from not having to process all the CLOBs.
Lastly, for performance, make very sure you are using SecureFile LOBs (both for BLOBs and CLOBs). They really are much faster and better in just about every way.
UPDATE - I found my code, at least the version that uses a Java Stored Procedure to do the opposite (converting a base64 encoded CLOB to its binary BLOB version). It would not be that difficult to write the inverse.
--DROP FUNCTION base64_decode ;
--DROP java source base64;
-- This is a PLSQL java wrapper function
create or replace
FUNCTION base64_decode (
myclob clob)
RETURN blob
AS LANGUAGE JAVA
NAME 'Base64.decode (
oracle.sql.CLOB)
return oracle.sql.BLOB';
/
-- The Java code that base64 decodes a clob and returns a blob.
create or replace and compile java source named base64 as
import java.sql.*;
import java.io.*;
import oracle.sql.*;
import sun.misc.BASE64Decoder;
import oracle.jdbc.driver.*;
public class Base64 {
public static oracle.sql.BLOB decode(oracle.sql.CLOB myBase64EncodedClob)
{
BASE64Decoder base64 = new BASE64Decoder();
OutputStream outstrm = null;
oracle.sql.BLOB myBlob = null;
ByteArrayInputStream instrm = null;
try
{
if (!myBase64EncodedClob.equals("Null"))
{
Connection conn = new OracleDriver().defaultConnection();
myBlob = oracle.sql.BLOB.createTemporary(conn, false,oracle.sql.BLOB.DURATION_CALL);
outstrm = myBlob.getBinaryOutputStream();
ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream();
InputStream in = myBase64EncodedClob.getAsciiStream();
int c;
while ((c = in.read()) != -1)
{
byteOutStream.write((char) c);
}
instrm = new ByteArrayInputStream(byteOutStream.toByteArray());
try // Input stream to output Stream
{
base64.decodeBuffer(instrm, outstrm);
}
catch (Exception e)
{
e.printStackTrace();
}
outstrm.close();
instrm.close();
byteOutStream.close();
in.close();
conn.close();
}
}
catch (Exception e)
{
e.printStackTrace();
}
return myBlob;
} // Public decode
} // Class Base64
;
/
The easiest way that I found, that works with special characters (in your case you don't have this problem), is using dbms_lob.converttoclob.
Create encapsulated Procedure:
CREATE OR REPLACE FUNCTION blob2clob(blob_i IN BLOB) RETURN CLOB IS
l_clob CLOB;
l_dest_offset NUMBER := 1;
l_src_offset NUMBER := 1;
l_amount INTEGER := dbms_lob.lobmaxsize;
l_clob_csid NUMBER := nls_charset_id('WE8ISO8859P15'); --dbms_lob.default_csid;
l_lang_context INTEGER := dbms_lob.default_lang_ctx;
l_warning INTEGER;
BEGIN
---------------------------
-- Create Temporary BLOB --
---------------------------
dbms_lob.createtemporary(lob_loc => l_clob,
cache => TRUE);
--------------------------
-- Convert CLOB to BLOB --
--------------------------
dbms_lob.converttoclob(dest_lob => l_clob,
src_blob => blob_i,
amount => l_amount,
dest_offset => l_dest_offset,
src_offset => l_src_offset,
blob_csid => l_clob_csid,
lang_context => l_lang_context,
warning => l_warning);
--
RETURN l_clob;
END blob2clob;
Then you can use:
blob2clob(utl_encode.base64_encode(image))
Related
I am having one program in Oracle PL/SQL.The program does some batch processing ie. it sends data to another system through REST API in batches of fixed number of records. the request and response object are clob and hence i am creating temporary lob and freeing it for each iteration.
My question is ,can't i create temp lob once and resuse it for every batch i process and then free it at last only once. Basically i want to bring create and free out of the loop so that it can improve performance and reuse the memory.
When i try to bring it outside loop, i will need to initialize clob variable at the start of each iteration, so i tried it using empty_clob() but did not work.Also assigning null does not work.
I am getting error as "Invalid lob locator specified at ..."
Below is my pseudo code
for i in start_batch to end_batch
loop
dbms_lob.createtemporary(l_clob,TRUE);
...code to generate request object.
dbms_lob.freetemporary(l_clob,TRUE) ;
end loop
Huh. I swear that worked, but you are correct. I shouldn't try to remember these things. I guess assigning '' to a clob does set it to null. You can't use a null clob with dbms_lob.append, since it's expecting basically a pointer. Try using the concatenation operator, ||.
I've confirmed this works:
declare
l_clob clob;
begin
for i in 1..5 loop
l_clob := '';
for j in 1..5 loop
l_clob := l_clob || 'a';
end loop;
dbms_output.put_line(l_clob);
end loop;
end;
Edit:
I'm not sure it's true that a clob concatenated with a varchar is a varchar and therefore limited to 32 kB. But that does contradict what the documentation says. Take this for example:
declare
c clob;
begin
for i in 1..40000 loop
c := c || 'a';
end loop;
dbms_output.put_line('len=' || dbms_lob.getlength(c));
end;
Result:
len=40000
I am trying to return a long paragraph from an Oracle Stored Function. I can do this in SQL Server, but I am new to oracle. How do I return long texts, like a User Agreement long paragraphs.
Note that it is only static texts and requires no DMLs.
If your text is going to be over 32767 characters, then you will want to use the CLOB datatype. The DETERMINISTIC clause is added to help with performance since there is no queries in the function, just string concatenation.
CREATE OR REPLACE FUNCTION return_clob
RETURN CLOB
DETERMINISTIC
IS
l_return_value CLOB;
BEGIN
l_return_value := 'Some text.';
l_return_value := l_return_value || ' Some more text.';
RETURN l_return_value;
END;
/
I have text stored in a database table, many short rows about 70-90 characters long fields. (of historical reasons). I want to append these fields (rows) into an CLOB in an APEX (CKEditor) and it does exeed 32k in many cases.
I have tried in many ways but it seems to be some limit. My code works fine as long the text is less than 32k! My plan is to save it in a new table and there use a clob instead. I have APEX 5.01.
I get 'ORA-06502: PL/SQL: numeric or value error' when it is over 32k.
declare
l_clob CLOB;
l_seq number;
cursor textrader_cur is
SELECT F1NR,FHTTYP,RADNR,FHTEXT,DATUM,UPPTAGEN,NUSER FROM DATATXT WHERE DATATXT.F1NR = :P10_F1NR ORDER BY F1NR,FHTTYP,RADNR;
TYPE datatext_typ IS TABLE OF DATATXT%ROWTYPE INDEX BY PLS_INTEGER;
l_datatext datatext_typ;
begin
l_clob := empty_clob();
DBMS_LOB.CREATETEMPORARY(l_clob,true);
apex_collection.create_or_truncate_collection(p_collection_name => 'TEXT');
select count(1) into x from DATATXT#HUMANAUTV WHERE DATATXT.F1NR = :P10_F1NR;
if x > 0 then
open textrader_cur;
loop
fetch textrader_cur bulk collect into l_datatext LIMIT 200;
for indx in 1..l_datatext.COUNT loop
y := length(l_datatext(indx).fhtext);
dbms_lob.writeappend (l_clob,y,l_datatext(indx).fhtext);
--l_clob := l_clob || l_datatext(indx).fhtext; -- This causes same error
end loop;
EXIT WHEN l_datatext.COUNT = 0;
end loop;
close textrader_cur;
l_seq := apex_collection.add_member(p_collection_name => 'TEXT',
p_d001 => sysdate,
p_d002 => sysdate,
p_n001 => dbms_lob.getlength(l_clob),
p_clob001 => l_clob);
-- :P10_WP := l_clob;
SELECT clob001 into :P10_WP FROM APEX_COLLECTIONS WHERE SEQ_ID = l_seq AND COLLECTION_NAME='TEXT';
end if;
end;
PL/SQL provides a dbms_lob package to manipulate this data type. The way I had addressed in a different technology (zope/python) similar problem was to create a framework:
To read from db, it returned data as multipe rows
To write to db, it would send data as multipel calls and server eventually combined it.
You can see that here Blob Journey from Database To Browser
The problem is the last line in your code. Session state variables (e.g. P10_WP) are all VARCHAR2, and limited to 32767 characters. You can see that in the APEX functions that call them (example). So you can't assign more than 32k characters to a PL/SQL page item.
But obviously you can put more than 32k characters in an HTML form item! So it's an awkward workaround - you have to get the clob data in and out of the HTML form item without using APEX page items. Typically this is done by writing the clob to a Collection, then using AJAX calls to an Application Process to retrieve it, since JavaScript has no problem with character limits.
It looks like you've gotten partway there on your own, with your TEXT collection, but you'll still have to write your own On-Demand Application Process so you can load the collection into JavaScript, and put it in the HTML form item from there. It'll be easier if you use the built-in apex.ajax.clob functionality with the CLOB_CONTENT collection.
I looked over a few articles about this, and this one is pretty well written and straightforward.
The short version is to change your collection name in your code to CLOB_CONTENT, then put this JavaScript function on your page and call it.
function clob_get(){
var clob_ob = new apex.ajax.clob(
function(){
var rs = p.readyState
if(rs == 1||rs == 2||rs == 3){
$x_Show('AjaxLoading');
}else if(rs == 4){
$s('P10_WP',p.responseText);
$x_Hide('AjaxLoading');
}else{return false;}
}
);
clob_ob._get();
}
PL/SQL in APEX are limited to 32k, pl/sql treats clobs as varchar and thats it. My problem cannot be solved within APEX
My stored function creates temporary LOB instance using: Dbms_Lob.CreateTemporary(BUFFER, TRUE, Dbms_Lob.SESSION); where BUFFER is a local CLOB variable. After that the function fills BUFFER with some data and returns it.
Duration parameter of Dbms_Lob.CreateTemporary in my case is Dbms_Lob.SESSION, but according to oracle documentation:
The duration parameter passed to dbms_lob.createtemporary() is a hint.
The duration of the new temp LOB is the same as the duration of the
locator variable in PL/SQL. For example, in the preceding program
block, the program variable a has the duration of the residing frame.
Therefore at the end of the block, memory of a will be freed at the
end of the function.
So BUFFER CLOB may be destroyed by Oracle after leaving the function block. I can see that in some cases, when the BUFFER is more than 32K, I can’t read it’s value returned this way from Java (JDBC) side.
Is there any other way to return temporary CLOB instance from a function?
In a comment you said:
clob.getSubString(0, clob.length()) throws: java.sql.SQLException:
Invalid argument(s) in call at
oracle.sql.CLOB.getSubString(CLOB.java:236) while clob.length()
returns actual length of my clob
The documentation of getSubString states that:
pos - the first character of the substring to be extracted. The first character is at position 1.
With a simple function to generate and return a CLOB, I can retrieve it over JDBC (ojdbc5 or ojdbc6) with no problems, either with getCLOB() or getString(). But if I try to assign the Oracle.sql.CLOB retrieved with getCLOB to a String using
String x = getSubString(0, clob.length());
then I also get the Invalid argument(s) in call error. Just changing that to:
String x = getSubString(1, clob.length());
works. So it seems to have nothing to do with the temporary allocation in the function, or the CLOB size. I don't understand why you didn't have a problem with smaller CLOBs - maybe your logic just didn't hit this if they were small?
In the meantime you've worked around this with clob.getCharacterStream().read(), so this may be a bit irrelevant now.
I created the function to return a clob, with random generated data, lenght is 200k characters.
create function f_clob
return clob is
l_clob CLOB := EMPTY_CLOB;
l_len BINARY_INTEGER;
l_content VARCHAR2(32000);
BEGIN
dbms_lob.createtemporary(l_clob, TRUE);
dbms_lob.open(l_clob, dbms_lob.lob_readwrite);
--
for i in 1..100
loop
l_content := dbms_random.string('A', 2000);
l_len := length(l_content);
dbms_lob.writeappend(l_clob, l_len, l_content);
end loop;
dbms_lob.close(l_clob);
--
return l_clob;
end f_clob;
Then I call the function:
select to_char(substr(f_clob, 1, 200)) clob_chunk
from (
select 1
from dual
union
select 2
from dual)
And I always get data out as a result. I wonder why your function isn't returning data.
I'm using Oracle 11g and I'm trying to find out the length of a text. I normally use select length(myvar) from table, but I can't do that.
The table which I want to query has a BLOB column that saves characters or photos. I want to know the number of characters that my BLOB column has.
I tried to convert my BLOB into a char using UTL_RAW.CAST_TO_VARCHAR2(myblob) from table, but this functions isn't working correctly or maybe I'm making a mistake.
For example:
My BLOB have the word Section, when I see this in the databse in the hexadecimal form I see S.e.c.t.i.o.n.. I don't know why it have those points in between each letter.
Then I used the this query:
select UTL_RAW.CAST_TO_VARCHAR2(myblob)
from table
The result of this query is 'S' so it's not the complete word that my BLOB has, and when I make this query:
select length(UTL_RAW.CAST_TO_VARCHAR2(myblob))
from table
the result is 18, but the word Sections doesn't have 18 characters.
I was trying to convert the BLOB into a VARCHAR, although I think my best choise would be a CLOB because the length of the text that it can save is more than the limit that VARCHAR has. I tried to do that by making this query (I'm not sure if this is correct but is what I found in the internet):
select UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR(myblob, 32767, 1))
from table
This query also returns 'S'
For anyone coming to this thread and wants to know how to convert a blob to a clob. Here is an example.
create function clobfromblob(p_blob blob) return clob is
l_clob clob;
l_dest_offsset integer := 1;
l_src_offsset integer := 1;
l_lang_context integer := dbms_lob.default_lang_ctx;
l_warning integer;
begin
if p_blob is null then
return null;
end if;
dbms_lob.createTemporary(lob_loc => l_clob
,cache => false);
dbms_lob.converttoclob(dest_lob => l_clob
,src_blob => p_blob
,amount => dbms_lob.lobmaxsize
,dest_offset => l_dest_offsset
,src_offset => l_src_offsset
,blob_csid => dbms_lob.default_csid
,lang_context => l_lang_context
,warning => l_warning);
return l_clob;
end;
To convert blob to clob, try this:
SELECT TO_CLOB(UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR(MYBLOB,2000)))
FROM MYTABLE;
SELECT DBMS_LOB.GetLength( myblob ) length_in_bytes
FROM table
will return the length of the BLOB in bytes. It sounds like the character data in your BLOB is probably encoded using the UTF-16 character set so the number of bytes is probably twice the number of characters (depending on the version of Unicode that is being used and the specific data being stored, some characters might require 4 bytes of storage but it is relatively unlikely that you're dealing with any of those characters).
You can use the DBMS_LOB.ConvertToClob procedure to convert a BLOB to a CLOB (though since this is a procedure, you'll need to call it in a PL/SQL block). As part of that conversion, you'll almost certainly need to specify the character set that the data is encoded in-- my assumption is that your application is using the UTF-16 character set but that's just an assumption.
Only converting to CLOB:
select TO_CLOB(UTL_RAW.CAST_TO_VARCHAR2(YOURCLOB)) from DUAL;
Inspired by Craig without limitation on size.