From 6b03c3f0debf8e82b64e309265d74fa9bde1c9c2 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Tue, 24 May 2022 12:29:41 +0200 Subject: [PATCH] [jOOQ/jOOQ#13560] Use MULTISET emulation for nested rows with LIMIT In older Oracle versions, we emulate LIMIT with ROWNUM calculations in derived tables. In those cases, it seems very hard to keep the flattening nested record emulation working. Better work with the MULTISET emulation of nested records, instead. This includes parts of the implementation of [jOOQ/jOOQ#13599] --- .../main/java/org/jooq/impl/AbstractBindContext.java | 5 +++-- .../src/main/java/org/jooq/impl/AbstractContext.java | 8 +++++++- jOOQ/src/main/java/org/jooq/impl/AbstractQuery.java | 8 ++++---- jOOQ/src/main/java/org/jooq/impl/BatchSingle.java | 2 +- jOOQ/src/main/java/org/jooq/impl/CursorImpl.java | 9 +++++++-- .../main/java/org/jooq/impl/DefaultBindContext.java | 5 +++-- .../main/java/org/jooq/impl/DefaultDSLContext.java | 4 ++-- .../java/org/jooq/impl/DefaultRenderContext.java | 12 +++++------- jOOQ/src/main/java/org/jooq/impl/ParamCollector.java | 2 +- .../main/java/org/jooq/impl/ParsingConnection.java | 2 +- jOOQ/src/main/java/org/jooq/impl/R2DBC.java | 6 +++--- .../src/main/java/org/jooq/impl/SelectQueryImpl.java | 6 ++++++ jOOQ/src/main/java/org/jooq/impl/Tools.java | 10 ++++++++-- 13 files changed, 51 insertions(+), 28 deletions(-) diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractBindContext.java b/jOOQ/src/main/java/org/jooq/impl/AbstractBindContext.java index 5b2ab15c4f..0d2c7f6916 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractBindContext.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractBindContext.java @@ -42,6 +42,7 @@ import java.sql.SQLException; import org.jooq.BindContext; import org.jooq.Configuration; +import org.jooq.ExecuteContext; import org.jooq.Field; import org.jooq.QueryPart; import org.jooq.QueryPartInternal; @@ -54,8 +55,8 @@ import org.jooq.exception.DataAccessException; */ abstract class AbstractBindContext extends AbstractContext implements BindContext { - AbstractBindContext(Configuration configuration, PreparedStatement stmt) { - super(configuration, stmt); + AbstractBindContext(Configuration configuration, ExecuteContext ctx, PreparedStatement stmt) { + super(configuration, ctx, stmt); } // ------------------------------------------------------------------------ diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java b/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java index 6c5c01ebb2..372998b593 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java @@ -54,6 +54,7 @@ import static org.jooq.impl.Tools.EMPTY_QUERYPART; import static org.jooq.impl.Tools.lazy; import static org.jooq.impl.Tools.BooleanDataKey.DATA_NESTED_SET_OPERATIONS; import static org.jooq.impl.Tools.BooleanDataKey.DATA_OMIT_CLAUSE_EVENT_EMISSION; +import static org.jooq.impl.Tools.SimpleDataKey.DATA_EXECUTE_CONTEXT; import java.sql.PreparedStatement; import java.text.DecimalFormat; @@ -79,6 +80,7 @@ import org.jooq.Condition; import org.jooq.Configuration; import org.jooq.Context; import org.jooq.DSLContext; +import org.jooq.ExecuteContext; import org.jooq.Field; import org.jooq.ForeignKey; import org.jooq.JoinType; @@ -159,8 +161,12 @@ abstract class AbstractContext> extends AbstractScope imple private transient DecimalFormat doubleFormat; private transient DecimalFormat floatFormat; - AbstractContext(Configuration configuration, PreparedStatement stmt) { + AbstractContext(Configuration configuration, ExecuteContext ctx, PreparedStatement stmt) { super(configuration); + + if (ctx != null) + data(DATA_EXECUTE_CONTEXT, ctx); + this.stmt = stmt; VisitListenerProvider[] providers = configuration.visitListenerProviders(); diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractQuery.java b/jOOQ/src/main/java/org/jooq/impl/AbstractQuery.java index 095b433701..30fe855db4 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractQuery.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractQuery.java @@ -465,23 +465,23 @@ abstract class AbstractQuery extends AbstractAttachableQueryPa // [#6474] [#6929] Can this be communicated in a leaner way? if (ctx.type() == DDL) { ctx.data(DATA_FORCE_STATIC_STATEMENT, true); - render = new DefaultRenderContext(c); + render = new DefaultRenderContext(c, ctx); result = new Rendered(render.paramType(INLINED).visit(this).render(), null, render.skipUpdateCounts()); } else if (executePreparedStatements(configuration().settings())) { try { - render = new DefaultRenderContext(c); + render = new DefaultRenderContext(c, ctx); render.data(DATA_COUNT_BIND_VALUES, true); result = new Rendered(render.visit(this).render(), render.bindValues(), render.skipUpdateCounts()); } catch (DefaultRenderContext.ForceInlineSignal e) { ctx.data(DATA_FORCE_STATIC_STATEMENT, true); - render = new DefaultRenderContext(c); + render = new DefaultRenderContext(c, ctx); result = new Rendered(render.paramType(INLINED).visit(this).render(), null, render.skipUpdateCounts()); } } else { - render = new DefaultRenderContext(c); + render = new DefaultRenderContext(c, ctx); result = new Rendered(render.paramType(INLINED).visit(this).render(), null, render.skipUpdateCounts()); } diff --git a/jOOQ/src/main/java/org/jooq/impl/BatchSingle.java b/jOOQ/src/main/java/org/jooq/impl/BatchSingle.java index f9fcd6bcd2..3160366dab 100644 --- a/jOOQ/src/main/java/org/jooq/impl/BatchSingle.java +++ b/jOOQ/src/main/java/org/jooq/impl/BatchSingle.java @@ -218,7 +218,7 @@ final class BatchSingle extends AbstractBatch implements BatchBindStep { // list to preserve type information // [#3547] The original query may have no Params specified - e.g. when it was constructed with // plain SQL. In that case, infer the bind value type directly from the bind value - visitAll(new DefaultBindContext(configuration, ctx.statement()), + visitAll(new DefaultBindContext(configuration, ctx, ctx.statement()), (params.length > 0) ? fields(bindValues, params) : fields(bindValues)); diff --git a/jOOQ/src/main/java/org/jooq/impl/CursorImpl.java b/jOOQ/src/main/java/org/jooq/impl/CursorImpl.java index 4f0380d1eb..1d44e984f7 100644 --- a/jOOQ/src/main/java/org/jooq/impl/CursorImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/CursorImpl.java @@ -37,6 +37,7 @@ */ package org.jooq.impl; +import static java.lang.Boolean.TRUE; import static java.util.Collections.emptyList; // ... import static org.jooq.impl.RowAsField.NO_NATIVE_SUPPORT; @@ -44,6 +45,7 @@ import static org.jooq.impl.Tools.embeddedFields; import static org.jooq.impl.Tools.embeddedRecordType; import static org.jooq.impl.Tools.recordFactory; import static org.jooq.impl.Tools.uncoerce; +import static org.jooq.impl.Tools.BooleanDataKey.DATA_MULTISET_CONTENT; import java.io.InputStream; import java.io.Reader; @@ -78,7 +80,6 @@ import org.jooq.BindingGetResultSetContext; import org.jooq.Converter; import org.jooq.ExecuteContext; import org.jooq.ExecuteListener; -import org.jooq.ExecuteType; import org.jooq.Field; // ... import org.jooq.Record; @@ -1548,7 +1549,11 @@ final class CursorImpl extends AbstractCursor { // RowField may have a Row[N].mapping(...) applied Field f = uncoerce(field); - if (f instanceof AbstractRowAsField && NO_NATIVE_SUPPORT.contains(ctx.dialect())) { + // [#13560] Queries may decide themselves to replace the + // flattening emulation by the MULTISET emulation + if (f instanceof AbstractRowAsField + && NO_NATIVE_SUPPORT.contains(ctx.dialect()) + && !TRUE.equals(ctx.data(DATA_MULTISET_CONTENT))) { nested = ((AbstractRowAsField) f).emulatedFields(configuration); recordType = (Class) ((AbstractRowAsField) f).getRecordType(); } diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultBindContext.java b/jOOQ/src/main/java/org/jooq/impl/DefaultBindContext.java index 5e7d18ce4d..8da884f68e 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultBindContext.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultBindContext.java @@ -42,6 +42,7 @@ import java.sql.SQLException; import org.jooq.BindContext; import org.jooq.Configuration; +import org.jooq.ExecuteContext; import org.jooq.Field; /** @@ -49,8 +50,8 @@ import org.jooq.Field; */ final class DefaultBindContext extends AbstractBindContext { - DefaultBindContext(Configuration configuration, PreparedStatement stmt) { - super(configuration, stmt); + DefaultBindContext(Configuration configuration, ExecuteContext ctx, PreparedStatement stmt) { + super(configuration, ctx, stmt); } @Override diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultDSLContext.java b/jOOQ/src/main/java/org/jooq/impl/DefaultDSLContext.java index 77b83b5a57..e44a164ed9 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultDSLContext.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultDSLContext.java @@ -716,7 +716,7 @@ public class DefaultDSLContext extends AbstractScope implements DSLContext, Seri @Override public RenderContext renderContext() { - return new DefaultRenderContext(configuration()); + return new DefaultRenderContext(configuration(), null); } @Override @@ -760,7 +760,7 @@ public class DefaultDSLContext extends AbstractScope implements DSLContext, Seri @Override public BindContext bindContext(PreparedStatement stmt) { - return new DefaultBindContext(configuration(), stmt); + return new DefaultBindContext(configuration(), null, stmt); } // ------------------------------------------------------------------------- diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultRenderContext.java b/jOOQ/src/main/java/org/jooq/impl/DefaultRenderContext.java index 3bb98226a7..c415fbd303 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultRenderContext.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultRenderContext.java @@ -46,10 +46,9 @@ import static org.jooq.impl.Identifiers.QUOTES; import static org.jooq.impl.Identifiers.QUOTE_END_DELIMITER; import static org.jooq.impl.Identifiers.QUOTE_END_DELIMITER_ESCAPED; import static org.jooq.impl.Identifiers.QUOTE_START_DELIMITER; -import static org.jooq.impl.Tools.DATAKEY_RESET_IN_SUBQUERY_SCOPE; -import static org.jooq.impl.Tools.lazy; import static org.jooq.impl.Tools.BooleanDataKey.DATA_COUNT_BIND_VALUES; import static org.jooq.impl.Tools.SimpleDataKey.DATA_APPEND_SQL; +import static org.jooq.impl.Tools.SimpleDataKey.DATA_EXECUTE_CONTEXT; import static org.jooq.impl.Tools.SimpleDataKey.DATA_PREPEND_SQL; import java.util.ArrayDeque; @@ -64,6 +63,7 @@ import java.util.regex.Pattern; import org.jooq.BindContext; import org.jooq.Configuration; import org.jooq.Constants; +import org.jooq.ExecuteContext; import org.jooq.Field; import org.jooq.ForeignKey; import org.jooq.Param; @@ -82,9 +82,7 @@ import org.jooq.conf.Settings; import org.jooq.conf.SettingsTools; import org.jooq.exception.ControlFlowSignal; import org.jooq.exception.DataAccessException; -import org.jooq.impl.AbstractContext.ScopeStackElement; import org.jooq.impl.ScopeMarker.ScopeContent; -import org.jooq.impl.Tools.DataKey; import org.jooq.tools.JooqLogger; import org.jooq.tools.StringUtils; @@ -121,8 +119,8 @@ class DefaultRenderContext extends AbstractContext implements Ren String cachedNewline; int cachedPrintMargin; - DefaultRenderContext(Configuration configuration) { - super(configuration, null); + DefaultRenderContext(Configuration configuration, ExecuteContext ctx) { + super(configuration, ctx, null); Settings settings = configuration.settings(); @@ -148,7 +146,7 @@ class DefaultRenderContext extends AbstractContext implements Ren } DefaultRenderContext(RenderContext context, boolean copyLocalState) { - this(context.configuration()); + this(context.configuration(), (ExecuteContext) context.data(DATA_EXECUTE_CONTEXT)); paramType(context.paramType()); qualifyCatalog(context.qualifyCatalog()); diff --git a/jOOQ/src/main/java/org/jooq/impl/ParamCollector.java b/jOOQ/src/main/java/org/jooq/impl/ParamCollector.java index 29fdd8b1f7..f86da9b73c 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ParamCollector.java +++ b/jOOQ/src/main/java/org/jooq/impl/ParamCollector.java @@ -68,7 +68,7 @@ final class ParamCollector extends AbstractBindContext { private final boolean includeInlinedParams; ParamCollector(Configuration configuration, boolean includeInlinedParams) { - super(configuration, null); + super(configuration, null, null); this.includeInlinedParams = includeInlinedParams; } diff --git a/jOOQ/src/main/java/org/jooq/impl/ParsingConnection.java b/jOOQ/src/main/java/org/jooq/impl/ParsingConnection.java index 267a37f1e5..238b4d4eed 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ParsingConnection.java +++ b/jOOQ/src/main/java/org/jooq/impl/ParsingConnection.java @@ -195,7 +195,7 @@ final class ParsingConnection extends DefaultConnection { if (i > 0) rendered = translate(configuration, sql, p.get(i).toArray(EMPTY_PARAM)); - new DefaultBindContext(configuration, s).visit(rendered.bindValues); + new DefaultBindContext(configuration, null, s).visit(rendered.bindValues); // TODO: Find a less hacky way to signal that we're batching. Currently: // - ArrayList>> = batching diff --git a/jOOQ/src/main/java/org/jooq/impl/R2DBC.java b/jOOQ/src/main/java/org/jooq/impl/R2DBC.java index d2f1f0cd28..4cdc1f777e 100644 --- a/jOOQ/src/main/java/org/jooq/impl/R2DBC.java +++ b/jOOQ/src/main/java/org/jooq/impl/R2DBC.java @@ -400,7 +400,7 @@ final class R2DBC { try { Rendered rendered = rendered(configuration, query); Statement stmt = c.createStatement(sql = rendered.sql); - new DefaultBindContext(configuration, new R2DBCPreparedStatement(configuration, stmt)).visit(rendered.bindValues); + new DefaultBindContext(configuration, null, new R2DBCPreparedStatement(configuration, stmt)).visit(rendered.bindValues); // TODO: Reuse org.jooq.impl.Tools.setFetchSize(ExecuteContext ctx, int fetchSize) AbstractResultQuery q1 = abstractResultQuery(query); @@ -501,7 +501,7 @@ final class R2DBC { // list to preserve type information // [#3547] The original query may have no Params specified - e.g. when it was constructed with // plain SQL. In that case, infer the bind value type directly from the bind value - visitAll(new DefaultBindContext(batch.configuration, new R2DBCPreparedStatement(batch.query.configuration(), stmt)), + visitAll(new DefaultBindContext(batch.configuration, null, new R2DBCPreparedStatement(batch.query.configuration(), stmt)), (params.length > 0) ? fields(bindValues, params) : fields(bindValues)); @@ -728,7 +728,7 @@ final class R2DBC { static final Rendered rendered(Configuration configuration, Query query) { DefaultRenderContext render = new DefaultRenderContext(configuration.deriveSettings(s -> setParamType(configuration.dialect(), s) - )); + ), null); return new Rendered(render.paramType(NAMED).visit(query).render(), render.bindValues(), render.skipUpdateCounts()); } diff --git a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java index 91ca2cb202..8a09491bfc 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java @@ -208,6 +208,7 @@ import static org.jooq.impl.Tools.BooleanDataKey.DATA_COLLECT_SEMI_ANTI_JOIN; import static org.jooq.impl.Tools.BooleanDataKey.DATA_FORCE_LIMIT_WITH_ORDER_BY; import static org.jooq.impl.Tools.BooleanDataKey.DATA_INSERT_SELECT; import static org.jooq.impl.Tools.BooleanDataKey.DATA_INSERT_SELECT_WITHOUT_INSERT_COLUMN_LIST; +import static org.jooq.impl.Tools.BooleanDataKey.DATA_MULTISET_CONTENT; import static org.jooq.impl.Tools.BooleanDataKey.DATA_NESTED_SET_OPERATIONS; import static org.jooq.impl.Tools.BooleanDataKey.DATA_OMIT_INTO_CLAUSE; import static org.jooq.impl.Tools.BooleanDataKey.DATA_RENDER_TRAILING_LIMIT_IF_APPLICABLE; @@ -216,6 +217,7 @@ import static org.jooq.impl.Tools.BooleanDataKey.DATA_WRAP_DERIVED_TABLES_IN_PAR import static org.jooq.impl.Tools.ExtendedDataKey.DATA_TRANSFORM_ROWNUM_TO_LIMIT; import static org.jooq.impl.Tools.SimpleDataKey.DATA_COLLECTED_SEMI_ANTI_JOIN; import static org.jooq.impl.Tools.SimpleDataKey.DATA_DML_TARGET_TABLE; +import static org.jooq.impl.Tools.SimpleDataKey.DATA_EXECUTE_CONTEXT; import static org.jooq.impl.Tools.SimpleDataKey.DATA_OVERRIDE_ALIASES_IN_ORDER_BY; import static org.jooq.impl.Tools.SimpleDataKey.DATA_RENDERING_DATA_CHANGE_DELTA_TABLE; import static org.jooq.impl.Tools.SimpleDataKey.DATA_SELECT_ALIASES; @@ -248,6 +250,7 @@ import org.jooq.Condition; import org.jooq.Configuration; import org.jooq.Context; import org.jooq.DataType; +import org.jooq.ExecuteContext; import org.jooq.Field; import org.jooq.ForeignKey; import org.jooq.GeneratorStatementType; @@ -2052,6 +2055,9 @@ final class SelectQueryImpl extends AbstractResultQuery imp + + + diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index 78857d8182..a69863b95e 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -188,6 +188,7 @@ import static org.jooq.impl.SQLDataType.VARCHAR; import static org.jooq.impl.SQLDataType.XML; import static org.jooq.impl.Tools.anyMatch; import static org.jooq.impl.Tools.SimpleDataKey.DATA_BLOCK_NESTING; +import static org.jooq.impl.Tools.SimpleDataKey.DATA_EXECUTE_CONTEXT; import static org.jooq.tools.StringUtils.defaultIfNull; import java.lang.annotation.Annotation; @@ -673,6 +674,12 @@ final class Tools { */ enum SimpleDataKey implements DataKey { + /** + * [#13560] [#13599] The {@link ExecuteContext} in whose scope another + * {@link Scope} may have been created. + */ + DATA_EXECUTE_CONTEXT, + /** * The level of anonymous block nesting, in case we're generating a block. */ @@ -2776,8 +2783,7 @@ final class Tools { char[] sqlChars = sql.toCharArray(); // [#1593] Create a dummy renderer if we're in bind mode - if (render == null) render = new DefaultRenderContext(bind.configuration()); - + if (render == null) render = new DefaultRenderContext(bind.configuration(), (ExecuteContext) ctx.data(DATA_EXECUTE_CONTEXT)); SQLDialect family = render.family(); boolean mysql = SUPPORTS_HASH_COMMENT_SYNTAX.contains(render.dialect()); char[][][] quotes = QUOTES.get(family);