Skip to content

Commit

Permalink
[#3015] Add support for HSQLDB user-defined aggregate functions
Browse files Browse the repository at this point in the history
  • Loading branch information
lukaseder committed Feb 7, 2014
1 parent db23122 commit dcc9850
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 49 deletions.
Expand Up @@ -41,6 +41,7 @@

package org.jooq.util.hsqldb;

import static org.jooq.impl.DSL.field;
import static org.jooq.impl.DSL.nvl;
import static org.jooq.util.hsqldb.information_schema.Tables.CHECK_CONSTRAINTS;
import static org.jooq.util.hsqldb.information_schema.Tables.ELEMENT_TYPES;
Expand Down Expand Up @@ -316,7 +317,8 @@ protected List<RoutineDefinition> getRoutines0() throws SQLException {
ROUTINES.SPECIFIC_NAME,
nvl(ELEMENT_TYPES.COLLECTION_TYPE_IDENTIFIER, ROUTINES.DATA_TYPE).as("datatype"),
ROUTINES.NUMERIC_PRECISION,
ROUTINES.NUMERIC_SCALE)
ROUTINES.NUMERIC_SCALE,
field(ROUTINES.ROUTINE_DEFINITION.likeRegex(".*(?i:(\\w+\\s+)+aggregate\\s+function).*")).as("aggregate"))
.from(ROUTINES)
.leftOuterJoin(ELEMENT_TYPES)
.on(ROUTINES.ROUTINE_SCHEMA.equal(ELEMENT_TYPES.OBJECT_SCHEMA))
Expand All @@ -334,7 +336,8 @@ protected List<RoutineDefinition> getRoutines0() throws SQLException {
record.getValue(ROUTINES.SPECIFIC_NAME),
record.getValue("datatype", String.class),
record.getValue(ROUTINES.NUMERIC_PRECISION),
record.getValue(ROUTINES.NUMERIC_SCALE)));
record.getValue(ROUTINES.NUMERIC_SCALE),
record.getValue("aggregate", boolean.class)));
}

return result;
Expand Down
Expand Up @@ -40,7 +40,9 @@
*/
package org.jooq.util.hsqldb;

import static org.jooq.impl.DSL.condition;
import static org.jooq.impl.DSL.nvl;
import static org.jooq.impl.DSL.val;
import static org.jooq.util.hsqldb.information_schema.Tables.ELEMENT_TYPES;
import static org.jooq.util.hsqldb.information_schema.Tables.PARAMETERS;
import static org.jooq.util.hsqldb.information_schema.Tables.ROUTINES;
Expand Down Expand Up @@ -69,7 +71,11 @@ public class HSQLDBRoutineDefinition extends AbstractRoutineDefinition {
private final String specificName; // internal name for the function used by HSQLDB

public HSQLDBRoutineDefinition(SchemaDefinition schema, String name, String specificName, String dataType, Number precision, Number scale) {
super(schema, null, name, null, null);
this(schema, name, specificName, dataType, precision, scale, false);
}

public HSQLDBRoutineDefinition(SchemaDefinition schema, String name, String specificName, String dataType, Number precision, Number scale, boolean aggregate) {
super(schema, null, name, null, null, aggregate);

if (!StringUtils.isBlank(dataType)) {
DataTypeDefinition type = new DefaultDataTypeDefinition(
Expand Down Expand Up @@ -109,6 +115,10 @@ protected void init0() throws SQLException {
.and(PARAMETERS.DTD_IDENTIFIER.equal(ELEMENT_TYPES.COLLECTION_TYPE_IDENTIFIER))
.where(PARAMETERS.SPECIFIC_SCHEMA.equal(getSchema().getName()))
.and(PARAMETERS.SPECIFIC_NAME.equal(this.specificName))

// [#3015] HSQLDB user-defined AGGREGATE functions have four parameters, but only one
// is relevant to client code
.and(condition(val(!isAggregate())).or(PARAMETERS.ORDINAL_POSITION.eq(1L)))
.orderBy(PARAMETERS.ORDINAL_POSITION.asc()).fetch();

for (Record record : result) {
Expand Down
5 changes: 5 additions & 0 deletions jOOQ-test/src/org/jooq/test/BaseTest.java
Expand Up @@ -59,6 +59,7 @@

import junit.framework.Assert;

import org.jooq.AggregateFunction;
// ...
import org.jooq.Configuration;
import org.jooq.DAO;
Expand Down Expand Up @@ -667,6 +668,10 @@ protected TableField<IPK, Integer> TIdentityPK_VAL() {
return delegate.TIdentityPK_VAL();
}

protected AggregateFunction<Integer> secondMax(Field<Integer> val) {
return delegate.secondMax(val);
}

protected Field<? extends Number> FAuthorExistsField(String authorName) {
return delegate.FAuthorExistsField(authorName);
}
Expand Down
7 changes: 7 additions & 0 deletions jOOQ-test/src/org/jooq/test/HSQLDBTest.java
Expand Up @@ -67,6 +67,7 @@
import java.sql.Timestamp;
import java.util.UUID;

import org.jooq.AggregateFunction;
// ...
import org.jooq.DSLContext;
import org.jooq.DataType;
Expand Down Expand Up @@ -651,6 +652,12 @@ protected TableField<TIdentityPkRecord, Integer> TIdentityPK_VAL() {
return T_IDENTITY_PK.VAL;
}

@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
protected AggregateFunction secondMax(Field val) {
return Routines.secondMax(val);
}

@Override
protected Field<? extends Number> FAuthorExistsField(String authorName) {
return Routines.fAuthorExists(authorName);
Expand Down
44 changes: 7 additions & 37 deletions jOOQ-test/src/org/jooq/test/OracleTest.java
Expand Up @@ -62,7 +62,6 @@
xxxxxx xxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxx xxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxx xxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxx xxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxx xxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxx xxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxx xxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Expand Down Expand Up @@ -110,6 +109,7 @@
xxxxxx xxxxxxxxxxxxxxx
xxxxxx xxxxxxxxxxxxxxx
xxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxx xxxxxxxxxxxxxxxxxxxxx
xxxxxx xxxxxxxxxxxxx
xxxxxx xxxxxxxxxxxxxxxxxxxx
Expand Down Expand Up @@ -740,6 +740,12 @@
xxxxxx xxxxx
x
xxxxxxxxxxxxxxxxxxx xxxxxxxxxxx xxxxxxxxxxx xx
xxxxxxxxx
xxxxxxxxx xxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxx xxxx x
xxxxxx xxxxxxxxxxxxxxxxxxxxxxxx
x
xxxxxxxxx
xxxxxxxxx xxxxxxx xxxxxxx xxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxx x
xxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Expand Down Expand Up @@ -1375,42 +1381,6 @@
xxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
x
xxxxx
xxxxxx xxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxx xxxxxxxxx x
xx xxxxx xxx xxxxxxxxxxx xx xxx xxxxxxxxx xxxxxxxx
xxxxxxxxxxxxx xxxxxxx x
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxx xxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxx xxx xxxxxxxxx
xx xxxxx xxx xxxxxxxxxxx xx xxx xxxxxxxxxx xxxxxxxx
xxxxxxxxxxxxx xxxxxxx x
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxx xxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxx xx xx xxx xxxxxxxxx
xx xxxxxxx xxxxx xx xxxxx xxxxxxxxxx xxx xxxxxxxxx xxxxxxxx xxxxxx xxx
xxxxxxxxxx xxxxxx x xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxx xxxxxxx x
xxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxx xxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxx xxx xxxxxxxxx
x
xxxxx
xxxxxx xxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxx xxxxxxxxx x
xxxxxxxxxx xxxxxx x xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxx
Expand Down
Expand Up @@ -106,6 +106,7 @@
import java.util.Arrays;
import java.util.List;

import org.jooq.DSLContext;
import org.jooq.Field;
import org.jooq.Name;
import org.jooq.Record;
Expand All @@ -122,6 +123,7 @@
import org.jooq.UpdatableRecord;
import org.jooq.WindowDefinition;
import org.jooq.WindowSpecification;
import org.jooq.impl.DSL;
import org.jooq.test.BaseTest;
import org.jooq.test.jOOQAbstractTest;

Expand Down Expand Up @@ -161,17 +163,63 @@ public void testSelectCountQuery() throws Exception {
}

@Test
public void testAggregateFunctions() throws Exception {
public void testUserDefinedAggregateFunctions() throws Exception {
if (secondMax(null) == null) {
log.info("SKIPPING", "User-defined aggregate function tests");
return;
}

// Standard aggregate functions, available in all dialects:
// --------------------------------------------------------
Field<BigDecimal> median = median(TBook_ID());
// Check the correctness of the aggregate function
List<Integer> result1 =
create().select(secondMax(TBook_ID()))
.from(TBook())
.groupBy(TBook_AUTHOR_ID())
.orderBy(TBook_AUTHOR_ID().asc())
.fetch(0, Integer.class);

// Some dialects don't support a median function or a simulation thereof
// Use AVG instead, as in this example the values of MEDIAN and AVG
// are the same
switch (dialect().family()) {
/* [pro] xx
assertEquals(asList(1, 3), result1);

/* [pro] xx
xxxxxx xxxxxxxxxxxxxxxxxxxx x
xxxx xxxxxxx x
xx xxxxx xxx xxxxxxxxxxx xx xxx xxxxxxxxxx xxxxxxxx
xxxxxxxxxxxxx xxxxxxx x
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxx xxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxx xx xx xxx xxxxxxxxx
xx xxxxxxx xxxxx xx xxxxx xxxxxxxxxx xxx xxxxxxxxx xxxxxxxx xxxxxx xxx
xxxxxxxxxx xxxxxx x xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxx xxxxxxx x
xxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxx xxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxx xxx xxxxxxxxx
xxxxxx
x
x
xx xxxxx xx
x
xxxxx
xxxxxx xxxx xxxxxxxxxxxxxxxxxxxxxxxx xxxxxx xxxxxxxxx x
xx xxxxxxxx xxxxxxxxx xxxxxxxxxx xxxxxxxxx xx xxx xxxxxxxxx
xx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxx xxxxxx x xxxxxxxxxxxxxxxxxxx
xx xxxx xxxxxxxx xxxxx xxxxxxx x xxxxxx xxxxxxxx xx x xxxxxxxxxx xxxxxxx
xx xxx xxx xxxxxxxx xx xx xxxx xxxxxxx xxx xxxxxx xx xxxxxx xxx xxx
xx xxx xxx xxxx
xxxxxx xxxxxxxxxxxxxxxxxxxx x
xx xxxxx xx
xxxx xxxx
xxxx xxxxxxx
xxxx xxxx
Expand Down
25 changes: 25 additions & 0 deletions jOOQ-test/src/org/jooq/test/hsqldb/create.sql
Expand Up @@ -22,6 +22,7 @@ DROP FUNCTION IF EXISTS f2502_2/
DROP PROCEDURE IF EXISTS p2502/
DROP FUNCTION IF EXISTS f2515/
DROP FUNCTION IF EXISTS f2515_/
DROP FUNCTION IF EXISTS second_max/

DROP VIEW IF EXISTS v_author/
DROP VIEW IF EXISTS v_book/
Expand Down Expand Up @@ -541,3 +542,27 @@ BEGIN ATOMIC
SET io2 = i2;
END
/

CREATE AGGREGATE FUNCTION second_max(
IN val INTEGER,
IN flag BOOLEAN,
INOUT highest INTEGER,
INOUT second_highest INTEGER
)
RETURNS INTEGER
CONTAINS SQL
BEGIN ATOMIC
DECLARE temp INTEGER;

IF flag THEN
RETURN second_highest;
ELSE
SET temp = highest;
SET highest = GREATEST(COALESCE(highest, -2147483648), val);
SET second_highest = CASE WHEN temp < highest THEN temp ELSE second_highest END;

SET temp = GREATEST(COALESCE(second_highest, -2147483648), val);
SET second_highest = CASE WHEN temp < highest THEN temp ELSE second_highest END;
END IF;
END
/
9 changes: 9 additions & 0 deletions jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java
Expand Up @@ -72,6 +72,7 @@
import java.util.Properties;
import java.util.UUID;

import org.jooq.AggregateFunction;
// ...
import org.jooq.DAO;
import org.jooq.DSLContext;
Expand Down Expand Up @@ -915,6 +916,9 @@ protected final TableField<BS, BigDecimal> TBookSale_SOLD_FOR() {
protected abstract TableField<IPK, Integer> TIdentityPK_ID();
protected abstract TableField<IPK, Integer> TIdentityPK_VAL();

protected <N extends Number> AggregateFunction<N> secondMax(Field<N> val) {
return null;
}
protected abstract Field<? extends Number> FAuthorExistsField(String authorName);
protected abstract Field<? extends Number> FOneField();
protected abstract Field<? extends Number> FNumberField(Number n);
Expand Down Expand Up @@ -2081,6 +2085,11 @@ public void testBitwiseOperations() throws Exception {
new FunctionTests(this).testBitwiseOperations();
}

@Test
public void testUserDefinedAggregateFunctions() throws Exception {
new AggregateWindowFunctionTests(this).testUserDefinedAggregateFunctions();
}

@Test
public void testAggregateFunctions() throws Exception {
new AggregateWindowFunctionTests(this).testAggregateFunctions();
Expand Down

0 comments on commit dcc9850

Please sign in to comment.