From 050ae4257bbdbe0f677ae5b8bc3b0bc1034a25dd Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Mon, 9 Jan 2012 22:55:42 +0000 Subject: [PATCH] [#1048] Simulate (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 --- .../src/org/jooq/test/jOOQAbstractTest.java | 207 ++++++++++-------- jOOQ/src/main/java/org/jooq/Field.java | 24 +- .../java/org/jooq/impl/AliasProviderImpl.java | 4 +- .../jooq/impl/ArrayAsSubqueryCondition.java | 63 +++--- .../main/java/org/jooq/impl/ArrayTable.java | 206 ++++++++++++----- .../org/jooq/impl/ArrayTableSimulation.java | 151 +++++++++++++ jOOQ/src/main/java/org/jooq/impl/Factory.java | 6 +- .../main/java/org/jooq/impl/Qualifier.java | 95 ++++++++ 8 files changed, 556 insertions(+), 200 deletions(-) create mode 100644 jOOQ/src/main/java/org/jooq/impl/ArrayTableSimulation.java create mode 100644 jOOQ/src/main/java/org/jooq/impl/Qualifier.java diff --git a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java index 378b09b5e3..d6347e31bc 100644 --- a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java +++ b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java @@ -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 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 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") diff --git a/jOOQ/src/main/java/org/jooq/Field.java b/jOOQ/src/main/java/org/jooq/Field.java index bed1a7b0ce..a3625e5528 100644 --- a/jOOQ/src/main/java/org/jooq/Field.java +++ b/jOOQ/src/main/java/org/jooq/Field.java @@ -517,7 +517,7 @@ public interface Field extends NamedTypeProviderQueryPart, AliasProvider extends NamedTypeProviderQueryPart, AliasProvider extends NamedTypeProviderQueryPart, AliasProvider extends NamedTypeProviderQueryPart, AliasProvider extends NamedTypeProviderQueryPart, AliasProvider extends NamedTypeProviderQueryPart, AliasProvider extends NamedTypeProviderQueryPart, AliasProvider extends NamedTypeProviderQueryPart, AliasProvider extends NamedTypeProviderQueryPart, AliasProvider extends NamedTypeProviderQueryPart, AliasProvider extends NamedTypeProviderQueryPart, AliasProvider extends NamedTypeProviderQueryPart, AliasProvider> 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> 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()); diff --git a/jOOQ/src/main/java/org/jooq/impl/ArrayAsSubqueryCondition.java b/jOOQ/src/main/java/org/jooq/impl/ArrayAsSubqueryCondition.java index be8acedd65..dc6c558a13 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ArrayAsSubqueryCondition.java +++ b/jOOQ/src/main/java/org/jooq/impl/ArrayAsSubqueryCondition.java @@ -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 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)); + } + } } } diff --git a/jOOQ/src/main/java/org/jooq/impl/ArrayTable.java b/jOOQ/src/main/java/org/jooq/impl/ArrayTable.java index c472472fa3..09bafc4ebd 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ArrayTable.java +++ b/jOOQ/src/main/java/org/jooq/impl/ArrayTable.java @@ -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 extends AbstractTable { +class ArrayTable extends AbstractTable { /** * 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 extends AbstractTable { this.array = array; this.alias = alias; this.field = new FieldList(); - this.field.add(field("COLUMN_VALUE", arrayType)); - } - - @SuppressWarnings("unchecked") - @Override - public final Class getRecordType() { - return (Class) RecordImpl.class; + this.field.add(new Qualifier(Factory.getDataType(arrayType), alias, "COLUMN_VALUE")); } @Override - public final Table as(String as) { - return new TableAlias(new ArrayTable(array, ""), as); + public final Class getRecordType() { + return RecordImpl.class; + } + + @Override + public final Table 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 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 { + + /** + * Generated UID + */ + private static final long serialVersionUID = 2662639259338694177L; + + DialectArrayTable() { + super(alias); + } + + @Override + public Class getRecordType() { + return RecordImpl.class; + } + + @Override + public Table as(String as) { + return new TableAlias(this, as); + } + + @Override + public void bind(BindContext context) throws DataAccessException { + context.bind(array); + } + + @Override + protected FieldList getFieldList() { + return ArrayTable.this.getFieldList(); + } + + @Override + protected List getAttachables0() { + return Collections.emptyList(); + } + } + + @SuppressWarnings("unchecked") + private final ArrayTableSimulation simulate() { + return new ArrayTableSimulation(((Param) array).getValue(), alias); + } + @Override protected final FieldList getFieldList() { return field; diff --git a/jOOQ/src/main/java/org/jooq/impl/ArrayTableSimulation.java b/jOOQ/src/main/java/org/jooq/impl/ArrayTableSimulation.java new file mode 100644 index 0000000000..5935378878 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/ArrayTableSimulation.java @@ -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 UNION ALL + * + * @author Lukas Eder + */ +class ArrayTableSimulation extends AbstractTable { + + /** + * Generated UID + */ + private static final long serialVersionUID = 2392515064450536343L; + + private final Object[] array; + private final FieldList field; + private final String alias; + + private transient Table 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 getRecordType() { + return RecordImpl.class; + } + + @Override + public final Table 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 getAttachables0() { + return Collections.emptyList(); + } + + private final Table table(Configuration configuration) { + if (table == null) { + Select select = null; + + for (Field val : vals(array)) { + Select 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; + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/Factory.java b/jOOQ/src/main/java/org/jooq/impl/Factory.java index 27d454389c..e54931e85b 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Factory.java +++ b/jOOQ/src/main/java/org/jooq/impl/Factory.java @@ -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(cursor); + return new ArrayTable(cursor); } // The field is an Oracle-style VARRAY field else if (ArrayRecord.class.isAssignableFrom(cursor.getDataType().getType())) { - return new ArrayTable(cursor); + return new ArrayTable(cursor); } // The field is a regular array else if (cursor.getType().isArray() && cursor.getType() != byte[].class) { - return new ArrayTable(cursor); + return new ArrayTable(cursor); } // The field has any other type. Try to make it an array diff --git a/jOOQ/src/main/java/org/jooq/impl/Qualifier.java b/jOOQ/src/main/java/org/jooq/impl/Qualifier.java new file mode 100644 index 0000000000..f89c5f367f --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/Qualifier.java @@ -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 Qualifier 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 extends AbstractField { + + /** + * Generated UID + */ + private static final long serialVersionUID = 6937002867156868761L; + + private final String[] sql; + + Qualifier(DataType type, String... sql) { + super(sql[sql.length - 1], type); + + this.sql = sql; + } + + // ------------------------------------------------------------------------ + // Field API + // ------------------------------------------------------------------------ + + @Override + public final List 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; + } +}