From ca32a0fa39b826fc7fad5a17b915b5e7cc0b4a89 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Fri, 6 Apr 2012 19:24:46 +0000 Subject: [PATCH] [#1273] Simulate GROUP_CONCAT() aggregate function using Oracle's LISTAGG() function, where available --- .../AggregateWindowFunctionTests.java | 15 ++- .../java/org/jooq/GroupConcatOrderByStep.java | 75 +++++++++++ .../org/jooq/GroupConcatSeparatorStep.java | 60 +++++++++ jOOQ/src/main/java/org/jooq/impl/Factory.java | 56 +++++++- .../src/main/java/org/jooq/impl/Function.java | 4 +- .../main/java/org/jooq/impl/GroupConcat.java | 121 ++++++++++++++++++ 6 files changed, 325 insertions(+), 6 deletions(-) create mode 100644 jOOQ/src/main/java/org/jooq/GroupConcatOrderByStep.java create mode 100644 jOOQ/src/main/java/org/jooq/GroupConcatSeparatorStep.java create mode 100644 jOOQ/src/main/java/org/jooq/impl/GroupConcat.java diff --git a/jOOQ-test/src/org/jooq/test/_/testcases/AggregateWindowFunctionTests.java b/jOOQ-test/src/org/jooq/test/_/testcases/AggregateWindowFunctionTests.java index 6641b7502e..3aca941004 100644 --- a/jOOQ-test/src/org/jooq/test/_/testcases/AggregateWindowFunctionTests.java +++ b/jOOQ-test/src/org/jooq/test/_/testcases/AggregateWindowFunctionTests.java @@ -48,6 +48,7 @@ import static org.jooq.impl.Factory.countDistinct; import static org.jooq.impl.Factory.cumeDist; import static org.jooq.impl.Factory.denseRank; import static org.jooq.impl.Factory.firstValue; +import static org.jooq.impl.Factory.groupConcat; import static org.jooq.impl.Factory.lag; import static org.jooq.impl.Factory.lead; import static org.jooq.impl.Factory.listAgg; @@ -746,7 +747,13 @@ extends BaseTest result1 = create().select( TAuthor_FIRST_NAME(), TAuthor_LAST_NAME(), - listAgg(TBook_ID(), ", ").withinGroupOrderBy(TBook_ID().desc()).as("books")) + listAgg(TBook_ID(), ", ") + .withinGroupOrderBy(TBook_ID().desc()) + .as("books1"), + groupConcat(TBook_ID()) + .orderBy(TBook_ID().desc()) + .separator(", ") + .as("books2")) .from(TAuthor()) .join(TBook()).on(TAuthor_ID().equal(TBook_AUTHOR_ID())) .groupBy( @@ -759,8 +766,10 @@ extends BaseTestGROUP_CONCAT function. + * + * @author Lukas Eder + * @see Factory#listAgg(Field) + */ +public interface GroupConcatOrderByStep extends GroupConcatSeparatorStep { + + /** + * Add an ORDER BY clause to the query + */ + @Support({ CUBRID, H2, HSQLDB, MYSQL, ORACLE, SYBASE }) + GroupConcatSeparatorStep orderBy(Field... fields); + + /** + * Add an ORDER BY clause to the query + */ + @Support({ CUBRID, H2, HSQLDB, MYSQL, ORACLE, SYBASE }) + GroupConcatSeparatorStep orderBy(SortField... fields); + + /** + * Add an ORDER BY clause to the query + */ + @Support({ CUBRID, H2, HSQLDB, MYSQL, ORACLE, SYBASE }) + GroupConcatSeparatorStep orderBy(Collection> fields); +} diff --git a/jOOQ/src/main/java/org/jooq/GroupConcatSeparatorStep.java b/jOOQ/src/main/java/org/jooq/GroupConcatSeparatorStep.java new file mode 100644 index 0000000000..f389ed25a9 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/GroupConcatSeparatorStep.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com + * All rights reserved. + * + * This software is licensed to you under the Apache License, Version 2.0 + * (the "License"); You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * . Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * . Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * . Neither the name "jOOQ" nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package org.jooq; + +import static org.jooq.SQLDialect.CUBRID; +import static org.jooq.SQLDialect.H2; +import static org.jooq.SQLDialect.HSQLDB; +import static org.jooq.SQLDialect.MYSQL; +import static org.jooq.SQLDialect.ORACLE; +import static org.jooq.SQLDialect.SYBASE; + +import org.jooq.impl.Factory; + +/** + * MySQL's GROUP_CONCAT function. + * + * @author Lukas Eder + * @see Factory#listAgg(Field) + */ +public interface GroupConcatSeparatorStep extends AggregateFunction { + + /** + * Specify the separator on the GROUP_CONCAT function + */ + @Support({ CUBRID, H2, HSQLDB, MYSQL, ORACLE, SYBASE }) + AggregateFunction separator(String separator); +} diff --git a/jOOQ/src/main/java/org/jooq/impl/Factory.java b/jOOQ/src/main/java/org/jooq/impl/Factory.java index b7971850ae..6f4ef8f818 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Factory.java +++ b/jOOQ/src/main/java/org/jooq/impl/Factory.java @@ -94,6 +94,7 @@ import org.jooq.ExecuteListener; import org.jooq.FactoryOperations; import org.jooq.Field; import org.jooq.FieldProvider; +import org.jooq.GroupConcatOrderByStep; import org.jooq.Insert; import org.jooq.InsertQuery; import org.jooq.InsertSetStep; @@ -4258,6 +4259,8 @@ public class Factory implements FactoryOperations { *
  • {@link SQLDialect#MYSQL}: Using GROUP_CONCAT()
  • *
  • {@link SQLDialect#SYBASE}: Using LIST()
  • * + * + * @see #groupConcat(Field) */ @Support({ CUBRID, H2, HSQLDB, MYSQL, ORACLE, SYBASE }) public static OrderedAggregateFunction listAgg(Field field) { @@ -4276,13 +4279,62 @@ public class Factory implements FactoryOperations { *
  • {@link SQLDialect#MYSQL}: Using GROUP_CONCAT
  • *
  • {@link SQLDialect#SYBASE}: Using LIST()
  • * + * + * @see #groupConcat(Field, String) */ @Support({ CUBRID, H2, HSQLDB, MYSQL, ORACLE, SYBASE }) - public static OrderedAggregateFunction listAgg(Field field, String delimiter) { - Field literal = literal("'" + delimiter.replace("'", "''") + "'"); + public static OrderedAggregateFunction listAgg(Field field, String separator) { + Field literal = literal("'" + separator.replace("'", "''") + "'"); return new Function(Term.LIST_AGG, SQLDataType.VARCHAR, nullSafe(field), literal); } + /** + * Get the aggregated concatenation for a field. + *

    + * This is natively supported by + *

      + *
    • {@link SQLDialect#CUBRID}
    • + *
    • {@link SQLDialect#H2}
    • + *
    • {@link SQLDialect#HSQLDB}
    • + *
    • {@link SQLDialect#MYSQL}
    • + *
    + *

    + * It is simulated by the following dialects: + *

      + *
    • {@link SQLDialect#ORACLE}: Using LISTAGG()
    • + *
    • {@link SQLDialect#SYBASE}: Using LIST()
    • + *
    + * + * @see #listAgg(Field) + */ + @Support({ CUBRID, H2, HSQLDB, MYSQL, ORACLE, SYBASE }) + public static GroupConcatOrderByStep groupConcat(Field field) { + return new GroupConcat(nullSafe(field)); + } + + /** + * Get the aggregated concatenation for a field. + *

    + * This is natively supported by + *

      + *
    • {@link SQLDialect#CUBRID}
    • + *
    • {@link SQLDialect#H2}
    • + *
    • {@link SQLDialect#HSQLDB}
    • + *
    • {@link SQLDialect#MYSQL}
    • + *
    + *

    + * It is simulated by the following dialects: + *

      + *
    • {@link SQLDialect#SYBASE}: Using LIST()
    • + *
    + * + * @see #listAgg(Field) + */ + @Support({ CUBRID, H2, HSQLDB, MYSQL, SYBASE }) + public static GroupConcatOrderByStep groupConcatDistinct(Field field) { + return new GroupConcat(nullSafe(field), true); + } + // ------------------------------------------------------------------------- // XXX Window functions // ------------------------------------------------------------------------- diff --git a/jOOQ/src/main/java/org/jooq/impl/Function.java b/jOOQ/src/main/java/org/jooq/impl/Function.java index 6a5e8cf1c3..3929ce49e6 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Function.java +++ b/jOOQ/src/main/java/org/jooq/impl/Function.java @@ -75,9 +75,11 @@ import org.jooq.WindowRowsStep; */ class Function extends AbstractField implements - // Cascading interface implementations for aggregate and window function behaviour + // Cascading interface implementations for aggregate function behaviour OrderedAggregateFunction, AggregateFunction, + + // and for window function behaviour WindowIgnoreNullsStep, WindowPartitionByStep, WindowRowsStep, diff --git a/jOOQ/src/main/java/org/jooq/impl/GroupConcat.java b/jOOQ/src/main/java/org/jooq/impl/GroupConcat.java new file mode 100644 index 0000000000..e2e5beab00 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/GroupConcat.java @@ -0,0 +1,121 @@ +/** + * Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com + * All rights reserved. + * + * This software is licensed to you under the Apache License, Version 2.0 + * (the "License"); You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * . Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * . Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * . Neither the name "jOOQ" nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package org.jooq.impl; + +import static org.jooq.impl.Factory.literal; + +import java.util.Arrays; +import java.util.Collection; + +import org.jooq.AggregateFunction; +import org.jooq.Configuration; +import org.jooq.Field; +import org.jooq.GroupConcatOrderByStep; +import org.jooq.GroupConcatSeparatorStep; +import org.jooq.SortField; +import org.jooq.WindowPartitionByStep; + +/** + * @author Lukas Eder + */ +class GroupConcat extends AbstractFunction implements GroupConcatOrderByStep { + + /** + * Generated UID + */ + private static final long serialVersionUID = -6884415527559632960L; + + private final Field field; + private final boolean distinct; + private final SortFieldList orderBy; + private String separator; + + GroupConcat(Field field) { + this(field, false); + } + + GroupConcat(Field field, boolean distinct) { + super("group_concat", SQLDataType.VARCHAR); + + this.field = field; + this.distinct = distinct; + this.orderBy = new SortFieldList(); + } + + @Override + final Field getFunction0(Configuration configuration) { + Function result; + + if (separator == null) { + result = new Function(Term.LIST_AGG, distinct, SQLDataType.VARCHAR, field); + } + else { + Field literal = literal("'" + separator.replace("'", "''") + "'"); + result = new Function(Term.LIST_AGG, distinct, SQLDataType.VARCHAR, field, literal); + } + + return result.withinGroupOrderBy(orderBy); + } + + @Override + public final WindowPartitionByStep over() { + throw new UnsupportedOperationException("OVER() not supported on GROUP_CONCAT aggregate function"); + } + + @Override + public final AggregateFunction separator(String s) { + this.separator = s; + return this; + } + + @Override + public final GroupConcatSeparatorStep orderBy(Field... fields) { + orderBy.addAll(fields); + return this; + } + + @Override + public final GroupConcatSeparatorStep orderBy(SortField... fields) { + orderBy.addAll(Arrays.asList(fields)); + return this; + } + + @Override + public final GroupConcatSeparatorStep orderBy(Collection> fields) { + orderBy.addAll(fields); + return this; + } +}