From 1119c72bf6b107149334ae002e2467aec4d02aef Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Sun, 15 Jan 2012 22:23:27 +0000 Subject: [PATCH] [#578] Add KEY JOIN syntax to simulate joining using generated foreign keys - Added methods to the Table API (not yet Select API) --- .../src/org/jooq/test/jOOQAbstractTest.java | 57 ++++++++++++++++++ jOOQ/src/main/java/org/jooq/SelectOnStep.java | 37 ++++++++++++ jOOQ/src/main/java/org/jooq/SelectQuery.java | 36 ++++++++++++ jOOQ/src/main/java/org/jooq/TableOnStep.java | 50 ++++++++++++++++ .../main/java/org/jooq/impl/JoinTable.java | 58 +++++++++++++++++++ 5 files changed, 238 insertions(+) diff --git a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java index 12d144b94a..19a176911e 100644 --- a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java +++ b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java @@ -234,6 +234,7 @@ public abstract class jOOQAbstractTest< protected static final List BOOK_IDS_SHORT = Arrays.asList((short) 1, (short) 2, (short) 3, (short) 4); protected static final List BOOK_IDS = Arrays.asList(1, 2, 3, 4); + protected static final List BOOK_AUTHOR_IDS = Arrays.asList(1, 1, 2, 2); protected static final List BOOK_TITLES = Arrays.asList("1984", "Animal Farm", "O Alquimista", "Brida"); protected static final List BOOK_FIRST_NAMES = Arrays.asList("George", "George", "Paulo", "Paulo"); protected static final List BOOK_LAST_NAMES = Arrays.asList("Orwell", "Orwell", "Coelho", "Coelho"); @@ -5902,6 +5903,62 @@ public abstract class jOOQAbstractTest< assertNull(result.getValue(3, TAuthor_LAST_NAME())); } + @Test + public void testJoinOnKey() throws Exception { + if (!supportsReferences()) { + log.info("SKIPPING", "JOIN ON KEY tests"); + return; + } + + try { + create().select(TAuthor_ID(), TBook_TITLE()) + .from(TAuthor().join(TBook()).onKey()); + fail(); + } + catch (DataAccessException expected) {} + + try { + create().select(TAuthor_ID(), TBook_TITLE()) + .from(TAuthor().join(TBook()).onKey(TBook_TITLE())); + fail(); + } + catch (DataAccessException expected) {} + + try { + create().select(TAuthor_ID(), TBook_TITLE()) + .from(TAuthor().join(TBook()).onKey(TBook_AUTHOR_ID(), TBook_ID())); + fail(); + } + catch (DataAccessException expected) {} + + // Test the Table API + // ------------------ + Result result1 = + create().select(TAuthor_ID(), TBook_TITLE()) + .from(TAuthor().join(TBook()).onKey(TBook_AUTHOR_ID())) + .orderBy(TBook_ID()) + .fetch(); + + assertEquals(4, result1.size()); + assertEquals(BOOK_AUTHOR_IDS, result1.getValues(0)); + assertEquals(BOOK_TITLES, result1.getValues(1)); + + Result result2 = + create().select(TAuthor_ID(), TBook_TITLE()) + .from(TAuthor() + .join(TBook()) + .onKey(TBook_AUTHOR_ID()) + .and(TBook_ID().in(1, 2))) + .orderBy(TBook_ID()) + .fetch(); + + // Test the Select API + // ------------------- + assertEquals(2, result2.size()); + assertEquals(BOOK_AUTHOR_IDS.subList(0, 2), result2.getValues(0)); + assertEquals(BOOK_TITLES.subList(0, 2), result2.getValues(1)); + } + @Test public void testOuterJoin() throws Exception { // Test LEFT OUTER JOIN diff --git a/jOOQ/src/main/java/org/jooq/SelectOnStep.java b/jOOQ/src/main/java/org/jooq/SelectOnStep.java index a7c3f78156..e569cb829c 100644 --- a/jOOQ/src/main/java/org/jooq/SelectOnStep.java +++ b/jOOQ/src/main/java/org/jooq/SelectOnStep.java @@ -123,6 +123,43 @@ public interface SelectOnStep { @Support SelectOnConditionStep on(String sql, Object... bindings); +// /** +// * Join the previous table on a non-ambiguous foreign key relationship +// * between the two joined tables. +// *

+// * See {@link Table#onKey(Key)} for examples. +// * +// * @see Table#onKey(Key) +// * @throws DataAccessException If there is no non-ambiguous key definition +// * known to jOOQ +// */ +// @Support +// SelectOnConditionStep onKey() throws DataAccessException; +// +// /** +// * Join the previous table on a non-ambiguous foreign key relationship +// * between the two joined tables. +// *

+// * See {@link Table#onKey(Key)} for examples. +// * +// * @see Table#onKey(Key) +// * @throws DataAccessException If there is no non-ambiguous key definition +// * known to jOOQ +// */ +// @Support +// SelectOnConditionStep onKey(TableField... keyFields) throws DataAccessException; +// +// /** +// * Join the table on a non-ambiguous foreign key relationship between the +// * two joined tables. +// *

+// * See {@link Table#onKey(Key)} for examples. +// * +// * @see Table#onKey(Key) +// */ +// @Support +// SelectOnConditionStep onKey(ForeignKey key); + /** * Join the previous table with the USING(column [, column...]) * syntax diff --git a/jOOQ/src/main/java/org/jooq/SelectQuery.java b/jOOQ/src/main/java/org/jooq/SelectQuery.java index 0ddeab634b..8c12462977 100644 --- a/jOOQ/src/main/java/org/jooq/SelectQuery.java +++ b/jOOQ/src/main/java/org/jooq/SelectQuery.java @@ -129,6 +129,42 @@ public interface SelectQuery extends Select, ConditionProvider, OrderPro @Support({ DERBY, HSQLDB, INGRES, MYSQL, ORACLE, POSTGRES, SQLITE }) void addJoinUsing(TableLike table, JoinType type, Collection> fields); +// /** +// * Joins the existing table product to a new table using a foreign key +// * +// * @param table The joined table +// * @param type The type of join +// * @see Table#onKey(Key) +// * @throws DataAccessException If there is no non-ambiguous key definition +// * known to jOOQ +// */ +// @Support +// void addJoinOnKey(TableLike table, JoinType type) throws DataAccessException; +// +// /** +// * Joins the existing table product to a new table using a foreign key +// * +// * @param table The joined table +// * @param type The type of join +// * @param keyFields The foreign key fields +// * @see Table#onKey(Key) +// * @throws DataAccessException If there is no non-ambiguous key definition +// * known to jOOQ +// */ +// @Support +// void addJoinOnKey(TableLike table, JoinType type, TableField... keyFields) throws DataAccessException; +// +// /** +// * Joins the existing table product to a new table using a foreign key +// * +// * @param table The joined table +// * @param type The type of join +// * @param key The foreign key +// * @see Table#onKey(Key) +// */ +// @Support +// void addJoinOnKey(TableLike table, JoinType type, ForeignKey key); + /** * Adds grouping fields * diff --git a/jOOQ/src/main/java/org/jooq/TableOnStep.java b/jOOQ/src/main/java/org/jooq/TableOnStep.java index 9a0b378212..5235b06e6e 100644 --- a/jOOQ/src/main/java/org/jooq/TableOnStep.java +++ b/jOOQ/src/main/java/org/jooq/TableOnStep.java @@ -45,6 +45,7 @@ import static org.jooq.SQLDialect.SQLITE; import java.util.Collection; +import org.jooq.exception.DataAccessException; import org.jooq.impl.Factory; /** @@ -106,4 +107,53 @@ public interface TableOnStep { */ @Support({ DERBY, HSQLDB, INGRES, MYSQL, ORACLE, POSTGRES, SQLITE }) Table using(Collection> fields); + + /** + * Join the table on a non-ambiguous foreign key relationship between the + * two joined tables. + *

+ * See {@link #onKey(Key)} for examples. + * + * @see #onKey(Key) + * @throws DataAccessException If there is no non-ambiguous key definition + * known to jOOQ + */ + @Support + TableOnConditionStep onKey() throws DataAccessException; + + /** + * Join the table on a non-ambiguous foreign key relationship between the + * two joined tables. + *

+ * See {@link #onKey(Key)} for examples. + * + * @see #onKey(Key) + * @throws DataAccessException If there is no non-ambiguous key definition + * known to jOOQ + */ + @Support + TableOnConditionStep onKey(TableField... keyFields) throws DataAccessException; + + /** + * Join the table on a non-ambiguous foreign key relationship between the + * two joined tables. + *

+ * An example:

+     * // There is a single foreign key relationship between A and B and it can
+     * // be obtained by A.getReferencesTo(B) or vice versa. The order of A and
+     * // B is not important
+     * A.join(B).onKey();
+     *
+     * // There are several foreign key relationships between A and B. In order
+     * // to disambiguate, you can provide a formal org.jooq.Key reference from
+     * // the generated Keys class
+     * A.join(B).onKey(key);
+     *
+     * // There are several foreign key relationships between A and B. In order
+     * // to disambiguate, you can provide any non-ambiguous foreign key column
+     * A.join(B).onKey(B.A_ID);
+     * 
+ */ + @Support + TableOnConditionStep onKey(ForeignKey key); } diff --git a/jOOQ/src/main/java/org/jooq/impl/JoinTable.java b/jOOQ/src/main/java/org/jooq/impl/JoinTable.java index ddc104c3fb..9a73eaea30 100644 --- a/jOOQ/src/main/java/org/jooq/impl/JoinTable.java +++ b/jOOQ/src/main/java/org/jooq/impl/JoinTable.java @@ -47,6 +47,7 @@ import org.jooq.Attachable; import org.jooq.BindContext; import org.jooq.Condition; import org.jooq.Field; +import org.jooq.ForeignKey; import org.jooq.JoinType; import org.jooq.Operator; import org.jooq.QueryPart; @@ -55,6 +56,7 @@ import org.jooq.RenderContext; import org.jooq.SQLDialect; import org.jooq.Select; import org.jooq.Table; +import org.jooq.TableField; import org.jooq.TableLike; import org.jooq.TableOnConditionStep; import org.jooq.TableOnStep; @@ -205,6 +207,62 @@ class JoinTable extends AbstractTable implements TableOnStep, TableOnCon return this; } + @Override + public final JoinTable onKey() throws DataAccessException { + List leftToRight = lhs.getReferencesTo(rhs); + List rightToLeft = rhs.getReferencesTo(lhs); + + if (leftToRight.size() == 1 && rightToLeft.size() == 0) { + return onKey((ForeignKey) leftToRight.get(0)); + } + else if (rightToLeft.size() == 1 && leftToRight.size() == 0) { + return onKey((ForeignKey) rightToLeft.get(0)); + } + + throw onKeyException(); + } + + @Override + public final JoinTable onKey(TableField... keyFields) throws DataAccessException { + if (keyFields != null && keyFields.length > 0) { + if (keyFields[0].getTable().equals(lhs)) { + for (ForeignKey key : lhs.getReferences()) { + if (key.getFields().containsAll(asList(keyFields))) { + return onKey(key); + } + } + } + else if (keyFields[0].getTable().equals(rhs)) { + for (ForeignKey key : rhs.getReferences()) { + if (key.getFields().containsAll(asList(keyFields))) { + return onKey(key); + } + } + } + } + + throw onKeyException(); + } + + @SuppressWarnings("unchecked") + @Override + public final JoinTable onKey(ForeignKey key) { + JoinTable result = this; + + TableField[] references = key.getFieldsArray(); + TableField[] referenced = key.getKey().getFieldsArray(); + + for (int i = 0; i < references.length; i++) { + result.and(((Field) references[i]).equal((Field) referenced[i])); + } + + return result; + } + + private final DataAccessException onKeyException() { + return new DataAccessException("Key ambiguous between tables " + lhs + " and " + rhs); + } + @Override public final JoinTable using(Field... fields) { return using(asList(fields));