From 4b9ceea9ea630577e4fd9709564b790106bdb257 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Tue, 13 Dec 2022 13:19:46 +0100 Subject: [PATCH] [jOOQ/jOOQ#14356] java.lang.StackOverflowError at org.jooq.impl.Expression.acceptAssociative --- .../main/java/org/jooq/impl/Expression.java | 67 ++++++++++--------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/jOOQ/src/main/java/org/jooq/impl/Expression.java b/jOOQ/src/main/java/org/jooq/impl/Expression.java index a09785f9c3..d121581b1b 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Expression.java +++ b/jOOQ/src/main/java/org/jooq/impl/Expression.java @@ -89,6 +89,10 @@ import static org.jooq.impl.SQLDataType.TIMESTAMP; import static org.jooq.impl.Tools.castIfNeeded; import java.sql.Timestamp; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; @@ -873,41 +877,44 @@ final class Expression extends AbstractTransformable implements UOperator2 Expr e = expProvider.apply(exp); Class expType = (Class) exp.getClass(); - // [#10665] Associativity is only given for two operands of the same data type - // [#12896] ... and if the feature is enabled - boolean associativity = ( - e.lhs instanceof Typed && e.rhs instanceof Typed - ? ((Typed) e.lhs).getDataType().equals(((Typed) e.rhs).getDataType()) - : true - ) && !ON.equals(ctx.settings().getRenderOptionalAssociativityParentheses()); + List elements = new ArrayList<>(); + Deque> queue = new ArrayDeque<>(); + queue.add(e); - acceptAssociative(ctx, associativity, e.lhs, e.op, expType, expProvider, formatSeparator); - formatSeparator.accept(ctx); - ctx.visit(e.op) - .sql(' '); - acceptAssociative(ctx, associativity, e.rhs, e.op, expType, expProvider, formatSeparator); - } + int insertAt = 0; + while (!queue.isEmpty()) { + Expr p = queue.pollFirst(); - @SuppressWarnings("unchecked") - private static final void acceptAssociative( - Context ctx, - boolean associativity, - Q1 q, - QueryPart op, - Class expType, - Function> expProvider, - Consumer> formatSeparator - ) { - if (associativity && expType.isInstance(q)) { - Expr exp = expProvider.apply((Q2) q); + // [#10665] Associativity is only given for two operands of the same data type + // [#12896] ... and if the feature is enabled + boolean associativity = ( + p.lhs instanceof Typed && p.rhs instanceof Typed + ? ((Typed) p.lhs).getDataType().equals(((Typed) p.rhs).getDataType()) + : true + ) && !ON.equals(ctx.settings().getRenderOptionalAssociativityParentheses()); - if (op.equals(exp.op)) { - acceptAssociative(ctx, (Q2) q, expProvider, formatSeparator); - return; - } + if (associativity && expType.isInstance(p.lhs)) + queue.offerFirst(expProvider.apply((Q2) p.lhs)); + else + elements.add(insertAt, p.lhs); + + if (associativity && expType.isInstance(p.rhs)) + queue.offerFirst(expProvider.apply((Q2) p.rhs)); + else + elements.add(insertAt++, p.rhs); } - ctx.visit(q); + // Reverse iteration optimises ArrayList.add(int, Object) operations in + // the normal case ((((A op B) op C) op D) op E) as opposed to the less + // frequent case (A op (B op (C op (D op E)))) + for (int i = elements.size() - 1; i >= 0; i--) { + ctx.visit(elements.get(i)); + + if (i > 0) { + formatSeparator.accept(ctx); + ctx.visit(e.op).sql(' '); + } + } } // -------------------------------------------------------------------------