From 367befbe9865799dc1dbcb76677f591e3b5638a8 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Fri, 6 Apr 2012 18:39:46 +0000 Subject: [PATCH] [#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 --- .../AggregateWindowFunctionTests.java | 17 +-- .../org/jooq/OrderedAggregateFunction.java | 2 +- jOOQ/src/main/java/org/jooq/impl/Factory.java | 23 +++- .../src/main/java/org/jooq/impl/Function.java | 102 +++++++++++++++--- jOOQ/src/main/java/org/jooq/impl/Term.java | 12 +++ 5 files changed, 130 insertions(+), 26 deletions(-) diff --git a/jOOQ-test/src/org/jooq/test/_/testcases/AggregateWindowFunctionTests.java b/jOOQ-test/src/org/jooq/test/_/testcases/AggregateWindowFunctionTests.java index 29059dd8e8..a182064082 100644 --- a/jOOQ-test/src/org/jooq/test/_/testcases/AggregateWindowFunctionTests.java +++ b/jOOQ-test/src/org/jooq/test/_/testcases/AggregateWindowFunctionTests.java @@ -733,17 +733,14 @@ extends BaseTest 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 result2 = create().select( TAuthor_FIRST_NAME(), diff --git a/jOOQ/src/main/java/org/jooq/OrderedAggregateFunction.java b/jOOQ/src/main/java/org/jooq/OrderedAggregateFunction.java index 4b0c0cfe53..2a04d36482 100644 --- a/jOOQ/src/main/java/org/jooq/OrderedAggregateFunction.java +++ b/jOOQ/src/main/java/org/jooq/OrderedAggregateFunction.java @@ -75,7 +75,7 @@ public interface OrderedAggregateFunction { * Add an WITHIN GROUP (ORDER BY ..) clause to the ordered * aggregate function */ - @Support + @Support(ORACLE) AggregateFunction withinGroupOrderBy(SortField... fields); /** diff --git a/jOOQ/src/main/java/org/jooq/impl/Factory.java b/jOOQ/src/main/java/org/jooq/impl/Factory.java index 53bd5d404c..bec9a52018 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Factory.java +++ b/jOOQ/src/main/java/org/jooq/impl/Factory.java @@ -4248,18 +4248,35 @@ public class Factory implements FactoryOperations { /** * Get the aggregated concatenation for a field. + *

+ * This is natively supported by {@link SQLDialect#ORACLE}. It is simulated + * by the following dialects: + *

    + *
  • {@link SQLDialect#CUBRID}: Using GROUP_CONCAT()
  • + *
  • {@link SQLDialect#MYSQL}: Using GROUP_CONCAT()
  • + *
  • {@link SQLDialect#SYBASE}: Using LIST()
  • + *
*/ - @Support(ORACLE) + @Support({ CUBRID, MYSQL, ORACLE, SYBASE }) public static OrderedAggregateFunction listAgg(Field field) { return new Function(Term.LIST_AGG, SQLDataType.VARCHAR, nullSafe(field)); } /** * Get the aggregated concatenation for a field. + *

+ * This is natively supported by {@link SQLDialect#ORACLE}. It is simulated + * by the following dialects: + *

    + *
  • {@link SQLDialect#CUBRID}: Using GROUP_CONCAT
  • + *
  • {@link SQLDialect#MYSQL}: Using GROUP_CONCAT
  • + *
  • {@link SQLDialect#SYBASE}: Using LIST()
  • + *
*/ - @Support(ORACLE) + @Support({ CUBRID, MYSQL, ORACLE, SYBASE }) public static OrderedAggregateFunction listAgg(Field field, String delimiter) { - return new Function(Term.LIST_AGG, SQLDataType.VARCHAR, nullSafe(field), literal("'" + delimiter.replace("'", "''") + "'")); + Field literal = literal("'" + delimiter.replace("'", "''") + "'"); + return new Function(Term.LIST_AGG, SQLDataType.VARCHAR, nullSafe(field), literal); } // ------------------------------------------------------------------------- diff --git a/jOOQ/src/main/java/org/jooq/impl/Function.java b/jOOQ/src/main/java/org/jooq/impl/Function.java index a0234043a0..839228173b 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Function.java +++ b/jOOQ/src/main/java/org/jooq/impl/Function.java @@ -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 extends AbstractField 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] LIST_AGG 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] LIST_AGG 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 extends AbstractField implements /** * Render WITHIN GROUP (ORDER BY ..) 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 extends AbstractField 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 extends AbstractField 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; diff --git a/jOOQ/src/main/java/org/jooq/impl/Term.java b/jOOQ/src/main/java/org/jooq/impl/Term.java index 62cb0eb0b9..1cc70c9322 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Term.java +++ b/jOOQ/src/main/java/org/jooq/impl/Term.java @@ -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"; } },