Skip to Main Content
  • Questions
  • User-defined aggregate function in Java

Breadcrumb

Question and Answer

Connor McDonald

Thanks for the question, Jaromir.

Asked: September 04, 2017 - 8:15 pm UTC

Last updated: January 22, 2018 - 2:11 am UTC

Version: Oracle 12.1

Viewed 1000+ times

You Asked

I'd like to implement a SQL user-defined aggregation function in language Java. I started with a 'Hello World' function, which is trivial to code in PL/SQL - see https://livesql.oracle.com/apex/livesql/s/finvtl1sfarg4x2ywvvdiwrjh

In the next step I defined a TYPE using a Java class to implement the ODCIAggregate functions as follows:

CREATE OR REPLACE 
TYPE j_hw_type AS OBJECT
  EXTERNAL NAME 'JHelloWorld' LANGUAGE JAVA 
  USING SQLData(  

   STATIC FUNCTION
        ODCIAggregateInitialize(sctx IN OUT j_hw_type )
        RETURN NUMBER
        EXTERNAL NAME 'ODCIAggregateInitialize(JHelloWorld) return int',

   MEMBER FUNCTION
        ODCIAggregateIterate(self IN OUT j_hw_type ,
                             VALUE IN VARCHAR2 )
        RETURN NUMBER
        EXTERNAL NAME 'ODCIAggregateIterate(String) return int',
        
   MEMBER FUNCTION
        ODCIAggregateTerminate(self IN j_hw_type,
                               returnValue OUT  VARCHAR2,
                               flags IN NUMBER)
        RETURN NUMBER
        EXTERNAL NAME 'ODCIAggregateTerminate(String, int) return int',
        
   MEMBER FUNCTION
        ODCIAggregateMerge(self IN OUT j_hw_type,
                           ctx2 IN StringAggType)
        RETURN NUMBER
        EXTERNAL NAME 'ODCIAggregateMerge(JHelloWorld) return int'
);
/


But the compilation fails with

PLS-00235: the external type is not appropriate for the parameter


which I assume is a problem of passing the IN OUT parameter to the Java methods.
At this point I'm stuck and I din't found some usefull reference or example of a Java implemented user-defined aggregated function.
Any help will be highly appretiated.


with LiveSQL Test Case:

and Chris said...

Any particular reason you feel the need to use Java? You sure you can't do this with PL/SQL? ;)

And what exactly is the source for your Java code?

It is possible to use out parameters with Java wrappers. But:

In Java and other object-oriented languages, a method cannot assign values to objects passed as arguments. When calling a method from SQL or PL/SQL, to change the value of an argument, you must declare it as an OUT or IN OUT parameter in the call specification. The corresponding Java parameter must be an array with only one element.

You can replace the element value with another Java object of the appropriate type, or you can modify the value, if the Java type permits. Either way, the new value propagates back to the caller. For example, you map a call specification OUT parameter of the NUMBER type to a Java parameter declared as float[] p, and then assign a new value to p[0].


http://docs.oracle.com/database/122/JJDEV/defining-call-specifications.htm#JJDEV13260

You can see an example of this over at (look for raiseSal):

http://docs.oracle.com/database/122/JJDEV/writing-object-type-call-specifications.htm#JJDEV13272

Rating

  (5 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

next step done, but still problems

Jaromir Nemec, September 11, 2017 - 8:55 pm UTC

Hi,

thanks for the response, it was very usefull and I could complete the example as follows.
Unfortunately while running a test query I get an error that I can not handle.
And yes, I have to use the Java implementation as in teh real use case I'll use Java functionality not available in PL/SQL.

So I created the type ...

-- Hello Wold aggregation function in java
--
CREATE OR REPLACE 
TYPE j_hw_type AS OBJECT
  EXTERNAL NAME 'JHelloWorld' LANGUAGE JAVA 
  USING SQLData(  
   dummy varchar(250) EXTERNAL NAME 'dummy', -- required due to PLS-00589: no attributes found in object type
   STATIC FUNCTION
        ODCIAggregateInitialize(sctx IN OUT j_hw_type )
        RETURN NUMBER
        EXTERNAL NAME 'ODCIAggregateInitialize(JHelloWorld[]) return int',

   MEMBER FUNCTION
        ODCIAggregateIterate(self IN OUT j_hw_type ,
                             VALUE IN VARCHAR2 )
        RETURN NUMBER
        EXTERNAL NAME 'ODCIAggregateIterate(JHelloWorld[], String) return int',
        
   MEMBER FUNCTION
        ODCIAggregateTerminate(self IN j_hw_type,
                               returnValue OUT  VARCHAR2,
                               flags IN NUMBER)
        RETURN NUMBER
        EXTERNAL NAME 'ODCIAggregateTerminate(JHelloWorld[],String[], int) return int',
        
   MEMBER FUNCTION
        ODCIAggregateMerge(self IN OUT j_hw_type,
                           ctx IN j_hw_type)
        RETURN NUMBER
        EXTERNAL NAME 'ODCIAggregateMerge(JHelloWorld, JHelloWorld[]) return int'
);
/


... and the java class
set def off;

CREATE OR REPLACE AND RESOLVE JAVA SOURCE NAMED "JHelloWorld"
AS 
import java.sql.SQLData;
import java.sql.SQLException;
import java.sql.SQLInput;
import java.sql.SQLOutput;
import java.math.BigDecimal;
/****** START PASTE JAVA CLASS HERE *****/
class JHelloWorld implements SQLData  {
/*
Supporting class for MD5 implementing the methods
initiate (constructor)
iterate
terminate
merge (not required due to order problems?)

*/

  private int dummy; /* Dummy */

  // constructor
  JHelloWorld()  {}; 
  
  // initialize
  public static int  ODCIAggregateInitialize(JHelloWorld[] hw)  {
   hw[0] = new JHelloWorld();
   return (0);
  
  };  
 
  // iterate
  public  int ODCIAggregateIterate(JHelloWorld[] hw, String str) {
    return(0); 
  
  };
  
  // terminate
  public  int ODCIAggregateTerminate(JHelloWorld[] hw, String[] ret,  int flg) {
  
     ret[0] = "Hello Wold";
     return(0);
  
  };
  
  // merge
  public  int ODCIAggregateMerge(JHelloWorld  hw, JHelloWorld[] hw2) {
    return(0); 
  
  };  
    

// SQLData
//  private int helloId;
  String sql_type;

  public String getSQLTypeName() throws SQLException {
    return sql_type;
  }

  public void readSQL(SQLInput stream, String typeName) throws SQLException {
    sql_type = typeName;
    dummy = stream.readInt();
  }

  public void writeSQL(SQLOutput stream) throws SQLException {
    stream.writeInt(dummy);
  }
 
}
/****** STOP PASTE JAVA CLASS HERE *****/
;
/


... and the wrapper function


CREATE OR REPLACE 
FUNCTION j_hello_wold(input VARCHAR2 )
RETURN VARCHAR2
AGGREGATE USING j_hw_type;
/


But the test raises an exception on the OUT parameter...
Any help will be highly appretiated!

</code>
-- test
select x, j_hello_wold(y)
from (select 1 x , 1 y from dual union all select 1 x , 2 y from dual union all select 2 x , 20 y from dual union all select 2 x , 2 y from dual)
group by x;



ORA-00932: inconsistent datatypes: expected an OUT argument at position 1 that is an instance of a user defined Java class convertible to an Oracle type got an object that could not be converted
</code>
Chris Saxon
November 03, 2017 - 1:25 pm UTC

This isn't actually a fix for your code, but a demonstration that this custom aggregates using Java are possible; DBPrism, a way to use Lucene and Solr in the database:

http://dbprism.cvs.sourceforge.net/viewvc/dbprism/odi/db/CreateAggregateFunction.sql?revision=1.1.1.1&view=markup
http://dbprism.cvs.sourceforge.net/viewvc/dbprism/odi/db/CreateAggregateFunction-bdy.sql?revision=1.1.1.1&view=markup
http://dbprism.cvs.sourceforge.net/viewvc/dbprism/odi/src/java/org/apache/lucene/search/facets/ojvm/HitCounter.java?revision=1.3&view=markup

Notice that they wrapped the Java code in a PL/SQL package. Then used the functions in this to build the type.

HT to Kuassi Mensah (PM for Java inside Oracle Database) for finding this.

Is this a documentation bug?

Jaromir Nemec, November 01, 2017 - 9:47 pm UTC

I was confident that it is possible to write aggregate functions in Java, but if neither AskTom nor Google can provide an example my optimism is somehow getting reduced. But I'm for sure will nor give up!
Chris Saxon
November 02, 2017 - 3:09 pm UTC

To be fair, we're not exactly Java experts ;)

I've reached out to the JVM product manager; we'll let you know if they come back with something. Or we figure it out in the meantime!

Racer I., November 03, 2017 - 10:37 am UTC

Hi,

maybe the merge-method (though unused?) is wrong :
ODCIAggregateMerge(JHelloWorld, JHelloWorld[]) return int
->
ODCIAggregateMerge(JHelloWorld[], JHelloWorld) return int

regards,

Chris Saxon
November 03, 2017 - 1:53 pm UTC

Perhaps... that would match the Java class posted.

Thanks for the Links

Jaromir D.B. Nemec, November 03, 2017 - 1:30 pm UTC


Chris Saxon
November 03, 2017 - 1:46 pm UTC

No worries, let us know if you get a working solution.

Aggregate Function in Java - Works Fine

A reader, January 21, 2018 - 12:23 pm UTC

Java implementation of Oracle aggregate function is indeed possible.
The enabling trick is not to use a Java implementation of an ORACLE TYPE but to define a PL/SQL wrapper package above the Java class and use the PL/SQL functions in the TYPE implementation.

Very simple implementation of a Hello World Java based aggregate function follows - the aggregate function returns always a string "Hello World".

The Java class makes nearly nothing only returns in the ODCITerminate function the string "Hello World".

Note that nny practical implementation of a Java based aggregate function will need to set up a context class and pass it to each iterate method - you can find an example of such context class in the DBPrism example - see follow up above.

Java Class

Create or Replace AND RESOLVE Java Source Named "JAggrFun" As
import java.math.BigDecimal;

class JAggrFun {
/*
Supporting Java class for dummy aggregate function
Implemented methods:
initiate   - dummy
iterate    - dummy
terminate  - return "Hello World"
merge (is not required)

*/
  final static BigDecimal SUCCESS = new BigDecimal(0);
  final static BigDecimal ERROR = new BigDecimal(1);

  static public BigDecimal ODCIInitialize(BigDecimal[] sctx) {

    return SUCCESS; 
  }

  static public BigDecimal ODCIIterate(BigDecimal ctx, String str) { 

     return SUCCESS;                                                                 
  } 

  static public BigDecimal ODCITerminate(BigDecimal ctx, String[] str) {

    str[0] = "Hello World";
    return SUCCESS;
  } 

}
/


wrapper package of Java methods

create or replace
PACKAGE JAggrFunPackage authid current_user AS
  FUNCTION ODCIInitialize(
                  ctx OUT NOCOPY NUMBER
                  ) RETURN NUMBER
                  AS LANGUAGE JAVA
    NAME 'JAggrFun.ODCIInitialize(
              java.math.BigDecimal[]) return java.math.BigDecimal';

  FUNCTION ODCIIterate(
                  ctx IN NUMBER,
                  str VARCHAR2) RETURN NUMBER
                  AS LANGUAGE JAVA
    NAME 'JAggrFun.ODCIIterate(
              java.math.BigDecimal,
              java.lang.String) return java.math.BigDecimal';

  FUNCTION ODCITerminate(
                  ctx IN NUMBER,
                  str OUT VARCHAR2) RETURN NUMBER
                  AS LANGUAGE JAVA
    NAME 'JAggrFun.ODCITerminate(
              java.math.BigDecimal,
              java.lang.String[]) return java.math.BigDecimal';

END JAggrFunPackage;
/


Type

CREATE OR REPLACE 
TYPE JAggrFunPackageType authid current_user AS OBJECT
  (  
   jctx NUMBER, -- stored context;  not used in dummy implementation
   STATIC FUNCTION
        ODCIAggregateInitialize(sctx IN OUT NOCOPY JAggrFunPackageType )
        RETURN NUMBER,

   MEMBER FUNCTION
        ODCIAggregateIterate(self IN OUT NOCOPY JAggrFunPackageType,
                             VALUE IN VARCHAR2 )
        RETURN NUMBER,

   MEMBER FUNCTION
        ODCIAggregateTerminate(self IN JAggrFunPackageType,
                               returnValue OUT NOCOPY VARCHAR2,
                               flags IN NUMBER)
        RETURN NUMBER, 

   MEMBER FUNCTION
        ODCIAggregateMerge(self IN OUT NOCOPY JAggrFunPackageType,
                           ctx IN JAggrFunPackageType)
        RETURN NUMBER
);
/


Aggregate Function
CREATE OR REPLACE 
FUNCTION JAggr(input VARCHAR2 )
RETURN VARCHAR2
AGGREGATE USING JAggrFunPackageType;
/


Type Body


create or replace
type body JAggrFunPackageType  is
  static function ODCIAggregateInitialize(sctx IN OUT NOCOPY JAggrFunPackageType)
  return number
  is
  begin
    sctx := JAggrFunPackageType( null );
    return ODCIConst.Success;
  end;

  member function ODCIAggregateIterate(self IN OUT NOCOPY JAggrFunPackageType,
                                       value IN varchar2 )
  return number
  is
     status  NUMBER;
  begin
    if self.jctx is null then

      status := JAggrFunPackage.ODCIInitialize(self.jctx);
      if (status <> ODCIConst.Success) then
         return status;
      end if;
    end if;

    status := JAggrFunPackage.ODCIIterate(jctx,value);

    return status;
  end;

  member function ODCIAggregateTerminate(self IN JAggrFunPackageType,
                                         returnValue OUT NOCOPY VARCHAR2,
                                         flags IN number)
  return number
  is
  begin

    return JAggrFunPackage.ODCITerminate(jctx, returnValue);
  end;

  member function  ODCIAggregateMerge(self IN OUT NOCOPY JAggrFunPackageType,
                           ctx IN JAggrFunPackageType)
  return number
  is
  begin
    return ODCIConst.Success;
  end;
end;
/


Test
select JAggr(dummy) from dual;

JAGGR(DUMMY)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
-------------
Hello World 



Thanks very much for the support with solving this problem. A a final note - you were right, I finally realized I do not need a Java implemented aggregate function at all. I can use a PL/SQL implementation for my purpose - see details in https://community.oracle.com/ideas/20275
Connor McDonald
January 22, 2018 - 2:11 am UTC

Thanks for the good info.

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library