[#5966] Add support for NATURAL FULL [ OUTER ] JOIN

This commit is contained in:
lukaseder 2018-12-14 15:08:31 +01:00
parent 649867c30f
commit d7cbbee8fe
9 changed files with 332 additions and 41 deletions

View File

@ -134,6 +134,7 @@ public enum Clause {
TABLE_JOIN_OUTER_FULL,
TABLE_JOIN_NATURAL_OUTER_LEFT,
TABLE_JOIN_NATURAL_OUTER_RIGHT,
TABLE_JOIN_NATURAL_OUTER_FULL,
TABLE_JOIN_CROSS_APPLY,
TABLE_JOIN_OUTER_APPLY,
TABLE_JOIN_SEMI_LEFT,

View File

@ -38,9 +38,28 @@
package org.jooq;
// ...
// ...
// ...
// ...
import static org.jooq.SQLDialect.CUBRID;
// ...
import static org.jooq.SQLDialect.DERBY;
import static org.jooq.SQLDialect.FIREBIRD;
import static org.jooq.SQLDialect.H2;
// ...
import static org.jooq.SQLDialect.HSQLDB;
// ...
// ...
import static org.jooq.SQLDialect.MARIADB;
import static org.jooq.SQLDialect.MYSQL;
// ...
// ...
import static org.jooq.SQLDialect.POSTGRES;
// ...
// ...
// ...
// ...
// ...
import org.jooq.impl.DSL;
@ -73,13 +92,13 @@ public enum JoinType {
/**
* <code>RIGHT OUTER JOIN</code> two tables.
*/
@Support
@Support({ CUBRID, DERBY, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
RIGHT_OUTER_JOIN("right outer join", true),
/**
* <code>FULL OUTER JOIN</code> two tables.
*/
@Support
@Support({ FIREBIRD, HSQLDB, POSTGRES })
FULL_OUTER_JOIN("full outer join", true),
/**
@ -97,9 +116,15 @@ public enum JoinType {
/**
* <code>NATURAL RIGHT OUTER JOIN</code> two tables.
*/
@Support
@Support({ CUBRID, DERBY, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
NATURAL_RIGHT_OUTER_JOIN("natural right outer join", false),
/**
* <code>NATURAL FULL OUTER JOIN</code> two tables.
*/
@Support({ FIREBIRD, HSQLDB, POSTGRES })
NATURAL_FULL_OUTER_JOIN("natural full outer join", false),
/**
* <code>CROSS APPLY</code> two tables.
*/

View File

@ -1449,6 +1449,119 @@ public interface SelectJoinStep<R extends Record> extends SelectWhereStep<R> {
@Support({ CUBRID, DERBY, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
SelectJoinStep<R> naturalRightOuterJoin(Name name);
/**
* Convenience method to <code>NATURAL FULL OUTER JOIN</code> a table to
* the last table added to the <code>FROM</code> clause using
* {@link Table#naturalFullOuterJoin(TableLike)}
* <p>
* Natural joins are supported by most RDBMS. If they aren't supported, they
* are emulated if jOOQ has enough information.
*
* @see Table#naturalFullOuterJoin(TableLike)
*/
@Support({ FIREBIRD, HSQLDB, POSTGRES })
SelectJoinStep<R> naturalFullOuterJoin(TableLike<?> table);
/**
* Convenience method to <code>NATURAL FULL OUTER JOIN</code> a table to
* the last table added to the <code>FROM</code> clause using
* {@link Table#naturalFullOuterJoin(String)}
* <p>
* Natural joins are supported by most RDBMS. If they aren't supported, they
* are emulated if jOOQ has enough information.
* <p>
* <b>NOTE</b>: When inserting plain SQL into jOOQ objects, you must
* guarantee syntax integrity. You may also create the possibility of
* malicious SQL injection. Be sure to properly use bind variables and/or
* escape literals when concatenated into SQL clauses!
*
* @see DSL#table(SQL)
* @see Table#naturalFullOuterJoin(SQL)
* @see SQL
*/
@Support({ FIREBIRD, HSQLDB, POSTGRES })
@PlainSQL
SelectJoinStep<R> naturalFullOuterJoin(SQL sql);
/**
* Convenience method to <code>NATURAL FULL OUTER JOIN</code> a table to
* the last table added to the <code>FROM</code> clause using
* {@link Table#naturalFullOuterJoin(String)}
* <p>
* Natural joins are supported by most RDBMS. If they aren't supported, they
* are emulated if jOOQ has enough information.
* <p>
* <b>NOTE</b>: When inserting plain SQL into jOOQ objects, you must
* guarantee syntax integrity. You may also create the possibility of
* malicious SQL injection. Be sure to properly use bind variables and/or
* escape literals when concatenated into SQL clauses!
*
* @see DSL#table(String)
* @see Table#naturalFullOuterJoin(String)
* @see SQL
*/
@Support({ FIREBIRD, HSQLDB, POSTGRES })
@PlainSQL
SelectJoinStep<R> naturalFullOuterJoin(String sql);
/**
* Convenience method to <code>NATURAL FULL OUTER JOIN</code> a table to
* the last table added to the <code>FROM</code> clause using
* {@link Table#naturalFullOuterJoin(String, Object...)}
* <p>
* Natural joins are supported by most RDBMS. If they aren't supported, they
* are emulated if jOOQ has enough information.
* <p>
* <b>NOTE</b>: When inserting plain SQL into jOOQ objects, you must
* guarantee syntax integrity. You may also create the possibility of
* malicious SQL injection. Be sure to properly use bind variables and/or
* escape literals when concatenated into SQL clauses!
*
* @see DSL#table(String, Object...)
* @see DSL#sql(String, Object...)
* @see Table#naturalFullOuterJoin(String, Object...)
* @see SQL
*/
@Support({ FIREBIRD, HSQLDB, POSTGRES })
@PlainSQL
SelectJoinStep<R> naturalFullOuterJoin(String sql, Object... bindings);
/**
* Convenience method to <code>NATURAL FULL OUTER JOIN</code> a table to
* the last table added to the <code>FROM</code> clause using
* {@link Table#naturalFullOuterJoin(String, QueryPart...)}
* <p>
* Natural joins are supported by most RDBMS. If they aren't supported, they
* are emulated if jOOQ has enough information.
* <p>
* <b>NOTE</b>: When inserting plain SQL into jOOQ objects, you must
* guarantee syntax integrity. You may also create the possibility of
* malicious SQL injection. Be sure to properly use bind variables and/or
* escape literals when concatenated into SQL clauses!
*
* @see DSL#table(String, QueryPart...)
* @see DSL#sql(String, QueryPart...)
* @see Table#naturalFullOuterJoin(String, QueryPart...)
* @see SQL
*/
@Support({ FIREBIRD, HSQLDB, POSTGRES })
@PlainSQL
SelectJoinStep<R> naturalFullOuterJoin(String sql, QueryPart... parts);
/**
* Convenience method to <code>NATURAL FULL OUTER JOIN</code> a table to
* the last table added to the <code>FROM</code> clause using
* {@link Table#naturalFullOuterJoin(Name)}
* <p>
* Natural joins are supported by most RDBMS. If they aren't supported, they
* are emulated if jOOQ has enough information.
*
* @see DSL#table(Name)
* @see Table#naturalFullOuterJoin(Name)
*/
@Support({ FIREBIRD, HSQLDB, POSTGRES })
SelectJoinStep<R> naturalFullOuterJoin(Name name);
// -------------------------------------------------------------------------
// XXX: SEMI and ANTI JOIN
// -------------------------------------------------------------------------

View File

@ -1885,6 +1885,100 @@ public interface Table<R extends Record> extends TableLike<R>, Named {
@Support({ CUBRID, DERBY, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
Table<Record> naturalRightOuterJoin(Name name);
/**
* <code>NATURAL FULL OUTER JOIN</code> a table to this table.
* <p>
* If this is not supported by your RDBMS, then jOOQ will try to emulate
* this behaviour using the information provided in this query.
*/
@Support({ FIREBIRD, HSQLDB, POSTGRES })
Table<Record> naturalFullOuterJoin(TableLike<?> table);
/**
* <code>NATURAL FULL OUTER JOIN</code> a table to this table.
* <p>
* If this is not supported by your RDBMS, then jOOQ will try to emulate
* this behaviour using the information provided in this query.
* <p>
* <b>NOTE</b>: When inserting plain SQL into jOOQ objects, you must
* guarantee syntax integrity. You may also create the possibility of
* malicious SQL injection. Be sure to properly use bind variables and/or
* escape literals when concatenated into SQL clauses!
*
* @see DSL#table(SQL)
* @see SQL
*/
@Support({ FIREBIRD, HSQLDB, POSTGRES })
@PlainSQL
Table<Record> naturalFullOuterJoin(SQL sql);
/**
* <code>NATURAL FULL OUTER JOIN</code> a table to this table.
* <p>
* If this is not supported by your RDBMS, then jOOQ will try to emulate
* this behaviour using the information provided in this query.
* <p>
* <b>NOTE</b>: When inserting plain SQL into jOOQ objects, you must
* guarantee syntax integrity. You may also create the possibility of
* malicious SQL injection. Be sure to properly use bind variables and/or
* escape literals when concatenated into SQL clauses!
*
* @see DSL#table(String)
* @see SQL
*/
@Support({ FIREBIRD, HSQLDB, POSTGRES })
@PlainSQL
Table<Record> naturalFullOuterJoin(String sql);
/**
* <code>NATURAL FULL OUTER JOIN</code> a table to this table.
* <p>
* If this is not supported by your RDBMS, then jOOQ will try to emulate
* this behaviour using the information provided in this query.
* <p>
* <b>NOTE</b>: When inserting plain SQL into jOOQ objects, you must
* guarantee syntax integrity. You may also create the possibility of
* malicious SQL injection. Be sure to properly use bind variables and/or
* escape literals when concatenated into SQL clauses!
*
* @see DSL#table(String, Object...)
* @see DSL#sql(String, Object...)
* @see SQL
*/
@Support({ FIREBIRD, HSQLDB, POSTGRES })
@PlainSQL
Table<Record> naturalFullOuterJoin(String sql, Object... bindings);
/**
* <code>NATURAL FULL OUTER JOIN</code> a table to this table.
* <p>
* If this is not supported by your RDBMS, then jOOQ will try to emulate
* this behaviour using the information provided in this query.
* <p>
* <b>NOTE</b>: When inserting plain SQL into jOOQ objects, you must
* guarantee syntax integrity. You may also create the possibility of
* malicious SQL injection. Be sure to properly use bind variables and/or
* escape literals when concatenated into SQL clauses!
*
* @see DSL#table(String, QueryPart...)
* @see DSL#sql(String, QueryPart...)
* @see SQL
*/
@Support({ FIREBIRD, HSQLDB, POSTGRES })
@PlainSQL
Table<Record> naturalFullOuterJoin(String sql, QueryPart... parts);
/**
* <code>NATURAL FULL OUTER JOIN</code> a table to this table.
* <p>
* If this is not supported by your RDBMS, then jOOQ will try to emulate
* this behaviour using the information provided in this query.
*
* @see DSL#table(Name)
*/
@Support({ FIREBIRD, HSQLDB, POSTGRES })
Table<Record> naturalFullOuterJoin(Name name);
// -------------------------------------------------------------------------
// XXX: APPLY clauses on tables
// -------------------------------------------------------------------------

View File

@ -45,6 +45,7 @@ import static org.jooq.JoinType.JOIN;
import static org.jooq.JoinType.LEFT_ANTI_JOIN;
import static org.jooq.JoinType.LEFT_OUTER_JOIN;
import static org.jooq.JoinType.LEFT_SEMI_JOIN;
import static org.jooq.JoinType.NATURAL_FULL_OUTER_JOIN;
import static org.jooq.JoinType.NATURAL_JOIN;
import static org.jooq.JoinType.NATURAL_LEFT_OUTER_JOIN;
import static org.jooq.JoinType.NATURAL_RIGHT_OUTER_JOIN;
@ -1455,6 +1456,36 @@ abstract class AbstractTable<R extends Record> extends AbstractNamed implements
return naturalRightOuterJoin(table(name));
}
@Override
public final Table<Record> naturalFullOuterJoin(TableLike<?> table) {
return join(table, NATURAL_FULL_OUTER_JOIN);
}
@Override
public final Table<Record> naturalFullOuterJoin(SQL sql) {
return naturalFullOuterJoin(table(sql));
}
@Override
public final Table<Record> naturalFullOuterJoin(String sql) {
return naturalFullOuterJoin(table(sql));
}
@Override
public final Table<Record> naturalFullOuterJoin(String sql, Object... bindings) {
return naturalFullOuterJoin(table(sql, bindings));
}
@Override
public final Table<Record> naturalFullOuterJoin(String sql, QueryPart... parts) {
return naturalFullOuterJoin(table(sql, parts));
}
@Override
public final Table<Record> naturalFullOuterJoin(Name name) {
return naturalFullOuterJoin(table(name));
}
@Override
public final Table<Record> crossApply(TableLike<?> table) {
return join(table, CROSS_APPLY);

View File

@ -45,6 +45,7 @@ import static org.jooq.Clause.TABLE_JOIN_CROSS;
import static org.jooq.Clause.TABLE_JOIN_CROSS_APPLY;
import static org.jooq.Clause.TABLE_JOIN_INNER;
import static org.jooq.Clause.TABLE_JOIN_NATURAL;
import static org.jooq.Clause.TABLE_JOIN_NATURAL_OUTER_FULL;
import static org.jooq.Clause.TABLE_JOIN_NATURAL_OUTER_LEFT;
import static org.jooq.Clause.TABLE_JOIN_NATURAL_OUTER_RIGHT;
import static org.jooq.Clause.TABLE_JOIN_ON;
@ -58,10 +59,12 @@ import static org.jooq.Clause.TABLE_JOIN_STRAIGHT;
import static org.jooq.Clause.TABLE_JOIN_USING;
import static org.jooq.JoinType.CROSS_APPLY;
import static org.jooq.JoinType.CROSS_JOIN;
import static org.jooq.JoinType.FULL_OUTER_JOIN;
import static org.jooq.JoinType.JOIN;
import static org.jooq.JoinType.LEFT_ANTI_JOIN;
import static org.jooq.JoinType.LEFT_OUTER_JOIN;
import static org.jooq.JoinType.LEFT_SEMI_JOIN;
import static org.jooq.JoinType.NATURAL_FULL_OUTER_JOIN;
import static org.jooq.JoinType.NATURAL_JOIN;
import static org.jooq.JoinType.NATURAL_LEFT_OUTER_JOIN;
import static org.jooq.JoinType.NATURAL_RIGHT_OUTER_JOIN;
@ -280,6 +283,7 @@ implements
NATURAL_JOIN,
NATURAL_LEFT_OUTER_JOIN,
NATURAL_RIGHT_OUTER_JOIN,
NATURAL_FULL_OUTER_JOIN,
CROSS_APPLY,
OUTER_APPLY).contains(translatedType)) {
ctx.formatIndentStart();
@ -337,6 +341,7 @@ implements
case FULL_OUTER_JOIN: return TABLE_JOIN_OUTER_FULL;
case NATURAL_LEFT_OUTER_JOIN: return TABLE_JOIN_NATURAL_OUTER_LEFT;
case NATURAL_RIGHT_OUTER_JOIN: return TABLE_JOIN_NATURAL_OUTER_RIGHT;
case NATURAL_FULL_OUTER_JOIN: return TABLE_JOIN_NATURAL_OUTER_FULL;
case CROSS_APPLY: return TABLE_JOIN_CROSS_APPLY;
case OUTER_APPLY: return TABLE_JOIN_OUTER_APPLY;
case LEFT_SEMI_JOIN: return TABLE_JOIN_SEMI_LEFT;
@ -358,6 +363,8 @@ implements
return LEFT_OUTER_JOIN;
else if (emulateNaturalRightOuterJoin(context))
return RIGHT_OUTER_JOIN;
else if (emulateNaturalFullOuterJoin(context))
return FULL_OUTER_JOIN;
else
return type;
}
@ -382,6 +389,10 @@ implements
return type == NATURAL_RIGHT_OUTER_JOIN && EMULATE_NATURAL_OUTER_JOIN.contains(context.family());
}
private final boolean emulateNaturalFullOuterJoin(Context<?> context) {
return type == NATURAL_FULL_OUTER_JOIN && EMULATE_NATURAL_OUTER_JOIN.contains(context.family());
}
private final void toSQLJoinCondition(Context<?> context) {
if (!using.isEmpty()) {
@ -427,7 +438,8 @@ implements
// common fields in lhs and rhs of the JOIN clause
else if (emulateNaturalJoin(context) ||
emulateNaturalLeftOuterJoin(context) ||
emulateNaturalRightOuterJoin(context)) {
emulateNaturalRightOuterJoin(context) ||
emulateNaturalFullOuterJoin(context)) {
boolean first = true;
for (Field<?> field : lhs.fields()) {

View File

@ -8468,57 +8468,38 @@ final class ParserImpl implements Parser {
}
private static final JoinType parseJoinTypeIf(ParserContext ctx) {
if (parseKeywordIf(ctx, "CROSS JOIN"))
return JoinType.CROSS_JOIN;
else if (parseKeywordIf(ctx, "CROSS APPLY"))
return JoinType.CROSS_APPLY;
else if (parseKeywordIf(ctx, "CROSS JOIN"))
return JoinType.CROSS_JOIN;
else if (parseKeywordIf(ctx, "INNER")) {
parseKeyword(ctx, "JOIN");
return JoinType.JOIN;
if (parseKeywordIf(ctx, "CROSS")) {
if (parseKeywordIf(ctx, "JOIN"))
return JoinType.CROSS_JOIN;
else if (parseKeywordIf(ctx, "APPLY"))
return JoinType.CROSS_APPLY;
}
else if (parseKeywordIf(ctx, "INNER") && parseKeyword(ctx, "JOIN"))
return JoinType.JOIN;
else if (parseKeywordIf(ctx, "JOIN"))
return JoinType.JOIN;
else if (parseKeywordIf(ctx, "LEFT")) {
if (parseKeywordIf(ctx, "SEMI")) {
parseKeyword(ctx, "JOIN");
if (parseKeywordIf(ctx, "SEMI") && parseKeyword(ctx, "JOIN"))
return JoinType.LEFT_SEMI_JOIN;
}
else if (parseKeywordIf(ctx, "ANTI")) {
parseKeyword(ctx, "JOIN");
else if (parseKeywordIf(ctx, "ANTI") && parseKeyword(ctx, "JOIN"))
return JoinType.LEFT_ANTI_JOIN;
}
else {
parseKeywordIf(ctx, "OUTER");
parseKeyword(ctx, "JOIN");
else if ((parseKeywordIf(ctx, "OUTER") || true) && parseKeyword(ctx, "JOIN"))
return JoinType.LEFT_OUTER_JOIN;
}
}
else if (parseKeywordIf(ctx, "RIGHT")) {
parseKeywordIf(ctx, "OUTER");
parseKeyword(ctx, "JOIN");
else if (parseKeywordIf(ctx, "RIGHT") && (parseKeywordIf(ctx, "OUTER") || true) && parseKeyword(ctx, "JOIN"))
return JoinType.RIGHT_OUTER_JOIN;
}
else if (parseKeywordIf(ctx, "FULL")) {
parseKeywordIf(ctx, "OUTER");
parseKeyword(ctx, "JOIN");
else if (parseKeywordIf(ctx, "FULL") && (parseKeywordIf(ctx, "OUTER") || true) && parseKeyword(ctx, "JOIN"))
return JoinType.FULL_OUTER_JOIN;
}
else if (parseKeywordIf(ctx, "OUTER APPLY"))
return JoinType.OUTER_APPLY;
else if (parseKeywordIf(ctx, "NATURAL")) {
if (parseKeywordIf(ctx, "LEFT")) {
parseKeywordIf(ctx, "OUTER");
parseKeyword(ctx, "JOIN");
if (parseKeywordIf(ctx, "LEFT") && (parseKeywordIf(ctx, "OUTER") || true) && parseKeyword(ctx, "JOIN"))
return JoinType.NATURAL_LEFT_OUTER_JOIN;
}
else if (parseKeywordIf(ctx, "RIGHT")) {
parseKeywordIf(ctx, "OUTER");
parseKeyword(ctx, "JOIN");
else if (parseKeywordIf(ctx, "RIGHT") && (parseKeywordIf(ctx, "OUTER") || true) && parseKeyword(ctx, "JOIN"))
return JoinType.NATURAL_RIGHT_OUTER_JOIN;
}
else if (parseKeywordIf(ctx, "JOIN"))
else if (parseKeywordIf(ctx, "FULL") && (parseKeywordIf(ctx, "OUTER") || true) && parseKeyword(ctx, "JOIN"))
return JoinType.NATURAL_FULL_OUTER_JOIN;
else if ((parseKeywordIf(ctx, "INNER") || true) && parseKeyword(ctx, "JOIN"))
return JoinType.NATURAL_JOIN;
}
else if (parseKeywordIf(ctx, "STRAIGHT_JOIN"))
@ -8740,9 +8721,11 @@ final class ParserImpl implements Parser {
return peekKeyword(ctx, string, true, false, true);
}
private static final void parseKeyword(ParserContext ctx, String keyword) {
private static final boolean parseKeyword(ParserContext ctx, String keyword) {
if (!parseKeywordIf(ctx, keyword))
throw ctx.expected("Keyword '" + keyword + "'");
return true;
}
private static final boolean parseKeywordIf(ParserContext ctx, String keyword) {

View File

@ -2059,6 +2059,7 @@ final class SelectImpl<R extends Record, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10
case NATURAL_JOIN:
case NATURAL_LEFT_OUTER_JOIN:
case NATURAL_RIGHT_OUTER_JOIN:
case NATURAL_FULL_OUTER_JOIN:
case CROSS_APPLY:
case OUTER_APPLY: {
getQuery().addJoin(table, type);
@ -2101,6 +2102,11 @@ final class SelectImpl<R extends Record, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10
return join(table, JoinType.NATURAL_RIGHT_OUTER_JOIN);
}
@Override
public final SelectImpl naturalFullOuterJoin(TableLike<?> table) {
return join(table, JoinType.NATURAL_FULL_OUTER_JOIN);
}
@Override
public final SelectImpl leftSemiJoin(TableLike<?> table) {
return join(table, JoinType.LEFT_SEMI_JOIN);
@ -2426,6 +2432,31 @@ final class SelectImpl<R extends Record, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10
return naturalRightOuterJoin(table(name));
}
@Override
public final SelectImpl naturalFullOuterJoin(SQL sql) {
return naturalFullOuterJoin(table(sql));
}
@Override
public final SelectImpl naturalFullOuterJoin(String sql) {
return naturalFullOuterJoin(table(sql));
}
@Override
public final SelectImpl naturalFullOuterJoin(String sql, Object... bindings) {
return naturalFullOuterJoin(table(sql, bindings));
}
@Override
public final SelectImpl naturalFullOuterJoin(String sql, QueryPart... parts) {
return naturalFullOuterJoin(table(sql, parts));
}
@Override
public final SelectImpl naturalFullOuterJoin(Name name) {
return naturalFullOuterJoin(table(name));
}
@Override
public final SelectImpl crossApply(SQL sql) {
return crossApply(table(sql));

View File

@ -2495,6 +2495,7 @@ final class SelectQueryImpl<R extends Record> extends AbstractResultQuery<R> imp
case NATURAL_JOIN:
case NATURAL_LEFT_OUTER_JOIN:
case NATURAL_RIGHT_OUTER_JOIN:
case NATURAL_FULL_OUTER_JOIN: