[jOOQ/jOOQ#8807] Add setting to specify whether INNER and OUTER keywords should be generated in joins

This commit is contained in:
Lukas Eder 2019-10-01 16:06:31 +02:00
parent 182c18c737
commit 582ee276c5
6 changed files with 227 additions and 28 deletions

View File

@ -76,7 +76,7 @@ public enum JoinType {
* <code>INNER JOIN</code> two tables.
*/
@Support
JOIN("join", true),
JOIN("join", "inner join", "join", true),
/**
* <code>CROSS JOIN</code> two tables.
@ -88,43 +88,43 @@ public enum JoinType {
* <code>LEFT OUTER JOIN</code> two tables.
*/
@Support
LEFT_OUTER_JOIN("left outer join", true),
LEFT_OUTER_JOIN("left outer join", "left outer join", "left join", true),
/**
* <code>RIGHT OUTER JOIN</code> two tables.
*/
@Support({ CUBRID, DERBY, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
RIGHT_OUTER_JOIN("right outer join", true),
RIGHT_OUTER_JOIN("right outer join", "right outer join", "right join", true),
/**
* <code>FULL OUTER JOIN</code> two tables.
*/
@Support({ FIREBIRD, HSQLDB, POSTGRES })
FULL_OUTER_JOIN("full outer join", true),
FULL_OUTER_JOIN("full outer join", "full outer join", "full join", true),
/**
* <code>NATURAL INNER JOIN</code> two tables.
*/
@Support
NATURAL_JOIN("natural join", false),
NATURAL_JOIN("natural join", "natural inner join", "natural join", false),
/**
* <code>NATURAL LEFT OUTER JOIN</code> two tables.
*/
@Support
NATURAL_LEFT_OUTER_JOIN("natural left outer join", false),
NATURAL_LEFT_OUTER_JOIN("natural left outer join", "natural left outer join", "natural left join", false),
/**
* <code>NATURAL RIGHT OUTER JOIN</code> two tables.
*/
@Support({ CUBRID, DERBY, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
NATURAL_RIGHT_OUTER_JOIN("natural right outer join", false),
NATURAL_RIGHT_OUTER_JOIN("natural right outer join", "natural right outer join", "natural right join", false),
/**
* <code>NATURAL FULL OUTER JOIN</code> two tables.
*/
@Support({ FIREBIRD, HSQLDB, POSTGRES })
NATURAL_FULL_OUTER_JOIN("natural full outer join", false),
NATURAL_FULL_OUTER_JOIN("natural full outer join", "natural full outer join", "natural full join", false),
/**
* <code>CROSS APPLY</code> two tables.
@ -158,22 +158,34 @@ public enum JoinType {
;
private final String sql;
private final Keyword keyword;
private final String defaultSql;
private final Keyword defaultKeyword;
private final Keyword includingOptionalKeywords;
private final Keyword excludingOptionalKeywords;
private final boolean qualified;
private JoinType(String sql, boolean qualified) {
this.sql = sql;
this.keyword = DSL.keyword(sql);
this(sql, sql, sql, qualified);
}
private JoinType(String defaultSql, String includingOptionalKeywords, String excludingOptionalKeywords, boolean qualified) {
this.defaultSql = defaultSql;
this.includingOptionalKeywords = DSL.keyword(includingOptionalKeywords);
this.excludingOptionalKeywords = DSL.keyword(excludingOptionalKeywords);
this.defaultKeyword = DSL.keyword(defaultSql);
this.qualified = qualified;
}
public final String toSQL() {
return sql;
return defaultSql;
}
public final Keyword toKeyword() {
return keyword;
return defaultKeyword;
}
public final Keyword toKeyword(boolean includeOptionalKeywords) {
return includeOptionalKeywords ? includingOptionalKeywords : excludingOptionalKeywords;
}
/**

View File

@ -0,0 +1,40 @@
package org.jooq.conf;
import javax.xml.bind.annotation.XmlEnum;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for RenderOptionalKeyword.
*
* <p>The following schema fragment specifies the expected content contained within this class.
* <p>
* <pre>
* &lt;simpleType name="RenderOptionalKeyword"&gt;
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}string"&gt;
* &lt;enumeration value="OFF"/&gt;
* &lt;enumeration value="ON"/&gt;
* &lt;enumeration value="DEFAULT"/&gt;
* &lt;/restriction&gt;
* &lt;/simpleType&gt;
* </pre>
*
*/
@XmlType(name = "RenderOptionalKeyword")
@XmlEnum
public enum RenderOptionalKeyword {
OFF,
ON,
DEFAULT;
public String value() {
return name();
}
public static RenderOptionalKeyword fromValue(String v) {
return valueOf(v);
}
}

View File

@ -65,6 +65,12 @@ public class Settings
@XmlElement(defaultValue = "false")
protected Boolean renderFormatted = false;
protected RenderFormatting renderFormatting;
@XmlElement(defaultValue = "DEFAULT")
@XmlSchemaType(name = "string")
protected RenderOptionalKeyword renderOptionalInnerKeyword = RenderOptionalKeyword.DEFAULT;
@XmlElement(defaultValue = "DEFAULT")
@XmlSchemaType(name = "string")
protected RenderOptionalKeyword renderOptionalOuterKeyword = RenderOptionalKeyword.DEFAULT;
@XmlElement(defaultValue = "false")
protected Boolean renderScalarSubqueriesForStoredFunctions = false;
@XmlElement(defaultValue = "true")
@ -473,6 +479,38 @@ public class Settings
this.renderFormatting = value;
}
/**
* Whether to render the optional <code>INNER</code> keyword in <code>INNER JOIN</code>, if it is optional in the output dialect.
*
*/
public RenderOptionalKeyword getRenderOptionalInnerKeyword() {
return renderOptionalInnerKeyword;
}
/**
* Whether to render the optional <code>INNER</code> keyword in <code>INNER JOIN</code>, if it is optional in the output dialect.
*
*/
public void setRenderOptionalInnerKeyword(RenderOptionalKeyword value) {
this.renderOptionalInnerKeyword = value;
}
/**
* Whether to render the optional <code>OUTER</code> keyword in <code>OUTER JOIN</code>, if it is optional in the output dialect.
*
*/
public RenderOptionalKeyword getRenderOptionalOuterKeyword() {
return renderOptionalOuterKeyword;
}
/**
* Whether to render the optional <code>OUTER</code> keyword in <code>OUTER JOIN</code>, if it is optional in the output dialect.
*
*/
public void setRenderOptionalOuterKeyword(RenderOptionalKeyword value) {
this.renderOptionalOuterKeyword = value;
}
/**
* Whether stored function calls should be wrapped in scalar subqueries.
* <p>
@ -1765,6 +1803,24 @@ public class Settings
return this;
}
/**
* Whether to render the optional <code>INNER</code> keyword in <code>INNER JOIN</code>, if it is optional in the output dialect.
*
*/
public Settings withRenderOptionalInnerKeyword(RenderOptionalKeyword value) {
setRenderOptionalInnerKeyword(value);
return this;
}
/**
* Whether to render the optional <code>OUTER</code> keyword in <code>OUTER JOIN</code>, if it is optional in the output dialect.
*
*/
public Settings withRenderOptionalOuterKeyword(RenderOptionalKeyword value) {
setRenderOptionalOuterKeyword(value);
return this;
}
public Settings withRenderScalarSubqueriesForStoredFunctions(Boolean value) {
setRenderScalarSubqueriesForStoredFunctions(value);
return this;
@ -2208,6 +2264,8 @@ public class Settings
builder.append("renderLocale", renderLocale);
builder.append("renderFormatted", renderFormatted);
builder.append("renderFormatting", renderFormatting);
builder.append("renderOptionalInnerKeyword", renderOptionalInnerKeyword);
builder.append("renderOptionalOuterKeyword", renderOptionalOuterKeyword);
builder.append("renderScalarSubqueriesForStoredFunctions", renderScalarSubqueriesForStoredFunctions);
builder.append("renderOrderByRownumberForEmulatedPagination", renderOrderByRownumberForEmulatedPagination);
builder.append("renderOutputForSQLServerReturningClause", renderOutputForSQLServerReturningClause);
@ -2392,6 +2450,24 @@ public class Settings
return false;
}
}
if (renderOptionalInnerKeyword == null) {
if (other.renderOptionalInnerKeyword!= null) {
return false;
}
} else {
if (!renderOptionalInnerKeyword.equals(other.renderOptionalInnerKeyword)) {
return false;
}
}
if (renderOptionalOuterKeyword == null) {
if (other.renderOptionalOuterKeyword!= null) {
return false;
}
} else {
if (!renderOptionalOuterKeyword.equals(other.renderOptionalOuterKeyword)) {
return false;
}
}
if (renderScalarSubqueriesForStoredFunctions == null) {
if (other.renderScalarSubqueriesForStoredFunctions!= null) {
return false;
@ -2906,6 +2982,8 @@ public class Settings
result = ((prime*result)+((renderLocale == null)? 0 :renderLocale.hashCode()));
result = ((prime*result)+((renderFormatted == null)? 0 :renderFormatted.hashCode()));
result = ((prime*result)+((renderFormatting == null)? 0 :renderFormatting.hashCode()));
result = ((prime*result)+((renderOptionalInnerKeyword == null)? 0 :renderOptionalInnerKeyword.hashCode()));
result = ((prime*result)+((renderOptionalOuterKeyword == null)? 0 :renderOptionalOuterKeyword.hashCode()));
result = ((prime*result)+((renderScalarSubqueriesForStoredFunctions == null)? 0 :renderScalarSubqueriesForStoredFunctions.hashCode()));
result = ((prime*result)+((renderOrderByRownumberForEmulatedPagination == null)? 0 :renderOrderByRownumberForEmulatedPagination.hashCode()));
result = ((prime*result)+((renderOutputForSQLServerReturningClause == null)? 0 :renderOutputForSQLServerReturningClause.hashCode()));

View File

@ -90,7 +90,7 @@ import static org.jooq.impl.DSL.exists;
import static org.jooq.impl.DSL.notExists;
import static org.jooq.impl.DSL.selectOne;
import static org.jooq.impl.Keywords.K_CROSS_JOIN_LATERAL;
import static org.jooq.impl.Keywords.K_INNER_JOIN;
import static org.jooq.impl.Keywords.K_LEFT_JOIN_LATERAL;
import static org.jooq.impl.Keywords.K_LEFT_OUTER_JOIN_LATERAL;
import static org.jooq.impl.Keywords.K_ON;
import static org.jooq.impl.Keywords.K_PARTITION_BY;
@ -126,6 +126,7 @@ import org.jooq.TableOnConditionStep;
import org.jooq.TableOptionalOnStep;
import org.jooq.TableOuterJoinStep;
import org.jooq.TablePartitionByStep;
import org.jooq.conf.RenderOptionalKeyword;
import org.jooq.exception.DataAccessException;
/**
@ -205,19 +206,7 @@ implements
public final void accept(Context<?> ctx) {
JoinType translatedType = translateType(ctx);
Clause translatedClause = translateClause(translatedType);
Keyword keyword = translatedType.toKeyword();
if (translatedType == CROSS_APPLY && EMULATE_APPLY.contains(ctx.family()))
keyword = K_CROSS_JOIN_LATERAL;
else if (translatedType == OUTER_APPLY && EMULATE_APPLY.contains(ctx.family()))
keyword = K_LEFT_OUTER_JOIN_LATERAL;
Keyword keyword = translateKeyword(ctx, translatedType);
toSQLTable(ctx, lhs);
@ -308,6 +297,63 @@ implements
.formatIndentEnd();
}
private final Keyword translateKeyword(Context<?> ctx, JoinType translatedType) {
Keyword keyword;
switch (translatedType) {
case JOIN:
case NATURAL_JOIN:
if (ctx.settings().getRenderOptionalInnerKeyword() == RenderOptionalKeyword.ON)
keyword = translatedType.toKeyword(true);
else
keyword = translatedType.toKeyword();
break;
case LEFT_OUTER_JOIN:
case NATURAL_LEFT_OUTER_JOIN:
case RIGHT_OUTER_JOIN:
case NATURAL_RIGHT_OUTER_JOIN:
case FULL_OUTER_JOIN:
case NATURAL_FULL_OUTER_JOIN:
if (ctx.settings().getRenderOptionalOuterKeyword() == RenderOptionalKeyword.OFF)
keyword = translatedType.toKeyword(false);
else
keyword = translatedType.toKeyword();
break;
default:
keyword = translatedType.toKeyword();
break;
}
if (translatedType == CROSS_APPLY && EMULATE_APPLY.contains(ctx.family()))
keyword = K_CROSS_JOIN_LATERAL;
else if (translatedType == OUTER_APPLY && EMULATE_APPLY.contains(ctx.family()))
if (ctx.settings().getRenderOptionalOuterKeyword() == RenderOptionalKeyword.OFF)
keyword = K_LEFT_JOIN_LATERAL;
else
keyword = K_LEFT_OUTER_JOIN_LATERAL;
return keyword;
}
private void toSQLTable(Context<?> ctx, Table<?> table) {
// [#671] Some databases formally require nested JOINS on the right hand

View File

@ -205,6 +205,7 @@ final class Keywords {
static final Keyword K_LATERAL = keyword("lateral");
static final Keyword K_LEADING = keyword("leading");
static final Keyword K_LEAVE = keyword("leave");
static final Keyword K_LEFT_JOIN_LATERAL = keyword("left join lateral");
static final Keyword K_LEFT_OUTER_JOIN_LATERAL = keyword("left outer join lateral");
static final Keyword K_LIKE = keyword("like");
static final Keyword K_LIKE_REGEX = keyword("like_regex");

View File

@ -110,6 +110,14 @@ providing a name to parameters, resulting in <code>:1</code> or <code>@1</code>
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[All sorts of formatting flags / settings.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="renderOptionalInnerKeyword" type="jooq-runtime:RenderOptionalKeyword" minOccurs="0" maxOccurs="1" default="DEFAULT">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[Whether to render the optional <code>INNER</code> keyword in <code>INNER JOIN</code>, if it is optional in the output dialect.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="renderOptionalOuterKeyword" type="jooq-runtime:RenderOptionalKeyword" minOccurs="0" maxOccurs="1" default="DEFAULT">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[Whether to render the optional <code>OUTER</code> keyword in <code>OUTER JOIN</code>, if it is optional in the output dialect.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="renderScalarSubqueriesForStoredFunctions" type="boolean" minOccurs="0" maxOccurs="1" default="false">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[Whether stored function calls should be wrapped in scalar subqueries.
<p>
@ -765,6 +773,20 @@ Either &lt;input/&gt; or &lt;inputExpression/&gt; must be provided]]></jxb:javad
</restriction>
</simpleType>
<simpleType name="RenderOptionalKeyword">
<restriction base="string">
<!-- Optional keywords will never be generated (does not affect mandatory keywords). -->
<enumeration value="OFF"/>
<!-- Optional keywords will always be generated (does not affect mandatory keywords). -->
<enumeration value="ON"/>
<!-- The default applies for optional keywords. -->
<enumeration value="DEFAULT"/>
</restriction>
</simpleType>
<simpleType name="ParseWithMetaLookups">
<restriction base="string">