From 1d5ae25f2b0ec2b0e8469d3de911d01bebde8367 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Fri, 4 Oct 2013 17:59:25 +0200 Subject: [PATCH] [#2765] Add support for a database-agnostic GENERATE_SERIES(FROM, TO) table function --- .../test/_/testcases/TableFunctionTests.java | 128 ++++++++++++++++++ .../src/org/jooq/test/jOOQAbstractTest.java | 6 + jOOQ/src/main/java/org/jooq/impl/DSL.java | 42 ++++++ .../java/org/jooq/impl/GenerateSeries.java | 128 ++++++++++++++++++ 4 files changed, 304 insertions(+) create mode 100644 jOOQ-test/src/org/jooq/test/_/testcases/TableFunctionTests.java create mode 100644 jOOQ/src/main/java/org/jooq/impl/GenerateSeries.java diff --git a/jOOQ-test/src/org/jooq/test/_/testcases/TableFunctionTests.java b/jOOQ-test/src/org/jooq/test/_/testcases/TableFunctionTests.java new file mode 100644 index 0000000000..68782784ad --- /dev/null +++ b/jOOQ-test/src/org/jooq/test/_/testcases/TableFunctionTests.java @@ -0,0 +1,128 @@ +/** + * Copyright (c) 2009-2013, Data Geekery GmbH (http://www.datageekery.com) + * All rights reserved. + * + * This work is dual-licensed + * - under the Apache Software License 2.0 (the "ASL") + * - under the jOOQ License and Maintenance Agreement (the "jOOQ License") + * ============================================================================= + * You may choose which license applies to you: + * + * - If you're using this work with Open Source databases, you may choose + * either ASL or jOOQ License. + * - If you're using this work with at least one commercial database, you must + * choose jOOQ License + * + * For more information, please visit http://www.jooq.org/licenses + * + * Apache Software License 2.0: + * ----------------------------------------------------------------------------- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * jOOQ License and Maintenance Agreement: + * ----------------------------------------------------------------------------- + * Data Geekery grants the Customer the non-exclusive, timely limited and + * non-transferable license to install and use the Software under the terms of + * the jOOQ License and Maintenance Agreement. + * + * This library is distributed with a LIMITED WARRANTY. See the jOOQ License + * and Maintenance Agreement for more details: http://www.jooq.org/licensing + */ +package org.jooq.test._.testcases; + +import static java.util.Arrays.asList; +import static org.jooq.SQLDialect.CUBRID; +import static org.jooq.SQLDialect.ORACLE; +import static org.jooq.SQLDialect.POSTGRES; +import static org.jooq.impl.DSL.generateSeries; +import static org.jooq.impl.DSL.selectOne; +import static org.junit.Assert.assertEquals; + +import java.sql.Date; +import java.util.List; + +import org.jooq.Record1; +import org.jooq.Record2; +import org.jooq.Record3; +import org.jooq.Record6; +import org.jooq.Table; +import org.jooq.TableRecord; +import org.jooq.UpdatableRecord; +import org.jooq.test.BaseTest; +import org.jooq.test.jOOQAbstractTest; + +import org.junit.Test; + +public class TableFunctionTests< + A extends UpdatableRecord & Record6, + AP, + B extends UpdatableRecord, + S extends UpdatableRecord & Record1, + B2S extends UpdatableRecord & Record3, + BS extends UpdatableRecord, + L extends TableRecord & Record2, + X extends TableRecord, + DATE extends UpdatableRecord, + BOOL extends UpdatableRecord, + D extends UpdatableRecord, + T extends UpdatableRecord, + U extends TableRecord, + UU extends UpdatableRecord, + I extends TableRecord, + IPK extends UpdatableRecord, + T725 extends UpdatableRecord, + T639 extends UpdatableRecord, + T785 extends TableRecord> +extends BaseTest { + + public TableFunctionTests(jOOQAbstractTest delegate) { + super(delegate); + } + + @Test + public void testGenerateSeries() throws Exception { + if (!asList(CUBRID, POSTGRES, ORACLE).contains(dialect().family())) + return; + + List expected = asList(0, 1, 2, 3); + Table> series = generateSeries(0, 3); + Table> t = series.as("t", "a"); + + assertEquals( + expected, + create().select() + .from(series) + .orderBy(1) + .fetch(0, Integer.class)); + assertEquals( + 0, (int) + create().selectFrom(series) + .orderBy(1) + .fetch() + .get(0) + .value1()); + assertEquals( + expected, + create().select() + .from(t) + .orderBy(t.field("a")) + .fetch(t.field("a"))); + + assertEquals(3, create().fetchCount( + selectOne() + .from(t) + .join(TBook()) + .on(TBook_ID().eq(t.field("a").coerce(Integer.class))) + )); + } +} diff --git a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java index 1f36baa822..2e6eeba34d 100644 --- a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java +++ b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java @@ -144,6 +144,7 @@ import org.jooq.test._.testcases.RowValueExpressionTests; import org.jooq.test._.testcases.SchemaAndMappingTests; import org.jooq.test._.testcases.SelectTests; import org.jooq.test._.testcases.StatementTests; +import org.jooq.test._.testcases.TableFunctionTests; import org.jooq.test._.testcases.ThreadSafetyTests; import org.jooq.test._.testcases.TruncateTests; import org.jooq.test._.testcases.ValuesConstructorTests; @@ -2141,6 +2142,11 @@ public abstract class jOOQAbstractTest< @Test public void testStoredProceduresWithCursorParameters() throws Exception { new RoutineAndUDTTests(this).testStoredProceduresWithCursorParameters(); + } + + @Test + public void testGenerateSeries() throws Exception { + new TableFunctionTests(this).testGenerateSeries(); } @Test diff --git a/jOOQ/src/main/java/org/jooq/impl/DSL.java b/jOOQ/src/main/java/org/jooq/impl/DSL.java index 93995d14a3..fdf4b0cb5a 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DSL.java +++ b/jOOQ/src/main/java/org/jooq/impl/DSL.java @@ -4658,6 +4658,48 @@ public class DSL { throw new SQLDialectNotSupportedException("Converting arbitrary types into array tables is currently not supported"); } + // ------------------------------------------------------------------------- + // XXX Table functions + // ------------------------------------------------------------------------- + + /** + * A table function generating a series of values from from to + * to (inclusive). + *

+ * This function is inspired by PostgreSQL's + * GENERATE_SERIES(from, to) function. Other SQL dialects may + * be capable of emulating this behaviour, e.g. Oracle:

+     * -- PostgreSQL
+     * SELECT * FROM GENERATE_SERIES(a, b)
+     *
+     * -- Oracle
+     * SELECT * FROM (SELECT a + LEVEL - 1 FROM DUAL CONNECT BY a + LEVEL - 1 <= b)
+     * 
+ */ + @Support({ CUBRID, ORACLE, POSTGRES }) + public static Table> generateSeries(int from, int to) { + return generateSeries(val(from), val(to)); + } + + /** + * A table function generating a series of values from from to + * to (inclusive). + *

+ * This function is inspired by PostgreSQL's + * GENERATE_SERIES(from, to) function. Other SQL dialects may + * be capable of emulating this behaviour, e.g. Oracle:

+     * -- PostgreSQL
+     * SELECT * FROM GENERATE_SERIES(a, b)
+     *
+     * -- Oracle
+     * SELECT * FROM (SELECT a + LEVEL - 1 FROM DUAL CONNECT BY a + LEVEL - 1 <= b)
+     * 
+ */ + @Support({ CUBRID, ORACLE, POSTGRES }) + public static Table> generateSeries(Field from, Field to) { + return new GenerateSeries(nullSafe(from), nullSafe(to)); + } + // ------------------------------------------------------------------------- // XXX SQL keywords // ------------------------------------------------------------------------- diff --git a/jOOQ/src/main/java/org/jooq/impl/GenerateSeries.java b/jOOQ/src/main/java/org/jooq/impl/GenerateSeries.java new file mode 100644 index 0000000000..a2870ca4a5 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/GenerateSeries.java @@ -0,0 +1,128 @@ +/** + * Copyright (c) 2009-2013, Data Geekery GmbH (http://www.datageekery.com) + * All rights reserved. + * + * This work is dual-licensed + * - under the Apache Software License 2.0 (the "ASL") + * - under the jOOQ License and Maintenance Agreement (the "jOOQ License") + * ============================================================================= + * You may choose which license applies to you: + * + * - If you're using this work with Open Source databases, you may choose + * either ASL or jOOQ License. + * - If you're using this work with at least one commercial database, you must + * choose jOOQ License + * + * For more information, please visit http://www.jooq.org/licenses + * + * Apache Software License 2.0: + * ----------------------------------------------------------------------------- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * jOOQ License and Maintenance Agreement: + * ----------------------------------------------------------------------------- + * Data Geekery grants the Customer the non-exclusive, timely limited and + * non-transferable license to install and use the Software under the terms of + * the jOOQ License and Maintenance Agreement. + * + * This library is distributed with a LIMITED WARRANTY. See the jOOQ License + * and Maintenance Agreement for more details: http://www.jooq.org/licensing + */ +package org.jooq.impl; + +import static org.jooq.impl.DSL.level; +import static org.jooq.impl.DSL.name; +import static org.jooq.impl.DSL.one; +import static org.jooq.impl.DSL.table; + +import org.jooq.BindContext; +import org.jooq.Configuration; +import org.jooq.Field; +import org.jooq.QueryPart; +import org.jooq.Record1; +import org.jooq.RenderContext; +import org.jooq.Table; + +/** + * @author Lukas Eder + */ +class GenerateSeries extends AbstractTable> { + + /** + * Generated UID + */ + private static final long serialVersionUID = 2385574114457239818L; + + private final Field from; + private final Field to; + + GenerateSeries(Field from, Field to) { + super("generate_series"); + + this.from = from; + this.to = to; + } + + @Override + public final void toSQL(RenderContext ctx) { + ctx.visit(delegate(ctx.configuration())); + } + + @Override + public final void bind(BindContext ctx) { + ctx.visit(delegate(ctx.configuration())); + } + + private final QueryPart delegate(Configuration configuration) { + switch (configuration.dialect().family()) { + case CUBRID: + /* [pro] */ + case ORACLE: + /* [/pro] */ + + // There is a bug in CUBRID preventing reuse of "level" in the + // predicate http://jira.cubrid.org/browse/ENGINE-119 + Field level = from.add(level()).sub(one()); + return table("({select} {0} {as} {1} {from} {2} {connect by} {level} <= {3})", + level, + name("generate_series"), + new Dual(), + to.add(one()).sub(from)); + + case POSTGRES: + default: + return table("{generate_series}({0}, {1})", from, to); + } + } + + @SuppressWarnings("unchecked") + @Override + public final Class> getRecordType() { + return (Class>) RecordImpl.class; + } + + @Override + public final Table> as(String alias) { + return new TableAlias>(this, alias); + } + + @Override + public final Table> as(String alias, String... fieldAliases) { + return new TableAlias>(this, alias, fieldAliases); + } + + @Override + final Fields> fields0() { + return new Fields>(DSL.fieldByName(Integer.class, "generate_series")); + } +}