diff --git a/jOOQ/src/main/java/org/jooq/conf/Settings.java b/jOOQ/src/main/java/org/jooq/conf/Settings.java index 98acbbe77a..7edd012713 100644 --- a/jOOQ/src/main/java/org/jooq/conf/Settings.java +++ b/jOOQ/src/main/java/org/jooq/conf/Settings.java @@ -93,6 +93,9 @@ public class Settings @XmlElement(defaultValue = "DEFAULT") @XmlSchemaType(name = "string") protected RenderImplicitJoinType renderImplicitJoinType = RenderImplicitJoinType.DEFAULT; + @XmlElement(defaultValue = "DEFAULT") + @XmlSchemaType(name = "string") + protected RenderImplicitJoinType renderImplicitJoinToManyType = RenderImplicitJoinType.DEFAULT; @XmlElement(defaultValue = "IMPLICIT_NULL") @XmlSchemaType(name = "string") protected RenderDefaultNullability renderDefaultNullability = RenderDefaultNullability.IMPLICIT_NULL; @@ -1027,6 +1030,32 @@ public class Settings this.renderImplicitJoinType = value; } + /** + * The join type to be generated by implicit joins for to-many paths in {@link org.jooq.Select} queries. + *

+ * The DEFAULT is SCALAR_SUBQUERY if the join path is implicit only, i.e. absent from + * the FROM clause, to prevent accidental cartesian products, or LEFT_JOIN if declared + * explicitly in the FROM clause. In DML statements, it is always SCALAR_SUBQUERY, + * unless DML joins are supported. + * + */ + public RenderImplicitJoinType getRenderImplicitJoinToManyType() { + return renderImplicitJoinToManyType; + } + + /** + * The join type to be generated by implicit joins for to-many paths in {@link org.jooq.Select} queries. + *

+ * The DEFAULT is SCALAR_SUBQUERY if the join path is implicit only, i.e. absent from + * the FROM clause, to prevent accidental cartesian products, or LEFT_JOIN if declared + * explicitly in the FROM clause. In DML statements, it is always SCALAR_SUBQUERY, + * unless DML joins are supported. + * + */ + public void setRenderImplicitJoinToManyType(RenderImplicitJoinType value) { + this.renderImplicitJoinToManyType = value; + } + /** * Whether the {@link org.jooq.Nullability#DEFAULT} nullablity should be rendered in generated DDL, and how it should be rendered. * @@ -6232,6 +6261,20 @@ public class Settings return this; } + /** + * The join type to be generated by implicit joins for to-many paths in {@link org.jooq.Select} queries. + *

+ * The DEFAULT is SCALAR_SUBQUERY if the join path is implicit only, i.e. absent from + * the FROM clause, to prevent accidental cartesian products, or LEFT_JOIN if declared + * explicitly in the FROM clause. In DML statements, it is always SCALAR_SUBQUERY, + * unless DML joins are supported. + * + */ + public Settings withRenderImplicitJoinToManyType(RenderImplicitJoinType value) { + setRenderImplicitJoinToManyType(value); + return this; + } + /** * Whether the {@link org.jooq.Nullability#DEFAULT} nullablity should be rendered in generated DDL, and how it should be rendered. * @@ -7646,6 +7689,7 @@ public class Settings builder.append("renderImplicitWindowRange", renderImplicitWindowRange); builder.append("renderScalarSubqueriesForStoredFunctions", renderScalarSubqueriesForStoredFunctions); builder.append("renderImplicitJoinType", renderImplicitJoinType); + builder.append("renderImplicitJoinToManyType", renderImplicitJoinToManyType); builder.append("renderDefaultNullability", renderDefaultNullability); builder.append("renderCoalesceToEmptyStringInConcat", renderCoalesceToEmptyStringInConcat); builder.append("renderOrderByRownumberForEmulatedPagination", renderOrderByRownumberForEmulatedPagination); @@ -8065,6 +8109,15 @@ public class Settings return false; } } + if (renderImplicitJoinToManyType == null) { + if (other.renderImplicitJoinToManyType!= null) { + return false; + } + } else { + if (!renderImplicitJoinToManyType.equals(other.renderImplicitJoinToManyType)) { + return false; + } + } if (renderDefaultNullability == null) { if (other.renderDefaultNullability!= null) { return false; @@ -9894,6 +9947,7 @@ public class Settings result = ((prime*result)+((renderImplicitWindowRange == null)? 0 :renderImplicitWindowRange.hashCode())); result = ((prime*result)+((renderScalarSubqueriesForStoredFunctions == null)? 0 :renderScalarSubqueriesForStoredFunctions.hashCode())); result = ((prime*result)+((renderImplicitJoinType == null)? 0 :renderImplicitJoinType.hashCode())); + result = ((prime*result)+((renderImplicitJoinToManyType == null)? 0 :renderImplicitJoinToManyType.hashCode())); result = ((prime*result)+((renderDefaultNullability == null)? 0 :renderDefaultNullability.hashCode())); result = ((prime*result)+((renderCoalesceToEmptyStringInConcat == null)? 0 :renderCoalesceToEmptyStringInConcat.hashCode())); result = ((prime*result)+((renderOrderByRownumberForEmulatedPagination == null)? 0 :renderOrderByRownumberForEmulatedPagination.hashCode())); diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java b/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java index 25a30ca2bd..25a866f2a6 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java @@ -1261,7 +1261,7 @@ abstract class AbstractContext> extends AbstractScope imple Table t = e.getValue().joinTree(); result = result - .join(t, node.joinType(LEFT_OUTER_JOIN)) + .join(t, node.joinToManyType()) .on(onKey0(e.getKey().getForeignKey(), t, result)); } @@ -1298,6 +1298,15 @@ abstract class AbstractContext> extends AbstractScope imple } } + private final JoinType joinToManyType() { + switch (defaultIfNull(Tools.settings(ctx.configuration()).getRenderImplicitJoinToManyType(), RenderImplicitJoinType.DEFAULT)) { + case INNER_JOIN: + return JOIN; + default: + return LEFT_OUTER_JOIN; + } + } + final boolean hasJoinPaths() { return !pathsToOne.isEmpty() || !pathsToMany.isEmpty(); } diff --git a/jOOQ/src/main/java/org/jooq/impl/TableFieldImpl.java b/jOOQ/src/main/java/org/jooq/impl/TableFieldImpl.java index b5e5632390..e5efcea8f8 100644 --- a/jOOQ/src/main/java/org/jooq/impl/TableFieldImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/TableFieldImpl.java @@ -43,6 +43,7 @@ import static java.util.stream.Collectors.joining; import static org.jooq.Clause.FIELD; import static org.jooq.Clause.FIELD_REFERENCE; // ... +import static org.jooq.conf.RenderImplicitJoinType.DEFAULT; import static org.jooq.conf.RenderImplicitJoinType.SCALAR_SUBQUERY; import static org.jooq.impl.DSL.select; import static org.jooq.impl.DefaultMetaProvider.meta; @@ -69,6 +70,7 @@ import org.jooq.RowId; import org.jooq.Table; import org.jooq.TableField; import org.jooq.Update; +import org.jooq.conf.RenderImplicitJoinType; import org.jooq.impl.QOM.UEmpty; import org.jooq.impl.Tools.SimpleDataKey; import org.jooq.tools.StringUtils; @@ -202,7 +204,13 @@ implements } private final boolean implicitJoinAsScalarSubquery(Context ctx, TableImpl t, Table root) { - return (t.childPath != null && ctx.settings().getRenderImplicitJoinType() == SCALAR_SUBQUERY) + return + + // [#15754] Explicit scalar subqueries can be configured for implicit to-one paths + (t.childPath != null && ctx.settings().getRenderImplicitJoinType() == SCALAR_SUBQUERY && !ctx.inScope(t)) + + // [#15755] The default is to render scalar subqueries for implicit to-many paths + || (t.parentPath != null && (ctx.settings().getRenderImplicitJoinToManyType() == DEFAULT || ctx.settings().getRenderImplicitJoinToManyType() == SCALAR_SUBQUERY) && !ctx.inScope(t)) // [#7508] Implicit join path references inside of DML queries have to // be emulated differently diff --git a/jOOQ/src/main/resources/org/jooq/xsd/jooq-runtime-3.19.0.xsd b/jOOQ/src/main/resources/org/jooq/xsd/jooq-runtime-3.19.0.xsd index e24756246f..55681ae34a 100644 --- a/jOOQ/src/main/resources/org/jooq/xsd/jooq-runtime-3.19.0.xsd +++ b/jOOQ/src/main/resources/org/jooq/xsd/jooq-runtime-3.19.0.xsd @@ -168,6 +168,15 @@ The DEFAULT is dependent on the nullability of the foreign key (INNER_JOIN for non-nullable foreign keys). In DML statements, it is always SCALAR_SUBQUERY, unless DML joins are supported.]]> + + + +The DEFAULT is SCALAR_SUBQUERY if the join path is implicit only, i.e. absent from +the FROM clause, to prevent accidental cartesian products, or LEFT_JOIN if declared +explicitly in the FROM clause. In DML statements, it is always SCALAR_SUBQUERY, +unless DML joins are supported.]]> +