From 10720d1fe67eaaf0299da27f5be55f2caa4242d1 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Tue, 17 Jan 2012 18:13:03 +0000 Subject: [PATCH] [#578] Add KEY JOIN syntax to simulate joining using generated foreign keys --- .../src/org/jooq/test/jOOQAbstractTest.java | 44 ++++++- jOOQ/src/main/java/org/jooq/SelectOnStep.java | 73 ++++++----- jOOQ/src/main/java/org/jooq/SelectQuery.java | 72 +++++----- .../main/java/org/jooq/impl/JoinTable.java | 12 ++ .../main/java/org/jooq/impl/SelectImpl.java | 31 +++++ .../java/org/jooq/impl/SelectQueryImpl.java | 124 ++++++++++++++++++ .../main/java/org/jooq/impl/TableAlias.java | 6 + 7 files changed, 289 insertions(+), 73 deletions(-) diff --git a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java index 19a176911e..4022f470ac 100644 --- a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java +++ b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java @@ -5952,11 +5952,51 @@ public abstract class jOOQAbstractTest< .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 the Select API + // ------------------- + Result result3 = + create().select(TAuthor_ID(), TBook_TITLE()) + .from(TAuthor()) + .join(TBook()).onKey(TBook_AUTHOR_ID()) + .orderBy(TBook_ID()) + .fetch(); + + assertEquals(4, result3.size()); + assertEquals(BOOK_AUTHOR_IDS, result3.getValues(0)); + assertEquals(BOOK_TITLES, result3.getValues(1)); + + // Test using unambiguous keys + // --------------------------- + Result result4 = + create().select(TBook_ID(), TBookStore_NAME()) + .from(TBook()) + .join(TBookToBookStore()).onKey() + .join(TBookStore()).onKey() + .orderBy(TBook_ID(), TBookStore_NAME()) + .fetch(); + + assertEquals(6, result4.size()); + assertEquals(asList(1, 1, 2, 3, 3, 3), result4.getValues(0)); + assertEquals(asList( + "Ex Libris", "Orell Füssli", "Orell Füssli", + "Buchhandlung im Volkshaus", "Ex Libris", "Orell Füssli"), result4.getValues(1)); + + // Test inverse join relationship [#671] TODO + // ------------------------------ +// Result result5 = +// create().select(TBook_TITLE(), TBookStore_NAME()) +// .from(TBook()) +// .join(TBookToBookStore() +// .join(TBookStore()).onKey()) +// .onKey() +// .orderBy(TBook_ID(), TBookStore_NAME()) +// .fetch(); +// +// assertEquals(result4, result5); } @Test diff --git a/jOOQ/src/main/java/org/jooq/SelectOnStep.java b/jOOQ/src/main/java/org/jooq/SelectOnStep.java index e569cb829c..52b7c0611d 100644 --- a/jOOQ/src/main/java/org/jooq/SelectOnStep.java +++ b/jOOQ/src/main/java/org/jooq/SelectOnStep.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; /** @@ -123,42 +124,42 @@ 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 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 + SelectJoinStep 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 + SelectJoinStep 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 + SelectJoinStep onKey(ForeignKey key); /** * Join the previous table with the USING(column [, column...]) diff --git a/jOOQ/src/main/java/org/jooq/SelectQuery.java b/jOOQ/src/main/java/org/jooq/SelectQuery.java index 8c12462977..2eb4d477c6 100644 --- a/jOOQ/src/main/java/org/jooq/SelectQuery.java +++ b/jOOQ/src/main/java/org/jooq/SelectQuery.java @@ -46,6 +46,8 @@ import static org.jooq.SQLDialect.SQLITE; import java.util.Collection; +import org.jooq.exception.DataAccessException; + /** * A query for data selection * @@ -129,41 +131,41 @@ 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); + /** + * 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/impl/JoinTable.java b/jOOQ/src/main/java/org/jooq/impl/JoinTable.java index 9a73eaea30..68f55403b3 100644 --- a/jOOQ/src/main/java/org/jooq/impl/JoinTable.java +++ b/jOOQ/src/main/java/org/jooq/impl/JoinTable.java @@ -40,6 +40,7 @@ import static org.jooq.impl.Factory.condition; import static org.jooq.impl.Factory.exists; import static org.jooq.impl.Factory.notExists; +import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -96,6 +97,17 @@ class JoinTable extends AbstractTable implements TableOnStep, TableOnCon // Table API // ------------------------------------------------------------------------ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public final List> getReferences() { + List> result = new ArrayList>(); + + result.addAll(lhs.getReferences()); + result.addAll(rhs.getReferences()); + + return (List) result; + } + @Override public final void toSQL(RenderContext context) { context.sql(lhs) diff --git a/jOOQ/src/main/java/org/jooq/impl/SelectImpl.java b/jOOQ/src/main/java/org/jooq/impl/SelectImpl.java index 809fd8a830..507d56f5d7 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SelectImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/SelectImpl.java @@ -46,6 +46,7 @@ import java.util.Collection; import org.jooq.Condition; import org.jooq.Configuration; import org.jooq.Field; +import org.jooq.ForeignKey; import org.jooq.JoinType; import org.jooq.Operator; import org.jooq.Record; @@ -62,7 +63,9 @@ import org.jooq.SelectQuery; import org.jooq.SelectSelectStep; import org.jooq.SortField; import org.jooq.Table; +import org.jooq.TableField; import org.jooq.TableLike; +import org.jooq.exception.DataAccessException; /** * A wrapper for a {@link SelectQuery} @@ -511,6 +514,34 @@ class SelectImpl extends AbstractDelegatingSelect implements return on(condition(sql, bindings)); } + @Override + public final SelectImpl onKey() throws DataAccessException { + conditionStep = ConditionStep.ON; + getQuery().addJoinOnKey(joinTable, joinType); + joinTable = null; + joinType = null; + return this; + } + + @Override + public final SelectImpl onKey(TableField... keyFields) throws DataAccessException { + conditionStep = ConditionStep.ON; + getQuery().addJoinOnKey(joinTable, joinType, keyFields); + joinTable = null; + joinType = null; + return this; + } + + @Override + public final SelectImpl onKey(ForeignKey key) { + conditionStep = ConditionStep.ON; + getQuery().addJoinOnKey(joinTable, joinType, key); + joinTable = null; + joinType = null; + return this; + + } + @Override public final SelectImpl using(Field... fields) { return using(Arrays.asList(fields)); diff --git a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java index a4b605a3b6..701d3fb4c8 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java @@ -42,12 +42,15 @@ import java.util.Collection; import org.jooq.Condition; import org.jooq.Configuration; import org.jooq.Field; +import org.jooq.ForeignKey; import org.jooq.JoinType; import org.jooq.Operator; import org.jooq.Record; import org.jooq.SelectQuery; import org.jooq.Table; +import org.jooq.TableField; import org.jooq.TableLike; +import org.jooq.exception.DataAccessException; /** * @author Lukas Eder @@ -137,6 +140,8 @@ class SelectQueryImpl extends AbstractSubSelect implements SelectQuery { @Override public final void addJoin(TableLike table, JoinType type, Condition... conditions) { + // TODO: This and similar methods should be refactored, patterns extracted... + int index = getFrom().size() - 1; Table joined = null; @@ -172,6 +177,123 @@ class SelectQueryImpl extends AbstractSubSelect implements SelectQuery { getFrom().set(index, joined); } + @Override + public final void addJoinOnKey(TableLike table, JoinType type) throws DataAccessException { + // TODO: This and similar methods should be refactored, patterns extracted... + + int index = getFrom().size() - 1; + Table joined = null; + + switch (type) { + case JOIN: + joined = getFrom().get(index).join(table).onKey(); + break; + case LEFT_OUTER_JOIN: + joined = getFrom().get(index).leftOuterJoin(table).onKey(); + break; + case RIGHT_OUTER_JOIN: + joined = getFrom().get(index).rightOuterJoin(table).onKey(); + break; + case FULL_OUTER_JOIN: + joined = getFrom().get(index).fullOuterJoin(table).onKey(); + break; + + // These join types don't take any ON clause. Ignore conditions. + case CROSS_JOIN: + joined = getFrom().get(index).crossJoin(table); + break; + case NATURAL_JOIN: + joined = getFrom().get(index).naturalJoin(table); + break; + case NATURAL_LEFT_OUTER_JOIN: + joined = getFrom().get(index).naturalLeftOuterJoin(table); + break; + case NATURAL_RIGHT_OUTER_JOIN: + joined = getFrom().get(index).naturalRightOuterJoin(table); + break; + } + + getFrom().set(index, joined); + } + + @Override + public final void addJoinOnKey(TableLike table, JoinType type, TableField... keyFields) throws DataAccessException { + // TODO: This and similar methods should be refactored, patterns extracted... + + int index = getFrom().size() - 1; + Table joined = null; + + switch (type) { + case JOIN: + joined = getFrom().get(index).join(table).onKey(keyFields); + break; + case LEFT_OUTER_JOIN: + joined = getFrom().get(index).leftOuterJoin(table).onKey(keyFields); + break; + case RIGHT_OUTER_JOIN: + joined = getFrom().get(index).rightOuterJoin(table).onKey(keyFields); + break; + case FULL_OUTER_JOIN: + joined = getFrom().get(index).fullOuterJoin(table).onKey(keyFields); + break; + + // These join types don't take any ON clause. Ignore conditions. + case CROSS_JOIN: + joined = getFrom().get(index).crossJoin(table); + break; + case NATURAL_JOIN: + joined = getFrom().get(index).naturalJoin(table); + break; + case NATURAL_LEFT_OUTER_JOIN: + joined = getFrom().get(index).naturalLeftOuterJoin(table); + break; + case NATURAL_RIGHT_OUTER_JOIN: + joined = getFrom().get(index).naturalRightOuterJoin(table); + break; + } + + getFrom().set(index, joined); + } + + @Override + public final void addJoinOnKey(TableLike table, JoinType type, ForeignKey key) { + // TODO: This and similar methods should be refactored, patterns extracted... + + int index = getFrom().size() - 1; + Table joined = null; + + switch (type) { + case JOIN: + joined = getFrom().get(index).join(table).onKey(key); + break; + case LEFT_OUTER_JOIN: + joined = getFrom().get(index).leftOuterJoin(table).onKey(key); + break; + case RIGHT_OUTER_JOIN: + joined = getFrom().get(index).rightOuterJoin(table).onKey(key); + break; + case FULL_OUTER_JOIN: + joined = getFrom().get(index).fullOuterJoin(table).onKey(key); + break; + + // These join types don't take any ON clause. Ignore conditions. + case CROSS_JOIN: + joined = getFrom().get(index).crossJoin(table); + break; + case NATURAL_JOIN: + joined = getFrom().get(index).naturalJoin(table); + break; + case NATURAL_LEFT_OUTER_JOIN: + joined = getFrom().get(index).naturalLeftOuterJoin(table); + break; + case NATURAL_RIGHT_OUTER_JOIN: + joined = getFrom().get(index).naturalRightOuterJoin(table); + break; + } + + getFrom().set(index, joined); + } + @Override public final void addJoinUsing(TableLike table, Collection> fields) { addJoinUsing(table, JoinType.JOIN, fields); @@ -179,6 +301,8 @@ class SelectQueryImpl extends AbstractSubSelect implements SelectQuery { @Override public final void addJoinUsing(TableLike table, JoinType type, Collection> fields) { + // TODO: This and similar methods should be refactored, patterns extracted... + int index = getFrom().size() - 1; Table joined = null; diff --git a/jOOQ/src/main/java/org/jooq/impl/TableAlias.java b/jOOQ/src/main/java/org/jooq/impl/TableAlias.java index c5b9fbb1ea..de3ab5f52f 100644 --- a/jOOQ/src/main/java/org/jooq/impl/TableAlias.java +++ b/jOOQ/src/main/java/org/jooq/impl/TableAlias.java @@ -41,6 +41,7 @@ import java.util.List; import org.jooq.Attachable; import org.jooq.BindContext; import org.jooq.Field; +import org.jooq.ForeignKey; import org.jooq.Record; import org.jooq.RenderContext; import org.jooq.Table; @@ -65,6 +66,11 @@ class TableAlias extends AbstractTable { this.aliasProvider = new AliasProviderImpl>(table, alias, wrapInParentheses); } + @Override + public final List> getReferences() { + return aliasProvider.getAliasProvider().getReferences(); + } + @Override public final List getAttachables0() { return getAttachables(aliasProvider, aliasedFields);