Blog do projektu Open Source JavaHotel

niedziela, 28 marca 2010

Oracle - DB2 migration

Problem
Assume that for very important reason we have to keep some data as base64 encoded data. This data is very often sent as a part of pure xml file and we strongly believe that keeping this data in this format and thus avoiding converting them every time we improve efficiency of our mission critical application.

It is quite easy in Oracle PL/SQL query language to convert this data to human readable format by using utl_raw and UTL_ENCODE functions.
SELECT CUST_ID, utl_raw.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_DECODE(utl_raw.cast_to_raw(CUST_ADDRESS))) FROM TEST1.CUST_ENCODED_TABLE;

SELECT CUST_ID, utl_raw.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_DECODE(utl_raw.cast_to_raw(CUST_ADDRESS))) FROM TEST1.CUST_ENCODED_TABLE WHERE utl_raw.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_DECODE(utl_raw.cast_to_raw(CUST_ADDRESS))) LIKE '%25%';

But after migrating to DB2 there is a little surprise - there is nothing like UTL_RAW and UTL_ENCODE functions and we have a lot of data in base64 encoded format. How to resolve this issue without costly redesigning our application and converting data once again ?

Solution idea
The solution is very simple - UTFs.

Example
Assume that we have created the following table:
 CREATE TABLE TEST.CUST_ENCODED_TABLE
   (    "CUST_ID" VARCHAR2(45),
        "CUST_ADDRESS" VARCHAR2(200)
   );

CUST_ADDRESS contains customer address in base64 encoded format.

Now populate our table with sample data:

DECLARE

    i NUMBER := 1;
    CUST_ID VARCHAR(100);
    CUST_ADDRESS VARCHAR(200);
    CUST_ENCODED VARCHAR(200);
    SEQ NUMBER;

BEGIN

    LOOP
        i := i+1;
        SEQ := LTrim(TO_CHAR(i,'999'));
        CUST_ID := 'ID'||SEQ;
        CUST_ADDRESS := 'CITY'||SEQ||' '||'ZIP'||SEQ||' '||'ADDRESS'||SEQ;

        CUST_ENCODED := UTL_RAW.cast_to_varchar2(UTL_ENCODE.BASE64_ENCODE(utl_raw.cast_to_raw(CUST_ADDRESS))); 
        dbms_output.put_line(CUST_ID || ':' || CUST_ADDRESS || ':' || CUST_ENCODED);
        INSERT INTO TEST1.CUST_ENCODED_TABLE VALUES(CUST_ID,CUST_ENCODED); 

        EXIT WHEN i>100;

    END LOOP;
END;

Query examples
SELECT CUST_ID,utl_raw.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_DECODE(utl_raw.cast_to_raw(CUST_ADDRESS))) FROM TEST1.CUST_ENCODED_TABLE;

SELECT CUST_ID, utl_raw.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_DECODE(utl_raw.cast_to_raw(CUST_ADDRESS))) FROM TEST1.CUST_ENCODED_TABLE WHERE utl_raw.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_DECODE(utl_raw.cast_to_raw(CUST_ADDRESS))) LIKE '%25%';
Migrate to DB2
Now let's migrate our table to db2. You can use IBM Migration Toolkit or accomplish it any other way.

After migration run a query - data are encoded as expected.
 db2 "select * from TEST.CUST_ENCODED_TABLE"

..........
ID87 Q0lUWTg3IFpJUDg3IEFERFJFU1M4Nw==
ID88 Q0lUWTg4IFpJUDg4IEFERFJFU1M4OA==
..........

Solution - UDF
Create and compile following simple java class.
import java.io.IOException;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import COM.ibm.db2.app.UDF;

public class UTL_ENCODE extends COM.ibm.db2.app.UDF {
   
    public static String BASE64_ENCODE(String in) {
        BASE64Encoder encoder = new BASE64Encoder();
        return encoder.encode(in.getBytes());
    }

        public static String BASE64_DECODE (String in) throws IOException {
            BASE64Decoder decoder = new BASE64Decoder();
            byte[] b = decoder.decodeBuffer(in);
            return new String(b);
        }

}
And compile using javac provided together with DB 9.7.
 /opt/ibm/db2/V9.7/java/jdk32/bin/javac UTL_ENCODE.java

Then copy output UTL_ENCODE.class to the /home/db2inst1/sqllib/function directory.
Next step is to create SQL functions.

CREATE OR REPLACE FUNCTION UTL_ENCODE.BASE64_DECODE(VARCHAR(200))
RETURNS varchar(200) EXTERNAL NAME 'UTL_ENCODE!BASE64_DECODE'
FENCED
PARAMETER STYLE JAVA
LANGUAGE JAVA
NO EXTERNAL ACTION ;
Tip: After changing anything in the java code don't forget to run
db2 "CALL SQLJ.REFRESH_CLASSES()"
after copying .class file to functions directory. Otherwise DB2 engine will be using the old version of the function.

Don't forget to grant execute right for just created function.
db2 grant execute on FUNCTION UTL_ENCODE.BASE64_DECODE to /user/
Final
db2 "SELECT CUST_ID,UTL_ENCODE.BASE64_DECODE(CUST_ADDRESS) from TEST1.CUST_ENCODED_TABLE"

ID87   CITY87 ZIP87 ADDRESS87
ID88   CITY88 ZIP88 ADDRESS88

SELECT CUST_ID,UTL_ENCODE.BASE64_DECODE(CUST_ADDRESS) FROM TEST1.CUST_ENCODED_TABLE WHERE UTL_ENCODE.BASE64_DECODE(CUST_ADDRESS) LIKE '%25%';

CUST_ID
--------------------------------------------- -----------
ID25    CITY25 ZIP25 ADDRESS25

2 komentarze:

  1. Hi
    I have a similar requirment ,but the only change is that .I have to get huge data from DB (The column is in CLOB)
    .As per the Above codethe max i can read is atound 3700,(VARCHAR limit.).I have changed the code to following .

    import java.io.IOException;
    import java.sql.Clob;
    import java.sql.SQLException;
    import sun.misc.BASE64Decoder;
    import sun.misc.BASE64Encoder;
    import COM.ibm.db2.app.UDF;


    public class UTL_ENCODE extends UDF{

    public UTL_ENCODE()
    {

    }


    public static String BASE64_ENCODE(String in)

    {
    BASE64Encoder encoder = new BASE64Encoder();
    return encoder.encode(in.getBytes());
    }

    public static String BASE64_DECODE (Clob clob) throws IOException, SQLException {
    BASE64Decoder decoder = new BASE64Decoder();
    String s=null;
    byte[] b=null;
    if (clob != null)
    {
    s = clob.getSubString(1, (int) clob.length());
    b = decoder.decodeBuffer(s);

    }

    return new String(b);
    }

    }



    SQL function chnages to

    CREATE OR REPLACE FUNCTION UTL_ENCODE.BASE64_DECODE(CLOB)
    RETURNS CLOB EXTERNAL NAME "UTL_ENCODE!BASE64_DECODE"
    FENCED
    PARAMETER STYLE JAVA
    LANGUAGE JAVA
    NO EXTERNAL ACTION ;


    I had put the the .class to following folder after checking the instance_profile_path.
    C:\ProgramData\IBM\DB2\DB2COPY1\function

    But i am not sure why i am getting the following error why executing select.

    "select UTL_ENCODE.BASE64_DECODE(DATA) from WMB_BINARY_DATA where WMB_MSGKEY='1'

    Java stored procedure or user-defined function "UTL_ENCODE.BASE64_DECODE", specific name "SQL131207163842800" could not load Java class "UTL_ENCODE", reason code "1".. SQLCODE=-4304, SQLSTATE=42724, DRIVER=3.66.46

    can you please suggets if i am making some mistake.
    thanks in advance.

    OdpowiedzUsuń
  2. Sorry missed to add ,Now when I put the original .class created by your code it doesnt work too.As per my understanding its a classpath issue but not sure .How can I resolve it.I tried fixing the environment variable too.No luck.
    I had refered this :
    http://pic.dhe.ibm.com/infocenter/db2luw/v10r1/index.jsp

    OdpowiedzUsuń