[#1273] Simulate MySQL's / CUBRID's GROUP_CONCAT() aggregate function using Oracle's LISTAGG() function

[#1275] Simulate Sybase's LIST() aggregate function using Oracle's LISTAGG() function
This commit is contained in:
Lukas Eder 2012-04-06 18:39:46 +00:00
parent e1220a5e75
commit 367befbe98
5 changed files with 130 additions and 26 deletions

View File

@ -733,17 +733,14 @@ extends BaseTest<A, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658, T725
public void testListAgg() throws Exception {
switch (getDialect()) {
case ASE:
case CUBRID:
case DB2:
case DERBY:
case H2:
case HSQLDB:
case INGRES:
case MYSQL:
case POSTGRES:
case SQLITE:
case SQLSERVER:
case SYBASE:
log.info("SKIPPING", "LISTAGG tests");
return;
}
@ -751,7 +748,7 @@ extends BaseTest<A, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658, T725
Result<?> result1 = create().select(
TAuthor_FIRST_NAME(),
TAuthor_LAST_NAME(),
listAgg(TBook_TITLE(), ", ").withinGroupOrderBy(TBook_ID().desc()).as("books"))
listAgg(TBook_ID(), ", ").withinGroupOrderBy(TBook_ID().desc()).as("books"))
.from(TAuthor())
.join(TBook()).on(TAuthor_ID().equal(TBook_AUTHOR_ID()))
.groupBy(
@ -764,8 +761,16 @@ extends BaseTest<A, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658, T725
assertEquals(2, result1.size());
assertEquals(AUTHOR_FIRST_NAMES, result1.getValues(TAuthor_FIRST_NAME()));
assertEquals(AUTHOR_LAST_NAMES, result1.getValues(TAuthor_LAST_NAME()));
assertEquals("Animal Farm, 1984", result1.getValue(0, "books"));
assertEquals("Brida, O Alquimista", result1.getValue(1, "books"));
assertEquals("2, 1", result1.getValue(0, "books"));
assertEquals("4, 3", result1.getValue(1, "books"));
switch (getDialect()) {
case CUBRID:
case MYSQL:
case SYBASE:
log.info("SKIPPING", "LISTAGG window function tests");
return;
}
Result<?> result2 = create().select(
TAuthor_FIRST_NAME(),

View File

@ -75,7 +75,7 @@ public interface OrderedAggregateFunction<T> {
* Add an <code>WITHIN GROUP (ORDER BY ..)</code> clause to the ordered
* aggregate function
*/
@Support
@Support(ORACLE)
AggregateFunction<T> withinGroupOrderBy(SortField<?>... fields);
/**

View File

@ -4248,18 +4248,35 @@ public class Factory implements FactoryOperations {
/**
* Get the aggregated concatenation for a field.
* <p>
* This is natively supported by {@link SQLDialect#ORACLE}. It is simulated
* by the following dialects:
* <ul>
* <li> {@link SQLDialect#CUBRID}: Using <code>GROUP_CONCAT()</code></li>
* <li> {@link SQLDialect#MYSQL}: Using <code>GROUP_CONCAT()</code></li>
* <li> {@link SQLDialect#SYBASE}: Using <code>LIST()</code></li>
* </ul>
*/
@Support(ORACLE)
@Support({ CUBRID, MYSQL, ORACLE, SYBASE })
public static OrderedAggregateFunction<String> listAgg(Field<?> field) {
return new Function<String>(Term.LIST_AGG, SQLDataType.VARCHAR, nullSafe(field));
}
/**
* Get the aggregated concatenation for a field.
* <p>
* This is natively supported by {@link SQLDialect#ORACLE}. It is simulated
* by the following dialects:
* <ul>
* <li> {@link SQLDialect#CUBRID}: Using <code>GROUP_CONCAT</code></li>
* <li> {@link SQLDialect#MYSQL}: Using <code>GROUP_CONCAT</code></li>
* <li> {@link SQLDialect#SYBASE}: Using <code>LIST()</code></li>
* </ul>
*/
@Support(ORACLE)
@Support({ CUBRID, MYSQL, ORACLE, SYBASE })
public static OrderedAggregateFunction<String> listAgg(Field<?> field, String delimiter) {
return new Function<String>(Term.LIST_AGG, SQLDataType.VARCHAR, nullSafe(field), literal("'" + delimiter.replace("'", "''") + "'"));
Field<String> literal = literal("'" + delimiter.replace("'", "''") + "'");
return new Function<String>(Term.LIST_AGG, SQLDataType.VARCHAR, nullSafe(field), literal);
}
// -------------------------------------------------------------------------

View File

@ -36,7 +36,12 @@
package org.jooq.impl;
import static java.util.Arrays.asList;
import static org.jooq.SQLDialect.CUBRID;
import static org.jooq.SQLDialect.MYSQL;
import static org.jooq.SQLDialect.SYBASE;
import static org.jooq.impl.Factory.one;
import static org.jooq.impl.Term.LIST_AGG;
import java.util.Arrays;
import java.util.Collection;
@ -138,14 +143,87 @@ class Function<T> extends AbstractField<T> implements
}
@Override
public final void toSQL(RenderContext context) {
context.sql(getFNName(context.getDialect()));
toSQLArguments(context);
toSQLWithinGroupClause(context);
toSQLOverClause(context);
public final void bind(BindContext context) {
if (term == LIST_AGG && asList(MYSQL, CUBRID).contains(context.getDialect())) {
context.bind(arguments.get(0));
context.bind((QueryPart) withinGroupOrderBy);
if (arguments.size() > 1) {
context.bind(arguments.get(1));
}
}
else {
context.bind((QueryPart) arguments)
.bind((QueryPart) withinGroupOrderBy)
.bind((QueryPart) partitionBy)
.bind((QueryPart) orderBy);
}
}
private void toSQLOverClause(RenderContext context) {
@Override
public final void toSQL(RenderContext context) {
context.sql(getFNName(context.getDialect()));
if (term == LIST_AGG && asList(MYSQL, CUBRID).contains(context.getDialect())) {
toSQLGroupConcat(context);
}
if (term == LIST_AGG && asList(SYBASE).contains(context.getDialect())) {
toSQLList(context);
}
else {
toSQLArguments(context);
toSQLWithinGroupClause(context);
toSQLOverClause(context);
}
}
/**
* [#1275] <code>LIST_AGG</code> simulation for MySQL and CUBRID
*/
private void toSQLList(RenderContext context) {
context.sql("(");
if (distinct) {
context.keyword("distinct ");
}
context.sql(arguments);
if (!withinGroupOrderBy.isEmpty()) {
context.keyword(" order by ")
.sql(withinGroupOrderBy);
}
context.sql(")");
}
/**
* [#1273] <code>LIST_AGG</code> simulation for MySQL and CUBRID
*/
private final void toSQLGroupConcat(RenderContext context) {
context.sql("(");
if (distinct) {
context.keyword("distinct ");
}
context.sql(arguments.get(0));
if (!withinGroupOrderBy.isEmpty()) {
context.keyword(" order by ")
.sql(withinGroupOrderBy);
}
if (arguments.size() > 1) {
context.keyword(" separator ")
.sql(arguments.get(1));
}
context.sql(")");
}
private final void toSQLOverClause(RenderContext context) {
if (!over) return;
String glue = "";
@ -214,7 +292,7 @@ class Function<T> extends AbstractField<T> implements
/**
* Render <code>WITHIN GROUP (ORDER BY ..)</code> clause
*/
private void toSQLWithinGroupClause(RenderContext context) {
private final void toSQLWithinGroupClause(RenderContext context) {
if (!withinGroupOrderBy.isEmpty()) {
context.keyword(" within group (order by ")
.sql(withinGroupOrderBy)
@ -225,7 +303,7 @@ class Function<T> extends AbstractField<T> implements
/**
* Render function arguments and argument modifiers
*/
private void toSQLArguments(RenderContext context) {
private final void toSQLArguments(RenderContext context) {
context.sql("(");
if (distinct) {
@ -285,14 +363,6 @@ class Function<T> extends AbstractField<T> implements
}
}
@Override
public final void bind(BindContext context) {
context.bind((QueryPart) arguments)
.bind((QueryPart) withinGroupOrderBy)
.bind((QueryPart) partitionBy)
.bind((QueryPart) orderBy);
}
@Override
public final boolean isNullLiteral() {
return false;

View File

@ -103,6 +103,18 @@ enum Term {
LIST_AGG {
@Override
public String translate(SQLDialect dialect) {
switch (dialect) {
case CUBRID:
case MYSQL:
return "group_concat";
case ORACLE:
return "listagg";
case SYBASE:
return "list";
}
return "listagg";
}
},