[jOOQ/jOOQ#3175] Apply SQL transformation to move nested CTE to top level where not supported

This commit is contained in:
Lukas Eder 2021-03-08 12:02:50 +01:00
parent fbc41df271
commit 8733df26f4
5 changed files with 59 additions and 32 deletions

View File

@ -998,6 +998,7 @@ abstract class AbstractContext<C extends Context<C>> extends AbstractScope imple
static class ScopeStackElement {
final int scopeLevel;
int[] positions;
int bindIndex;
int indent;
JoinNode joinNode;

View File

@ -39,9 +39,7 @@ package org.jooq.impl;
import static java.lang.Boolean.TRUE;
import static org.jooq.SQLDialect.SQLITE;
import static org.jooq.conf.ParamType.INDEXED;
import static org.jooq.conf.ParamType.INLINED;
import static org.jooq.conf.ParamType.NAMED;
import static org.jooq.conf.SettingsTools.renderLocale;
import static org.jooq.impl.Identifiers.QUOTES;
import static org.jooq.impl.Identifiers.QUOTE_END_DELIMITER;
@ -56,7 +54,6 @@ import java.util.Arrays;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
@ -96,7 +93,6 @@ class DefaultRenderContext extends AbstractContext<RenderContext> implements Ren
final StringBuilder sql;
private final QueryPartList<Param<?>> bindValues;
private int params;
private int alias;
private int indent;
private Deque<Integer> indentLock;
@ -177,6 +173,7 @@ class DefaultRenderContext extends AbstractContext<RenderContext> implements Ren
applyNewLine();
ScopeStackElement e = scopeStack.getOrCreate(part);
e.positions = new int[] { sql.length(), -1 };
e.bindIndex = peekIndex();
e.indent = indent;
resetSeparatorFlags();
}
@ -253,7 +250,8 @@ class DefaultRenderContext extends AbstractContext<RenderContext> implements Ren
outer:
for (ScopeStackElement e1 : scopeStack.iterable(e -> e.scopeLevel == scopeStack.scopeLevel())) {
String replaced = null;
String replacedSQL = null;
QueryPartList<Param<?>> insertedBindValues = null;
if (e1.positions == null) {
continue outer;
@ -262,7 +260,7 @@ class DefaultRenderContext extends AbstractContext<RenderContext> implements Ren
// [#11367] TODO: Move this logic into a ScopeMarker as well
// TODO: subqueryLevel() is lower than scopeLevel if we use implicit join in procedural logic
else if (e1.joinNode != null && !e1.joinNode.children.isEmpty()) {
replaced = configuration
replacedSQL = configuration
.dsl()
.renderContext()
.declareTables(true)
@ -283,21 +281,24 @@ class DefaultRenderContext extends AbstractContext<RenderContext> implements Ren
ScopeContent c = content[i];
if (e1 == e && c != null) {
replaced = markers[i].renderer.render(
configuration.dsl().renderContext().formatIndentStart(e.indent),
DefaultRenderContext ctx = (DefaultRenderContext) configuration.dsl().renderContext();
markers[i].renderer.render(
(DefaultRenderContext) ctx.formatIndentStart(e.indent),
e,
afterLast[i],
c
);
replacedSQL = ctx.render();
insertedBindValues = ctx.bindValues();
break elementLoop;
}
}
}
if (replaced != null) {
sql.replace(e1.positions[0], e1.positions[1], replaced);
int shift = replaced.length() - (e1.positions[1] - e1.positions[0]);
if (replacedSQL != null) {
sql.replace(e1.positions[0], e1.positions[1], replacedSQL);
int shift = replacedSQL.length() - (e1.positions[1] - e1.positions[0]);
inner:
for (ScopeStackElement e2 : scopeStack) {
@ -309,6 +310,19 @@ class DefaultRenderContext extends AbstractContext<RenderContext> implements Ren
e2.positions[1] = e2.positions[1] + shift;
}
}
if (insertedBindValues != null) {
bindValues.addAll(e1.bindIndex - 1, insertedBindValues);
inner:
for (ScopeStackElement e2 : scopeStack) {
if (e2.positions == null)
continue inner;
if (e2.bindIndex > e1.bindIndex)
e2.bindIndex = e2.bindIndex + insertedBindValues.size();
}
}
}
}
}
@ -938,7 +952,7 @@ class DefaultRenderContext extends AbstractContext<RenderContext> implements Ren
public ForceInlineSignal() {
if (log.isDebugEnabled())
log.debug("Re-render query", "Forcing bind variable inlining as " + configuration().dialect() + " does not support " + params + " bind variables (or more) in a single query");
log.debug("Re-render query", "Forcing bind variable inlining as " + configuration().dialect() + " does not support " + peekIndex() + " bind variables (or more) in a single query");
}
}
}

View File

@ -101,13 +101,12 @@ final class GenerateSeries extends AbstractTable<Record1<Integer>> implements Au
@Override
public final void accept(Context<?> ctx) {
if (EMULATE_WITH_RECURSIVE.contains(ctx.dialect())) {
Name v = unquotedName("v");
Field<Integer> f = DSL.field(v, INTEGER);
Field<Integer> f = DSL.field(N_GENERATE_SERIES, INTEGER);
visitSubquery(
ctx,
withRecursive(N_GENERATE_SERIES, v)
withRecursive(N_GENERATE_SERIES, N_GENERATE_SERIES)
.as(select(from).unionAll(select(iadd(f, step == null ? inline(1) : step)).from(N_GENERATE_SERIES).where(f.lt(to))))
.select(f.as(N_GENERATE_SERIES)).from(N_GENERATE_SERIES),
.select(f).from(N_GENERATE_SERIES),
true
);
}

View File

@ -107,10 +107,6 @@ enum ScopeMarker {
@ -122,23 +118,30 @@ enum ScopeMarker {
(ctx, beforeFirst, afterLast, object) -> {
TopLevelCte cte = (TopLevelCte) object;
boolean single = cte.size() == 1;
boolean noWith = afterLast != null && beforeFirst.positions[0] == afterLast.positions[0];
// There is no WITH clause
if (afterLast != null && beforeFirst.positions[0] == afterLast.positions[0])
if (noWith) {
ctx.visit(K_WITH);
if (single)
ctx.formatIndentStart()
.formatSeparator();
else
ctx.sql(' ');
if (single)
ctx.formatIndentStart()
.formatSeparator();
else
ctx.sql(' ');
}
ctx.declareCTE(true).visit(cte).declareCTE(false);
if (single)
ctx.formatIndentEnd();
if (noWith) {
if (single)
ctx.formatIndentEnd();
}
return ctx.render();
// Top level CTE are inserted before all other CTEs
else
ctx.sql(',');
ctx.formatSeparator().sql("");
}
);
@ -158,8 +161,8 @@ enum ScopeMarker {
@FunctionalInterface
interface ReplacementRenderer {
String render(
Context<?> ctx,
void render(
DefaultRenderContext ctx,
ScopeStackElement beforeFirst,
ScopeStackElement afterLast,
ScopeContent content

View File

@ -51,6 +51,7 @@ import static org.jooq.impl.Keywords.K_RECURSIVE;
import static org.jooq.impl.Keywords.K_WITH;
import static org.jooq.impl.Tools.EMPTY_NAME;
import static org.jooq.impl.Tools.BooleanDataKey.DATA_LIST_ALREADY_INDENTED;
import static org.jooq.impl.Tools.DataKey.DATA_TOP_LEVEL_CTE;
import java.util.Arrays;
import java.util.Collection;
@ -176,6 +177,8 @@ implements
private final CommonTableExpressionList ctes;
private final boolean recursive;
private Configuration configuration;
@ -214,6 +217,13 @@ implements
list = ctes;
if (!list.isEmpty()) {
ctx.visit(K_WITH);
if (recursive