[#1048] Simulate <op> <quantifier> (array) syntax for dialects that do not support arrays

[#1055] Simulate Factory.table(Object[]) and table(List<?>) using UNION ALL in dialects that do not support arrays
This commit is contained in:
Lukas Eder 2012-01-09 22:55:42 +00:00
parent 2ad7fb795f
commit 050ae4257b
8 changed files with 556 additions and 200 deletions

View File

@ -3105,28 +3105,25 @@ public abstract class jOOQAbstractTest<
.where(TBook_ID().equalAny(create().select(TBook_ID()).from(TBook()).where(TBook_ID().in(1, 2))))
.orderBy(TBook_ID()).fetch(TBook_ID()));
// [#1048] TODO: Simulate this for other dialects
if (asList(H2, HSQLDB, POSTGRES).contains(getDialect())) {
// Testing = ALL(array)
assertEquals(Arrays.asList(1), create().select(TBook_ID())
.from(TBook())
.where(TBook_ID().equalAll(1))
.orderBy(TBook_ID()).fetch(TBook_ID()));
assertEquals(Arrays.asList(), create().select(TBook_ID())
.from(TBook())
.where(TBook_ID().equalAll(1, 2))
.orderBy(TBook_ID()).fetch(TBook_ID()));
// Testing = ALL(array)
assertEquals(Arrays.asList(1), create().select(TBook_ID())
.from(TBook())
.where(TBook_ID().equalAll(1))
.orderBy(TBook_ID()).fetch(TBook_ID()));
assertEquals(Arrays.asList(), create().select(TBook_ID())
.from(TBook())
.where(TBook_ID().equalAll(1, 2))
.orderBy(TBook_ID()).fetch(TBook_ID()));
// Testing = ANY(array)
assertEquals(Arrays.asList(1), create().select(TBook_ID())
.from(TBook())
.where(TBook_ID().equalAny(1))
.orderBy(TBook_ID()).fetch(TBook_ID()));
assertEquals(Arrays.asList(1, 2), create().select(TBook_ID())
.from(TBook())
.where(TBook_ID().equalAny(1, 2))
.orderBy(TBook_ID()).fetch(TBook_ID()));
}
// Testing = ANY(array)
assertEquals(Arrays.asList(1), create().select(TBook_ID())
.from(TBook())
.where(TBook_ID().equalAny(1))
.orderBy(TBook_ID()).fetch(TBook_ID()));
assertEquals(Arrays.asList(1, 2), create().select(TBook_ID())
.from(TBook())
.where(TBook_ID().equalAny(1, 2))
.orderBy(TBook_ID()).fetch(TBook_ID()));
// Inducing the above to work the same way as all other operators
// Check all operators in a single query
@ -3135,22 +3132,34 @@ public abstract class jOOQAbstractTest<
.from(TBook())
.where(TBook_ID().equal(create().select(val(3))))
.and(TBook_ID().equalAll(create().select(val(3))))
.and(TBook_ID().equalAll(3, 3))
.and(TBook_ID().equalAny(create().select(TBook_ID()).from(TBook()).where(TBook_ID().in(3, 4))))
.and(TBook_ID().equalAny(3, 4))
.and(TBook_ID().notEqual(create().select(val(1))))
.and(TBook_ID().notEqualAll(create().select(TBook_ID()).from(TBook()).where(TBook_ID().in(1, 4))))
.and(TBook_ID().notEqualAll(1, 4, 4))
.and(TBook_ID().notEqualAny(create().select(TBook_ID()).from(TBook()).where(TBook_ID().in(1, 4))))
.and(TBook_ID().notEqualAny(1, 4, 4))
.and(TBook_ID().greaterOrEqual(create().select(val(1))))
.and(TBook_ID().greaterOrEqualAll(create().select(TBook_ID()).from(TBook()).where(TBook_ID().in(1, 2))))
.and(TBook_ID().greaterOrEqualAll(1, 2))
.and(TBook_ID().greaterOrEqualAny(create().select(TBook_ID()).from(TBook()).where(TBook_ID().in(1, 4))))
.and(TBook_ID().greaterOrEqualAny(1, 4))
.and(TBook_ID().greaterThan(create().select(val(1))))
.and(TBook_ID().greaterThanAll(create().select(TBook_ID()).from(TBook()).where(TBook_ID().in(1, 2))))
.and(TBook_ID().greaterThanAll(1, 2))
.and(TBook_ID().greaterThanAny(create().select(TBook_ID()).from(TBook()).where(TBook_ID().in(1, 4))))
.and(TBook_ID().greaterThanAny(1, 4))
.and(TBook_ID().lessOrEqual(create().select(val(3))))
.and(TBook_ID().lessOrEqualAll(create().select(TBook_ID()).from(TBook()).where(TBook_ID().in(3, 4))))
.and(TBook_ID().lessOrEqualAll(3, 4))
.and(TBook_ID().lessOrEqualAny(create().select(TBook_ID()).from(TBook()).where(TBook_ID().in(1, 4))))
.and(TBook_ID().lessOrEqualAny(1, 4))
.and(TBook_ID().lessThan(create().select(val(4))))
.and(TBook_ID().lessThanAll(create().select(val(4))))
.and(TBook_ID().lessThanAll(4, 5))
.and(TBook_ID().lessThanAny(create().select(TBook_ID()).from(TBook()).where(TBook_ID().in(1, 4))))
.and(TBook_ID().lessThanAny(1, 4))
.fetch(TBook_ID()));
break;
@ -8752,76 +8761,8 @@ public abstract class jOOQAbstractTest<
}
else if (TArrays_NUMBER() != null) {
Result<Record> result;
// An empty array
// --------------
Integer[] array = new Integer[0];
result = create().select().from(table(new Integer[0])).fetch();
assertEquals(0, result.size());
assertEquals(1, result.getFields().size());
// An array containing null
// ------------------------
array = new Integer[] { null };
result = create().select().from(table(array)).fetch();
assertEquals(1, result.size());
assertEquals(1, result.getFields().size());
assertEquals(null, result.getValue(0, 0));
// An array containing two values
// ------------------------------
array = new Integer[] { null, 1 };
result = create().select().from(table(array)).fetch();
assertEquals(2, result.size());
assertEquals(1, result.getFields().size());
assertEquals(null, result.getValue(0, 0));
assertEquals(1, result.getValue(1, 0));
// An array containing three values
// --------------------------------
array = new Integer[] { null, 1, 2 };
result = create().select().from(table(array)).fetch();
assertEquals(3, result.size());
assertEquals(1, result.getFields().size());
assertEquals(null, result.getValue(0, 0));
assertEquals(1, result.getValue(1, 0));
assertEquals(2, result.getValue(2, 0));
// Joining an unnested array table
// -------------------------------
array = new Integer[] { 2, 3 };
Table<?> table = table(array);
result = create()
.select(TBook_ID(), TBook_TITLE())
.from(TBook())
.join(table)
.on(table.getField(0).cast(Integer.class).equal(TBook_ID()))
.fetch();
assertEquals(2, result.size());
assertEquals(Integer.valueOf(2), result.getValue(0, TBook_ID()));
assertEquals(Integer.valueOf(3), result.getValue(1, TBook_ID()));
assertEquals("Animal Farm", result.getValue(0, TBook_TITLE()));
assertEquals("O Alquimista", result.getValue(1, TBook_TITLE()));
// Joining an aliased unnested array table
// ---------------------------------------
result = create()
.select(TBook_ID(), TBook_TITLE())
.from(TBook())
.join(table.as("t"))
.on(table.as("t").getField(0).cast(Integer.class).equal(TBook_ID()))
.fetch();
assertEquals(2, result.size());
assertEquals(Integer.valueOf(2), result.getValue(0, TBook_ID()));
assertEquals(Integer.valueOf(3), result.getValue(1, TBook_ID()));
assertEquals("Animal Farm", result.getValue(0, TBook_TITLE()));
assertEquals("O Alquimista", result.getValue(1, TBook_TITLE()));
Table<?> table;
Integer[] array;
// Cross join the array table with the unnested string array value
// ---------------------------------------------------------------
@ -8883,6 +8824,80 @@ public abstract class jOOQAbstractTest<
}
}
@Test
public void testArrayTableSimulation() throws Exception {
Result<Record> result;
// An empty array
// --------------
Integer[] array = new Integer[0];
result = create().select().from(table(new Integer[0])).fetch();
assertEquals(0, result.size());
assertEquals(1, result.getFields().size());
// An array containing null
// ------------------------
array = new Integer[] { null };
result = create().select().from(table(array)).fetch();
assertEquals(1, result.size());
assertEquals(1, result.getFields().size());
assertEquals(null, result.getValue(0, 0));
// An array containing two values (some DB's can't guarantee ordering)
// -------------------------------------------------------------------
array = new Integer[] { null, 1 };
result = create().select().from(table(array)).fetch();
assertEquals(2, result.size());
assertEquals(1, result.getFields().size());
assertTrue(asList(array).containsAll(result.getValues(0)));
// An array containing three values (some DB's can't guarantee ordering)
// ---------------------------------------------------------------------
array = new Integer[] { null, 1, 2 };
result = create().select().from(table(array)).fetch();
assertEquals(3, result.size());
assertEquals(1, result.getFields().size());
assertTrue(asList(array).containsAll(result.getValues(0)));
// Joining an unnested array table
// -------------------------------
array = new Integer[] { 2, 3 };
Table<?> table = table(array);
result = create()
.select(TBook_ID(), TBook_TITLE())
.from(TBook())
.join(table)
.on(table.getField(0).cast(Integer.class).equal(TBook_ID()))
.orderBy(TBook_ID().asc())
.fetch();
assertEquals(2, result.size());
assertEquals(Integer.valueOf(2), result.getValue(0, TBook_ID()));
assertEquals(Integer.valueOf(3), result.getValue(1, TBook_ID()));
assertEquals("Animal Farm", result.getValue(0, TBook_TITLE()));
assertEquals("O Alquimista", result.getValue(1, TBook_TITLE()));
// Joining an aliased unnested array table
// ---------------------------------------
result = create()
.select(TBook_ID(), TBook_TITLE())
.from(TBook())
.join(table.as("t"))
.on(table.as("t").getField(0).cast(Integer.class).equal(TBook_ID()))
.orderBy(TBook_ID().asc())
.fetch();
assertEquals(2, result.size());
assertEquals(Integer.valueOf(2), result.getValue(0, TBook_ID()));
assertEquals(Integer.valueOf(3), result.getValue(1, TBook_ID()));
assertEquals("Animal Farm", result.getValue(0, TBook_TITLE()));
assertEquals("O Alquimista", result.getValue(1, TBook_TITLE()));
}
@Test
public void testStoredProceduresWithCursorParameters() throws Exception {
switch (getDialect()) {
@ -9566,7 +9581,7 @@ public abstract class jOOQAbstractTest<
create().loadInto(TAuthor())
.loadCSV(
"####Some Data####\n" +
"\"ID\",\"Last Name\"\r" +
"\"ID\",\"Last Qualifier\"\r" +
"3,Hesse\n" +
"4,Frisch")
.fields(TAuthor_ID(), TAuthor_LAST_NAME())
@ -9592,7 +9607,7 @@ public abstract class jOOQAbstractTest<
loader =
create().loadInto(TAuthor())
.loadCSV(
"\"ID\",\"First Name\",\"Last Name\"\r" +
"\"ID\",\"First Qualifier\",\"Last Qualifier\"\r" +
"5,Hermann,Hesse\n" +
"6,\"Max\",Frisch")
.fields(TAuthor_ID(), null, TAuthor_LAST_NAME())
@ -9638,7 +9653,7 @@ public abstract class jOOQAbstractTest<
create().loadInto(TAuthor())
.onDuplicateKeyUpdate()
.loadCSV(
"\"ID\",\"First Name\",\"Last Name\"\r" +
"\"ID\",\"First Qualifier\",\"Last Qualifier\"\r" +
"1,Hermann,Hesse\n" +
"7,\"Max\",Frisch")
.fields(TAuthor_ID(), null, TAuthor_LAST_NAME())
@ -9678,7 +9693,7 @@ public abstract class jOOQAbstractTest<
.onDuplicateKeyError()
.onErrorAbort()
.loadCSV(
"\"ID\",\"First Name\",\"Last Name\"\r" +
"\"ID\",\"First Qualifier\",\"Last Qualifier\"\r" +
"8,Hermann,Hesse\n" +
"1,\"Max\",Frisch\n" +
"2,Friedrich,Dürrenmatt")
@ -9710,7 +9725,7 @@ public abstract class jOOQAbstractTest<
.onDuplicateKeyIgnore()
.onErrorAbort()
.loadCSV(
"\"ID\",\"First Name\",\"Last Name\"\r" +
"\"ID\",\"First Qualifier\",\"Last Qualifier\"\r" +
"8,Hermann,Hesse\n" +
"1,\"Max\",Frisch\n" +
"2,Friedrich,Dürrenmatt")

View File

@ -517,7 +517,7 @@ public interface Field<T> extends NamedTypeProviderQueryPart<T>, AliasProvider<F
* This is natively supported by {@link SQLDialect#POSTGRES}. Other dialects
* will render a subselect unnesting the array.
*/
@Support({ H2, HSQLDB, POSTGRES })
@Support({ ASE, DB2, DERBY, H2, HSQLDB, INGRES, MYSQL, ORACLE, POSTGRES, SQLSERVER, SYBASE })
Condition equalAny(T... array);
/**
@ -550,7 +550,7 @@ public interface Field<T> extends NamedTypeProviderQueryPart<T>, AliasProvider<F
* This is natively supported by {@link SQLDialect#POSTGRES}. Other dialects
* will render a subselect unnesting the array.
*/
@Support({ H2, HSQLDB, POSTGRES })
@Support({ ASE, DB2, DERBY, H2, HSQLDB, INGRES, MYSQL, ORACLE, POSTGRES, SQLSERVER, SYBASE })
Condition equalAll(T... array);
/**
@ -596,7 +596,7 @@ public interface Field<T> extends NamedTypeProviderQueryPart<T>, AliasProvider<F
* This is natively supported by {@link SQLDialect#POSTGRES}. Other dialects
* will render a subselect unnesting the array.
*/
@Support({ H2, HSQLDB, POSTGRES })
@Support({ ASE, DB2, DERBY, H2, HSQLDB, INGRES, MYSQL, ORACLE, POSTGRES, SQLSERVER, SYBASE })
Condition notEqualAny(T... array);
/**
@ -629,7 +629,7 @@ public interface Field<T> extends NamedTypeProviderQueryPart<T>, AliasProvider<F
* This is natively supported by {@link SQLDialect#POSTGRES}. Other dialects
* will render a subselect unnesting the array.
*/
@Support({ H2, HSQLDB, POSTGRES })
@Support({ ASE, DB2, DERBY, H2, HSQLDB, INGRES, MYSQL, ORACLE, POSTGRES, SQLSERVER, SYBASE })
Condition notEqualAll(T... array);
/**
@ -671,7 +671,7 @@ public interface Field<T> extends NamedTypeProviderQueryPart<T>, AliasProvider<F
* This is natively supported by {@link SQLDialect#POSTGRES}. Other dialects
* will render a subselect unnesting the array.
*/
@Support({ H2, HSQLDB, POSTGRES })
@Support({ ASE, DB2, DERBY, H2, HSQLDB, INGRES, MYSQL, ORACLE, POSTGRES, SQLSERVER, SYBASE })
Condition lessThanAny(T... array);
/**
@ -704,7 +704,7 @@ public interface Field<T> extends NamedTypeProviderQueryPart<T>, AliasProvider<F
* This is natively supported by {@link SQLDialect#POSTGRES}. Other dialects
* will render a subselect unnesting the array.
*/
@Support({ H2, HSQLDB, POSTGRES })
@Support({ ASE, DB2, DERBY, H2, HSQLDB, INGRES, MYSQL, ORACLE, POSTGRES, SQLSERVER, SYBASE })
Condition lessThanAll(T... array);
/**
@ -746,7 +746,7 @@ public interface Field<T> extends NamedTypeProviderQueryPart<T>, AliasProvider<F
* This is natively supported by {@link SQLDialect#POSTGRES}. Other dialects
* will render a subselect unnesting the array.
*/
@Support({ H2, HSQLDB, POSTGRES })
@Support({ ASE, DB2, DERBY, H2, HSQLDB, INGRES, MYSQL, ORACLE, POSTGRES, SQLSERVER, SYBASE })
Condition lessOrEqualAny(T... array);
/**
@ -779,7 +779,7 @@ public interface Field<T> extends NamedTypeProviderQueryPart<T>, AliasProvider<F
* This is natively supported by {@link SQLDialect#POSTGRES}. Other dialects
* will render a subselect unnesting the array.
*/
@Support({ H2, HSQLDB, POSTGRES })
@Support({ ASE, DB2, DERBY, H2, HSQLDB, INGRES, MYSQL, ORACLE, POSTGRES, SQLSERVER, SYBASE })
Condition lessOrEqualAll(T... array);
/**
@ -821,7 +821,7 @@ public interface Field<T> extends NamedTypeProviderQueryPart<T>, AliasProvider<F
* This is natively supported by {@link SQLDialect#POSTGRES}. Other dialects
* will render a subselect unnesting the array.
*/
@Support({ H2, HSQLDB, POSTGRES })
@Support({ ASE, DB2, DERBY, H2, HSQLDB, INGRES, MYSQL, ORACLE, POSTGRES, SQLSERVER, SYBASE })
Condition greaterThanAny(T... array);
/**
@ -854,7 +854,7 @@ public interface Field<T> extends NamedTypeProviderQueryPart<T>, AliasProvider<F
* This is natively supported by {@link SQLDialect#POSTGRES}. Other dialects
* will render a subselect unnesting the array.
*/
@Support({ H2, HSQLDB, POSTGRES })
@Support({ ASE, DB2, DERBY, H2, HSQLDB, INGRES, MYSQL, ORACLE, POSTGRES, SQLSERVER, SYBASE })
Condition greaterThanAll(T... array);
/**
@ -896,7 +896,7 @@ public interface Field<T> extends NamedTypeProviderQueryPart<T>, AliasProvider<F
* This is natively supported by {@link SQLDialect#POSTGRES}. Other dialects
* will render a subselect unnesting the array.
*/
@Support({ H2, HSQLDB, POSTGRES })
@Support({ ASE, DB2, DERBY, H2, HSQLDB, INGRES, MYSQL, ORACLE, POSTGRES, SQLSERVER, SYBASE })
Condition greaterOrEqualAny(T... array);
/**
@ -929,7 +929,7 @@ public interface Field<T> extends NamedTypeProviderQueryPart<T>, AliasProvider<F
* This is natively supported by {@link SQLDialect#POSTGRES}. Other dialects
* will render a subselect unnesting the array.
*/
@Support({ H2, HSQLDB, POSTGRES })
@Support({ ASE, DB2, DERBY, H2, HSQLDB, INGRES, MYSQL, ORACLE, POSTGRES, SQLSERVER, SYBASE })
Condition greaterOrEqualAll(T... array);
/**

View File

@ -107,6 +107,8 @@ class AliasProviderImpl<T extends AliasProvider<T>> extends AbstractNamedQueryPa
//
// SELECT t.column_value FROM UNNEST(ARRAY[1, 2]) AS t(column_value)
// TODO: Is this still needed?
switch (context.getDialect()) {
case HSQLDB:
case POSTGRES: {
@ -114,7 +116,7 @@ class AliasProviderImpl<T extends AliasProvider<T>> extends AbstractNamedQueryPa
// The javac compiler doesn't like casting of generics
Object o = aliasProvider;
ArrayTable<?> table = (ArrayTable<?>) o;
ArrayTable table = (ArrayTable) o;
context.sql("(");
Util.toSQLNames(context, table.getFields());

View File

@ -36,15 +36,15 @@
package org.jooq.impl;
import static org.jooq.impl.Factory.table;
import static org.jooq.impl.Factory.val;
import java.util.List;
import org.jooq.Attachable;
import org.jooq.BindContext;
import org.jooq.Configuration;
import org.jooq.Field;
import org.jooq.QueryPart;
import org.jooq.RenderContext;
import org.jooq.exception.SQLDialectNotSupportedException;
/**
* @author Lukas Eder
@ -73,40 +73,37 @@ class ArrayAsSubqueryCondition<T> extends AbstractCondition {
@Override
public final void toSQL(RenderContext context) {
switch (context.getDialect()) {
// [#869] H2 and HSQLDB can simulate this syntax by unnesting
// the array in a subselect
case H2:
case HSQLDB: {
context.sql(field)
.sql(" ")
.sql(operator.toSQL())
.sql(" (")
.sql(create(context).select().from(table(array)))
.sql(")");
break;
}
// [#869] Postgres supports this syntax natively
case POSTGRES: {
context.sql(field)
.sql(" ")
.sql(operator.toSQL())
.sql(" (")
.sql(array)
.sql(")");
break;
}
default:
throw new SQLDialectNotSupportedException("Arrays as subquery conditions not yet supported by " + context.getDialect());
}
context.sql(field)
.sql(" ")
.sql(operator.toSQL())
.sql(" (")
.sql(array(context))
.sql(")");
}
@Override
public final void bind(BindContext context) {
context.bind(field).bind(val(array));
context.bind(field).bind(array(context));
}
private final QueryPart array(Configuration context) {
switch (context.getDialect()) {
// [#869] Postgres supports this syntax natively
case POSTGRES: {
return array;
}
// [#869] H2 and HSQLDB can simulate this syntax by unnesting
// the array in a subselect
case H2:
case HSQLDB:
// [#1048] All other dialects simulate unnesting of arrays using
// UNION ALL-connected subselects
default: {
return create(context).select().from(table(array));
}
}
}
}

View File

@ -35,41 +35,44 @@
*/
package org.jooq.impl;
import static org.jooq.impl.Factory.field;
import java.util.Collections;
import java.util.List;
import org.jooq.Attachable;
import org.jooq.BindContext;
import org.jooq.Configuration;
import org.jooq.Field;
import org.jooq.Param;
import org.jooq.Record;
import org.jooq.RenderContext;
import org.jooq.Table;
import org.jooq.exception.DataAccessException;
import org.jooq.exception.SQLDialectNotSupportedException;
import org.jooq.tools.StringUtils;
import org.jooq.util.h2.H2DataType;
/**
* An unnested array
*
* @author Lukas Eder
*/
class ArrayTable<R extends Record> extends AbstractTable<R> {
class ArrayTable extends AbstractTable<Record> {
/**
* Generated UID
*/
private static final long serialVersionUID = 2380426377794577041L;
private static final long serialVersionUID = 2380426377794577041L;
private final Field<?> array;
private final FieldList field;
private final String alias;
private final Field<?> array;
private final FieldList field;
private final String alias;
ArrayTable(Field<?> array) {
this(array, "array_table(COLUMN_VALUE)");
this(array, "array_table");
}
@SuppressWarnings({ "rawtypes", "unchecked" })
ArrayTable(Field<?> array, String alias) {
super("array_table");
super(alias);
Class<?> arrayType;
if (array.getDataType().getType().isArray()) {
@ -83,77 +86,170 @@ class ArrayTable<R extends Record> extends AbstractTable<R> {
this.array = array;
this.alias = alias;
this.field = new FieldList();
this.field.add(field("COLUMN_VALUE", arrayType));
}
@SuppressWarnings("unchecked")
@Override
public final Class<? extends R> getRecordType() {
return (Class<? extends R>) RecordImpl.class;
this.field.add(new Qualifier(Factory.getDataType(arrayType), alias, "COLUMN_VALUE"));
}
@Override
public final Table<R> as(String as) {
return new TableAlias<R>(new ArrayTable<R>(array, ""), as);
public final Class<? extends Record> getRecordType() {
return RecordImpl.class;
}
@Override
public final Table<Record> as(String as) {
return new ArrayTable(array, as);
}
@Override
public final boolean declaresTables() {
// Always true, because unnested tables are always aliased
return true;
}
@Override
public final void toSQL(RenderContext context) {
switch (context.getDialect()) {
context.sql(table(context));
}
@Override
public final void bind(BindContext context) {
context.bind(table(context));
}
private final Table<Record> table(Configuration configuration) {
switch (configuration.getDialect()) {
case ORACLE: {
context.sql("table(").sql(array).sql(")");
break;
if (array.getDataType().getType().isArray()) {
return simulate().as(alias);
}
else {
return new OracleArrayTable().as(alias);
}
}
case H2: {
context.sql("table(COLUMN_VALUE ");
// If the array type is unknown (e.g. because it's returned from a stored function
// Then the best choice for arbitrary types is varchar
if (array.getDataType().getType() == Object[].class) {
context.sql(H2DataType.VARCHAR.getTypeName());
}
else {
context.sql(array.getDataType().getTypeName());
}
context.sql(" = ").sql(array).sql(")");
break;
return new H2ArrayTable().as(alias);
}
// [#756] These dialects need special care when aliasing unnested
// arrays
case HSQLDB:
case POSTGRES: {
context.sql("unnest(").sql(array).sql(")");
if (!StringUtils.isBlank(alias)) {
context.sql(" as ").sql(alias);
}
break;
return new PostgresHSQLDBTable().as(alias);
}
default:
throw new SQLDialectNotSupportedException("ARRAY TABLE is not supported for " + context.getDialect());
// Other dialects can simulate unnested arrays using UNION ALL
default: {
if (array.getDataType().getType().isArray() && array instanceof Param) {
return simulate();
}
else {
throw new SQLDialectNotSupportedException("ARRAY TABLE is not supported for " + configuration.getDialect());
}
}
}
}
@Override
public final void bind(BindContext context) {
switch (context.getDialect()) {
case ORACLE:
case H2:
case HSQLDB:
case POSTGRES:
context.bind(array);
break;
private class PostgresHSQLDBTable extends DialectArrayTable {
default:
throw new SQLDialectNotSupportedException("ARRAY TABLE is not supported for " + context.getDialect());
/**
* Generated UID
*/
private static final long serialVersionUID = 6989279597964488457L;
@Override
public void toSQL(RenderContext context) {
context.sql("(select * from unnest(")
.sql(array)
.sql(") as ")
.literal(alias)
.sql("(")
.literal("COLUMN_VALUE")
.sql("))");
}
}
private class H2ArrayTable extends DialectArrayTable {
/**
* Generated UID
*/
private static final long serialVersionUID = 8679404596822098711L;
@Override
public void toSQL(RenderContext context) {
context.sql("table(COLUMN_VALUE ");
// If the array type is unknown (e.g. because it's returned from
// a stored function
// Then the best choice for arbitrary types is varchar
if (array.getDataType().getType() == Object[].class) {
context.sql(H2DataType.VARCHAR.getTypeName());
}
else {
context.sql(array.getDataType().getTypeName());
}
context.sql(" = ").sql(array).sql(")");
}
}
private class OracleArrayTable extends DialectArrayTable {
/**
* Generated UID
*/
private static final long serialVersionUID = 1716687061980551706L;
@Override
public void toSQL(RenderContext context) {
context.sql("table (").sql(array).sql(")");
}
}
private abstract class DialectArrayTable extends AbstractTable<Record> {
/**
* Generated UID
*/
private static final long serialVersionUID = 2662639259338694177L;
DialectArrayTable() {
super(alias);
}
@Override
public Class<? extends Record> getRecordType() {
return RecordImpl.class;
}
@Override
public Table<Record> as(String as) {
return new TableAlias<Record>(this, as);
}
@Override
public void bind(BindContext context) throws DataAccessException {
context.bind(array);
}
@Override
protected FieldList getFieldList() {
return ArrayTable.this.getFieldList();
}
@Override
protected List<Attachable> getAttachables0() {
return Collections.emptyList();
}
}
@SuppressWarnings("unchecked")
private final ArrayTableSimulation simulate() {
return new ArrayTableSimulation(((Param<Object[]>) array).getValue(), alias);
}
@Override
protected final FieldList getFieldList() {
return field;

View File

@ -0,0 +1,151 @@
/**
* 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.falseCondition;
import static org.jooq.impl.Factory.one;
import static org.jooq.impl.Factory.vals;
import java.util.Collections;
import java.util.List;
import org.jooq.Attachable;
import org.jooq.BindContext;
import org.jooq.Configuration;
import org.jooq.Field;
import org.jooq.Record;
import org.jooq.RenderContext;
import org.jooq.Select;
import org.jooq.Table;
import org.jooq.exception.DataAccessException;
/**
* Essentially, this is the same as {@link ArrayTable}, except that it simulates
* unnested arrays using <code>UNION ALL</code>
*
* @author Lukas Eder
*/
class ArrayTableSimulation extends AbstractTable<Record> {
/**
* Generated UID
*/
private static final long serialVersionUID = 2392515064450536343L;
private final Object[] array;
private final FieldList field;
private final String alias;
private transient Table<Record> table;
ArrayTableSimulation(Object[] array) {
this(array, "array_table");
}
@SuppressWarnings({ "unchecked", "rawtypes" })
ArrayTableSimulation(Object[] array, String alias) {
super(alias);
this.array = array;
this.field = new FieldList();
this.alias = alias;
this.field.add(new Qualifier(Factory.getDataType(array.getClass().getComponentType()), alias, "COLUMN_VALUE"));
}
@Override
public final Class<? extends Record> getRecordType() {
return RecordImpl.class;
}
@Override
public final Table<Record> as(String as) {
return new ArrayTableSimulation(array, as);
}
@Override
public final boolean declaresTables() {
// [#1055] Always true, because unnested tables are always aliased.
// This is particularly important for simulated unnested arrays
return true;
}
@Override
public final void toSQL(RenderContext context) {
context.sql(table(context));
}
@Override
public final void bind(BindContext context) throws DataAccessException {
context.bind(table(context));
}
@Override
protected final FieldList getFieldList() {
return field;
}
@Override
protected final List<Attachable> getAttachables0() {
return Collections.emptyList();
}
private final Table<Record> table(Configuration configuration) {
if (table == null) {
Select<Record> select = null;
for (Field<?> val : vals(array)) {
Select<Record> subselect = create(configuration).select(val.as("COLUMN_VALUE"));
if (select == null) {
select = subselect;
}
else {
select = select.unionAll(subselect);
}
}
// Empty arrays should result in empty tables
if (select == null) {
select = create(configuration).select(one().as("COLUMN_VALUE")).where(falseCondition());
}
table = select.asTable(alias);
}
return table;
}
}

View File

@ -455,17 +455,17 @@ public class Factory implements FactoryOperations {
// The field is an Oracle-style VARRAY constant
else if (ArrayConstant.class.isAssignableFrom(cursor.getClass())) {
return new ArrayTable<Record>(cursor);
return new ArrayTable(cursor);
}
// The field is an Oracle-style VARRAY field
else if (ArrayRecord.class.isAssignableFrom(cursor.getDataType().getType())) {
return new ArrayTable<Record>(cursor);
return new ArrayTable(cursor);
}
// The field is a regular array
else if (cursor.getType().isArray() && cursor.getType() != byte[].class) {
return new ArrayTable<Record>(cursor);
return new ArrayTable(cursor);
}
// The field has any other type. Try to make it an array

View File

@ -0,0 +1,95 @@
/**
* Copyright (c) 2009-2011, 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 java.util.Collections;
import java.util.List;
import org.jooq.Attachable;
import org.jooq.BindContext;
import org.jooq.DataType;
import org.jooq.Field;
import org.jooq.RenderContext;
/**
* A <code>Qualifier</code> is a {@link Field} that always renders a field name
* or alias as a literal using {@link RenderContext#literal(String)}
*
* @author Lukas Eder
*/
class Qualifier<T> extends AbstractField<T> {
/**
* Generated UID
*/
private static final long serialVersionUID = 6937002867156868761L;
private final String[] sql;
Qualifier(DataType<T> type, String... sql) {
super(sql[sql.length - 1], type);
this.sql = sql;
}
// ------------------------------------------------------------------------
// Field API
// ------------------------------------------------------------------------
@Override
public final List<Attachable> getAttachables() {
return Collections.emptyList();
}
@Override
public final void toSQL(RenderContext context) {
String separator = "";
for (String string : sql) {
context.sql(separator);
context.literal(string);
separator = ".";
}
}
@Override
public final void bind(BindContext context) {}
@Override
public final boolean isNullLiteral() {
return false;
}
}