From 52912c3ccaaaa6d92351b6378a5bcc2c75870c24 Mon Sep 17 00:00:00 2001 From: Avgustin Marinov Date: Tue, 10 Sep 2024 14:48:59 +0300 Subject: [PATCH] Optimize G2 subqueries for != and out - remove unused joins in all cases (#1838) At the moment such unused are removed for "tag!=1 and tag==2" but not for "tag==2 and tag!=1" Signed-off-by: Marinov Avgustin --- .../jpa/rsql/JpaQueryRsqlVisitorG2.java | 33 +++++++------------ .../repository/jpa/rsql/RSQLToSQLTest.java | 17 ++++++++++ 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/JpaQueryRsqlVisitorG2.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/JpaQueryRsqlVisitorG2.java index 26f9528d1..52346680a 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/JpaQueryRsqlVisitorG2.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/JpaQueryRsqlVisitorG2.java @@ -63,7 +63,6 @@ public class JpaQueryRsqlVisitorG2 & RsqlQueryField, T> public static final Character LIKE_WILDCARD = '*'; private static final char ESCAPE_CHAR = '\\'; private static final String ESCAPE_CHAR_WITH_ASTERISK = ESCAPE_CHAR +"*"; - private static final List NO_JOINS_OPERATOR = List.of("!=", "=out="); private final Root root; private final CriteriaQuery query; @@ -76,7 +75,6 @@ public class JpaQueryRsqlVisitorG2 & RsqlQueryField, T> private boolean inOr; private final Map, Path> javaTypeToPath = new HashMap<>(); - private boolean joinsNeeded; public JpaQueryRsqlVisitorG2(final Class enumType, final Root root, final CriteriaQuery query, final CriteriaBuilder cb, @@ -120,8 +118,6 @@ public class JpaQueryRsqlVisitorG2 & RsqlQueryField, T> transformedValues.add(convertValueIfNecessary(node, queryField.getEnumValue(), fieldPath, value)); } - this.joinsNeeded = this.joinsNeeded || areJoinsNeeded(node); - return mapToPredicate(node, queryField, fieldPath, node.getArguments(), transformedValues); } @@ -219,8 +215,7 @@ public class JpaQueryRsqlVisitorG2 & RsqlQueryField, T> } } - clearJoinsIfNotNeeded(); - return toNotExistsSubQueryPredicate(queryField, expressionToCompare -> + return toNotExistsSubQueryPredicate(queryField, fieldPath, expressionToCompare -> isPattern(transformedValueStr) ? // a pattern, use like like(expressionToCompare, toSQL(transformedValueStr)) : equal(expressionToCompare, transformedValueStr)); @@ -237,8 +232,7 @@ public class JpaQueryRsqlVisitorG2 & RsqlQueryField, T> return cb.or(cb.isNull(fieldPath), cb.not(in(pathOfString(fieldPath), transformedValues))); } - clearJoinsIfNotNeeded(); - return toNotExistsSubQueryPredicate(queryField, expressionToCompare -> in(expressionToCompare, transformedValues)); + return toNotExistsSubQueryPredicate(queryField, fieldPath, expressionToCompare -> in(expressionToCompare, transformedValues)); } private Path getFieldPath(final Root root, final QuertPath queryField) { @@ -267,9 +261,8 @@ public class JpaQueryRsqlVisitorG2 & RsqlQueryField, T> return root.get(fieldNameSplit); } } // if a collection - it is a join - if (inOr && root == this.root) { // try to reuse join of the same or level and no subquery - final Class objectClass = attribute.getJavaType(); - return javaTypeToPath.computeIfAbsent(objectClass, k -> root.join(fieldNameSplit, JoinType.LEFT)); + if (inOr && root == this.root) { // try to reuse join of the same "or" level and no subquery + return javaTypeToPath.computeIfAbsent(attribute.getJavaType(), k -> root.join(fieldNameSplit, JoinType.LEFT)); } else { return root.join(fieldNameSplit, JoinType.LEFT); } @@ -338,12 +331,6 @@ public class JpaQueryRsqlVisitorG2 & RsqlQueryField, T> } } - private void clearJoinsIfNotNeeded() { - if (!joinsNeeded) { - root.getJoins().clear(); - } - } - private Predicate toNullOrNotEqualPredicate(final Path fieldPath, final Object transformedValue) { return cb.or( cb.isNull(fieldPath), @@ -353,7 +340,13 @@ public class JpaQueryRsqlVisitorG2 & RsqlQueryField, T> } @SuppressWarnings({ "unchecked", "rawtypes" }) - private Predicate toNotExistsSubQueryPredicate(final QuertPath queryField, final Function, Predicate> subQueryPredicateProvider) { + private Predicate toNotExistsSubQueryPredicate(final QuertPath queryField, final Path fieldPath, final Function, Predicate> subQueryPredicateProvider) { + // if a subquery the field's parent joins are not actually used + if (!inOr) { + // so, if not in or (hence not reused) we remove them + root.getJoins().remove(fieldPath.getParentPath()); + } + final Class javaType = root.getJavaType(); final Subquery subquery = query.subquery(javaType); final Root subqueryRoot = subquery.from(javaType); @@ -454,8 +447,4 @@ public class JpaQueryRsqlVisitorG2 & RsqlQueryField, T> return transformedValue.indexOf(LIKE_WILDCARD) != -1; } } - - private static boolean areJoinsNeeded(final ComparisonNode node) { - return !NO_JOINS_OPERATOR.contains(node.getOperator().getSymbol()); - } } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLToSQLTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLToSQLTest.java index b07f76c72..9764cf6a3 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLToSQLTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLToSQLTest.java @@ -52,6 +52,12 @@ public class RSQLToSQLTest { print(JpaTarget.class, TargetFields.class, "(tag!=TAG1 or tag !=TAG2)"); } + @Test + public void printPG() { + printFrom(JpaTarget.class, TargetFields.class, "tag!=TAG1 and tag==TAG2"); + printFrom(JpaTarget.class, TargetFields.class, "tag==TAG1 and tag!=TAG2"); + } + private & RsqlQueryField> void print(final Class domainClass, final Class fieldsClass, final String rsql) { System.out.println(rsql); System.out.println("\tlegacy:\n" + @@ -59,4 +65,15 @@ public class RSQLToSQLTest { System.out.println("\tG2:\n" + "\t\t" + rsqlToSQL.toSQL(domainClass, fieldsClass, rsql, false)); } + + private & RsqlQueryField> void printFrom(final Class domainClass, final Class fieldsClass, final String rsql) { + System.out.println(rsql); + System.out.println("\tlegacy:\n" + + "\t\t" + from(rsqlToSQL.toSQL(domainClass, fieldsClass, rsql, true))); + System.out.println("\tG2:\n" + + "\t\t" + from(rsqlToSQL.toSQL(domainClass, fieldsClass, rsql, false))); + } + private static String from(final String sql) { + return sql.substring(sql.indexOf("FROM")); + } } \ No newline at end of file