[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]
This commit is contained in:
Lukas Eder 2022-05-24 12:29:41 +02:00
parent a3a3b94592
commit 6b03c3f0de
13 changed files with 51 additions and 28 deletions

View File

@ -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<BindContext> implements BindContext {
AbstractBindContext(Configuration configuration, PreparedStatement stmt) {
super(configuration, stmt);
AbstractBindContext(Configuration configuration, ExecuteContext ctx, PreparedStatement stmt) {
super(configuration, ctx, stmt);
}
// ------------------------------------------------------------------------

View File

@ -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<C extends Context<C>> 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();

View File

@ -465,23 +465,23 @@ abstract class AbstractQuery<R extends Record> 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());
}

View File

@ -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));

View File

@ -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<R extends Record> extends AbstractCursor<R> {
// 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<? extends AbstractRecord>) ((AbstractRowAsField<?>) f).getRecordType();
}

View File

@ -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

View File

@ -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);
}
// -------------------------------------------------------------------------

View File

@ -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<RenderContext> 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<RenderContext> 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());

View File

@ -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;
}

View File

@ -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<Arraylist<Param<?>>> = batching

View File

@ -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());
}

View File

@ -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<R extends Record> extends AbstractResultQuery<R> imp

View File

@ -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);