Apache MetaModel allows for extending the query language using User Defined Functions. In fact, the functions that are built into Apache MetaModel are also "just" built-in UDFs.
Functions in MetaModel come in two forms:
Aggregate functions, which implement the org.apache.metamodel.query.AggregateFunction interface.
Scalar functions, which implement the org.apache.metamodel.query.ScalarFunction interface.
A scalar function is a function that provides a result on each row of the dataset that it is applied to. For instance, the built-in function TO_NUMBER will convert each value to a java.lang.Number instead of some other data type.
Normally you can apply the Scalar function TO_NUMBER like this:
Query q = dataContext.query().from(table).select(FunctionType.TO_NUMBER, "id").toQuery(); |
If you dig into FunctionType.TO_NUMBER you will find out that it is simply an object that implements ScalarFunction. Let's imagine you wanted to implement instead now a hash code function. We can implement it like this:
public class HashCodeFunction implements ScalarFunction {
@Override
public ColumnType getExpectedColumnType(ColumnType type) {
return ColumnType.INTEGER;
}
@Override
public String getFunctionName() {
return "HASH_CODE";
}
@Override
public Object evaluate(Row row, SelectItem operandItem) {
Object value = row.getValue(operandItem);
return value == null ? null : value.hashCode();
}
} |
As you can see the implementation part here is pretty easy. We need only to provide a name, a data type and implement the evaluate(...) method. Now to apply the function to our query:
Query q = dataContext.query().from(table).select(new HashCodeFunction(), "id").toQuery(); |
Aggregate functions are used to make calculations which span multiple rows of the dataset. Typically used on a complete dataset or in combination with a GROUP BY condition.
In a similar way to scalar functions, you can also implement your own aggregate functions. Let's say we wanted to implement a DISTINCT_COUNT function (ie. a count of distinct/unique values), we could do it like this:
public class DistinctCountFunction implements AggregateFunction {
@Override
public String getFunctionName() {
return "DISTINCT_COUNT";
}
@Override
public ColumnType getExpectedColumnType(ColumnType type) {
return ColumnType.INTEGER;
}
@Override
public Object evaluate(Object... values) {
AggregateBuilder<?> aggregateBuilder = createAggregateBuilder();
for (Object value : values) {
aggregateBuilder.add(value);
}
return aggregateBuilder.getAggregate();
}
@Override
public AggregateBuilder<?> createAggregateBuilder() {
return new AggregateBuilder<Integer>() {
private Set<Object> uniqueSet = new HashSet<>();
@Override
public void add(Object o) {
uniqueSet.add(o);
}
@Override
public Integer getAggregate() {
return uniqueSet.size();
}
};
}
} |
(you may choose to extend DefaultAggregateFunction which will save you the effort of implementing evaluate(...))
And again you can apply your function in a query like this:
Query q = dataContext.query().from(table).select(new DistinctCountFunction(), "type").toQuery(); |