[#2765] Add support for a database-agnostic GENERATE_SERIES(FROM, TO) table function

This commit is contained in:
Lukas Eder 2013-10-04 17:59:25 +02:00
parent a5f36eb2b7
commit 1d5ae25f2b
4 changed files with 304 additions and 0 deletions

View File

@ -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<A> & Record6<Integer, String, String, Date, Integer, ?>,
AP,
B extends UpdatableRecord<B>,
S extends UpdatableRecord<S> & Record1<String>,
B2S extends UpdatableRecord<B2S> & Record3<String, Integer, Integer>,
BS extends UpdatableRecord<BS>,
L extends TableRecord<L> & Record2<String, String>,
X extends TableRecord<X>,
DATE extends UpdatableRecord<DATE>,
BOOL extends UpdatableRecord<BOOL>,
D extends UpdatableRecord<D>,
T extends UpdatableRecord<T>,
U extends TableRecord<U>,
UU extends UpdatableRecord<UU>,
I extends TableRecord<I>,
IPK extends UpdatableRecord<IPK>,
T725 extends UpdatableRecord<T725>,
T639 extends UpdatableRecord<T639>,
T785 extends TableRecord<T785>>
extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, UU, I, IPK, T725, T639, T785> {
public TableFunctionTests(jOOQAbstractTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, UU, I, IPK, T725, T639, T785> delegate) {
super(delegate);
}
@Test
public void testGenerateSeries() throws Exception {
if (!asList(CUBRID, POSTGRES, ORACLE).contains(dialect().family()))
return;
List<Integer> expected = asList(0, 1, 2, 3);
Table<Record1<Integer>> series = generateSeries(0, 3);
Table<Record1<Integer>> 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)))
));
}
}

View File

@ -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

View File

@ -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 <code>from</code> to
* <code>to</code> (inclusive).
* <p>
* This function is inspired by PostgreSQL's
* <code>GENERATE_SERIES(from, to)</code> function. Other SQL dialects may
* be capable of emulating this behaviour, e.g. Oracle: <code><pre>
* -- PostgreSQL
* SELECT * FROM GENERATE_SERIES(a, b)
*
* -- Oracle
* SELECT * FROM (SELECT a + LEVEL - 1 FROM DUAL CONNECT BY a + LEVEL - 1 &lt;= b)
* </pre></code>
*/
@Support({ CUBRID, ORACLE, POSTGRES })
public static Table<Record1<Integer>> generateSeries(int from, int to) {
return generateSeries(val(from), val(to));
}
/**
* A table function generating a series of values from <code>from</code> to
* <code>to</code> (inclusive).
* <p>
* This function is inspired by PostgreSQL's
* <code>GENERATE_SERIES(from, to)</code> function. Other SQL dialects may
* be capable of emulating this behaviour, e.g. Oracle: <code><pre>
* -- PostgreSQL
* SELECT * FROM GENERATE_SERIES(a, b)
*
* -- Oracle
* SELECT * FROM (SELECT a + LEVEL - 1 FROM DUAL CONNECT BY a + LEVEL - 1 &lt;= b)
* </pre></code>
*/
@Support({ CUBRID, ORACLE, POSTGRES })
public static Table<Record1<Integer>> generateSeries(Field<Integer> from, Field<Integer> to) {
return new GenerateSeries(nullSafe(from), nullSafe(to));
}
// -------------------------------------------------------------------------
// XXX SQL keywords
// -------------------------------------------------------------------------

View File

@ -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<Record1<Integer>> {
/**
* Generated UID
*/
private static final long serialVersionUID = 2385574114457239818L;
private final Field<Integer> from;
private final Field<Integer> to;
GenerateSeries(Field<Integer> from, Field<Integer> 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<Integer> 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<? extends Record1<Integer>> getRecordType() {
return (Class<? extends Record1<Integer>>) RecordImpl.class;
}
@Override
public final Table<Record1<Integer>> as(String alias) {
return new TableAlias<Record1<Integer>>(this, alias);
}
@Override
public final Table<Record1<Integer>> as(String alias, String... fieldAliases) {
return new TableAlias<Record1<Integer>>(this, alias, fieldAliases);
}
@Override
final Fields<Record1<Integer>> fields0() {
return new Fields<Record1<Integer>>(DSL.fieldByName(Integer.class, "generate_series"));
}
}