From 50d509be1b3247398e7c18a665d582e1271c0da4 Mon Sep 17 00:00:00 2001 From: madschemas <155993105+MadSchemas@users.noreply.github.com> Date: Mon, 24 Aug 2026 12:42:29 +0300 Subject: [PATCH] [fea] Support nested joins logging --- .../restream/reindexer/QueryLogBuilder.java | 11 +- .../reindexer/QueryLogBuilderTest.java | 213 ++++++++++++++++++ .../reindexer/connector/NestedJoinTest.java | 42 +++- 3 files changed, 261 insertions(+), 5 deletions(-) create mode 100644 src/test/java/ru/rt/restream/reindexer/QueryLogBuilderTest.java diff --git a/src/main/java/ru/rt/restream/reindexer/QueryLogBuilder.java b/src/main/java/ru/rt/restream/reindexer/QueryLogBuilder.java index 0b48bfbd..e8c9e511 100644 --- a/src/main/java/ru/rt/restream/reindexer/QueryLogBuilder.java +++ b/src/main/java/ru/rt/restream/reindexer/QueryLogBuilder.java @@ -627,11 +627,14 @@ private String getJoinPart() { private String getSingleJoinPart(JoinEntry joinEntry) { QueryLogBuilder joinQueryLogBuilder = joinEntry.joinQueryLogBuilder; JoinType type = joinEntry.type; + String joinedSql = joinQueryLogBuilder.getSql(); + String joinedSource = isBareSelect(joinQueryLogBuilder, joinedSql) + ? joinQueryLogBuilder.namespace + : "(" + joinedSql + ")"; StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(type.name) .append(" ") - .append(joinQueryLogBuilder.whereEntries.isEmpty() ? joinQueryLogBuilder.namespace - : "(" + joinQueryLogBuilder.getSql() + ")") + .append(joinedSource) .append(" ON "); if (joinQueryLogBuilder.onEntries.size() > 1) { stringBuilder.append("("); @@ -651,6 +654,10 @@ private String getSingleJoinPart(JoinEntry joinEntry) { return stringBuilder.toString(); } + private static boolean isBareSelect(QueryLogBuilder joinQueryLogBuilder, String joinedSql) { + return joinedSql.equals(QueryType.SELECT.name() + " * FROM " + joinQueryLogBuilder.namespace); + } + private String getMergePart() { StringBuilder stringBuilder = new StringBuilder(); for (QueryLogBuilder mergeQuery : mergeQueries) { diff --git a/src/test/java/ru/rt/restream/reindexer/QueryLogBuilderTest.java b/src/test/java/ru/rt/restream/reindexer/QueryLogBuilderTest.java new file mode 100644 index 00000000..d906373a --- /dev/null +++ b/src/test/java/ru/rt/restream/reindexer/QueryLogBuilderTest.java @@ -0,0 +1,213 @@ +/* + * Copyright 2020-present Restream + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ru.rt.restream.reindexer; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static ru.rt.restream.reindexer.binding.Consts.INNER_JOIN; +import static ru.rt.restream.reindexer.binding.Consts.LEFT_JOIN; + +@Tag("builtin") +@Tag("cproto") +public class QueryLogBuilderTest { + + private static final int OP_AND = 2; + private static final int EQ = 1; + + @Test + public void testFlatLeftJoinStaysUnwrapped() { + QueryLogBuilder books = query("books"); + join(books, query("authors"), LEFT_JOIN, "authorId", "id"); + + assertThat(books.getSql(), is( + "SELECT * FROM books LEFT JOIN authors ON authors.id = books.authorId")); + } + + @Test + public void testFlatInnerJoinStaysUnwrapped() { + QueryLogBuilder books = query("books"); + join(books, query("authors"), INNER_JOIN, "authorId", "id"); + + assertThat(books.getSql(), is( + "SELECT * FROM books WHERE INNER JOIN authors ON authors.id = books.authorId")); + } + + @Test + public void testNestedLeftJoinIsDumped() { + QueryLogBuilder authors = query("authors"); + join(authors, query("locations"), LEFT_JOIN, "locationId", "id"); + + QueryLogBuilder books = query("books"); + join(books, authors, LEFT_JOIN, "authorId", "id"); + + assertThat(books.getSql(), is( + "SELECT * FROM books LEFT JOIN (SELECT * FROM authors " + + "LEFT JOIN locations ON locations.id = authors.locationId) " + + "ON authors.id = books.authorId")); + } + + @Test + public void testInnerJoinWithNestedLeftJoinIsDumped() { + QueryLogBuilder authors = query("authors"); + join(authors, query("locations"), LEFT_JOIN, "locationId", "id"); + + QueryLogBuilder books = query("books"); + join(books, authors, INNER_JOIN, "authorId", "id"); + + assertThat(books.getSql(), is( + "SELECT * FROM books WHERE INNER JOIN (SELECT * FROM authors " + + "LEFT JOIN locations ON locations.id = authors.locationId) " + + "ON authors.id = books.authorId")); + } + + @Test + public void testNestedInnerJoinIsDumpedViaWhere() { + QueryLogBuilder authors = query("authors"); + join(authors, query("locations"), INNER_JOIN, "locationId", "id"); + + QueryLogBuilder books = query("books"); + join(books, authors, INNER_JOIN, "authorId", "id"); + + assertThat(books.getSql(), is( + "SELECT * FROM books WHERE INNER JOIN (SELECT * FROM authors " + + "WHERE INNER JOIN locations ON locations.id = authors.locationId) " + + "ON authors.id = books.authorId")); + } + + @Test + public void testNestedJoinDepthTwoPlus() { + QueryLogBuilder locations = query("locations"); + join(locations, query("countries"), INNER_JOIN, "countryId", "id"); + + QueryLogBuilder authors = query("authors"); + join(authors, locations, INNER_JOIN, "locationId", "id"); + + QueryLogBuilder books = query("books"); + join(books, authors, INNER_JOIN, "authorId", "id"); + + assertThat(books.getSql(), is( + "SELECT * FROM books WHERE INNER JOIN (SELECT * FROM authors " + + "WHERE INNER JOIN (SELECT * FROM locations " + + "WHERE INNER JOIN countries ON countries.id = locations.countryId) " + + "ON locations.id = authors.locationId) " + + "ON authors.id = books.authorId")); + } + + @Test + public void testNestedLeftJoinDepthTwoPlus() { + QueryLogBuilder locations = query("locations"); + join(locations, query("countries"), LEFT_JOIN, "countryId", "id"); + + QueryLogBuilder authors = query("authors"); + join(authors, locations, LEFT_JOIN, "locationId", "id"); + + QueryLogBuilder books = query("books"); + join(books, authors, LEFT_JOIN, "authorId", "id"); + + assertThat(books.getSql(), is( + "SELECT * FROM books LEFT JOIN (SELECT * FROM authors " + + "LEFT JOIN (SELECT * FROM locations " + + "LEFT JOIN countries ON countries.id = locations.countryId) " + + "ON locations.id = authors.locationId) " + + "ON authors.id = books.authorId")); + } + + @Test + public void testJoinInsideMerge() { + QueryLogBuilder firstAuthors = query("authors"); + join(firstAuthors, query("locations"), LEFT_JOIN, "locationId", "id"); + QueryLogBuilder first = query("books"); + first.where(OP_AND, "title", EQ, "Book1"); + join(first, firstAuthors, INNER_JOIN, "authorId", "id"); + + QueryLogBuilder secondAuthors = query("authors"); + join(secondAuthors, query("locations"), LEFT_JOIN, "locationId", "id"); + QueryLogBuilder second = query("books"); + second.where(OP_AND, "title", EQ, "OtherBook"); + join(second, secondAuthors, INNER_JOIN, "authorId", "id"); + + first.merge(second); + + assertThat(first.getSql(), is( + "SELECT * FROM books WHERE title = 'Book1' AND INNER JOIN (SELECT * FROM authors " + + "LEFT JOIN locations ON locations.id = authors.locationId) " + + "ON authors.id = books.authorId MERGE(SELECT * FROM books WHERE title = 'OtherBook' " + + "AND INNER JOIN (SELECT * FROM authors " + + "LEFT JOIN locations ON locations.id = authors.locationId) " + + "ON authors.id = books.authorId)")); + } + + @Test + public void testJoinedQueryWithLimitIsDumped() { + QueryLogBuilder authors = query("authors"); + authors.limit(10); + QueryLogBuilder books = query("books"); + join(books, authors, LEFT_JOIN, "authorId", "id"); + + assertThat(books.getSql(), is( + "SELECT * FROM books LEFT JOIN (SELECT * FROM authors LIMIT 10) ON authors.id = books.authorId")); + } + + @Test + public void testJoinedQueryWithOffsetIsDumped() { + QueryLogBuilder authors = query("authors"); + authors.offset(5); + QueryLogBuilder books = query("books"); + join(books, authors, LEFT_JOIN, "authorId", "id"); + + assertThat(books.getSql(), is( + "SELECT * FROM books LEFT JOIN (SELECT * FROM authors OFFSET 5) ON authors.id = books.authorId")); + } + + @Test + public void testJoinedQueryWithSelectIsDumped() { + QueryLogBuilder authors = query("authors"); + authors.select("id"); + QueryLogBuilder books = query("books"); + join(books, authors, LEFT_JOIN, "authorId", "id"); + + assertThat(books.getSql(), is( + "SELECT * FROM books LEFT JOIN (SELECT id FROM authors) ON authors.id = books.authorId")); + } + + @Test + public void testJoinedQueryWithSortIsDumped() { + QueryLogBuilder authors = query("authors"); + authors.sort("name", false); + QueryLogBuilder books = query("books"); + join(books, authors, LEFT_JOIN, "authorId", "id"); + + assertThat(books.getSql(), is( + "SELECT * FROM books LEFT JOIN (SELECT * FROM authors ORDER BY 'name') " + + "ON authors.id = books.authorId")); + } + + private static QueryLogBuilder query(String namespace) { + QueryLogBuilder builder = new QueryLogBuilder(); + builder.namespace(namespace); + return builder; + } + + private static void join(QueryLogBuilder parent, QueryLogBuilder joined, int joinType, + String parentField, String joinIndex) { + joined.on(OP_AND, parentField, EQ, joinIndex); + parent.join(joined, joinType); + } + +} diff --git a/src/test/java/ru/rt/restream/reindexer/connector/NestedJoinTest.java b/src/test/java/ru/rt/restream/reindexer/connector/NestedJoinTest.java index 5f75298c..44170dab 100644 --- a/src/test/java/ru/rt/restream/reindexer/connector/NestedJoinTest.java +++ b/src/test/java/ru/rt/restream/reindexer/connector/NestedJoinTest.java @@ -56,9 +56,15 @@ public void testNestedInnerJoin() { .on("locationId", EQ, "id"), "locations") .on("authorId", EQ, "id"); - Map booksById = byId(db.query(BOOKS_NS, Book.class) - .innerJoin(authors, "authors") - .toList()); + Query booksQuery = db.query(BOOKS_NS, Book.class) + .innerJoin(authors, "authors"); + assertThat(booksQuery.toString(), is( + "SELECT * FROM nested_join_books WHERE INNER JOIN (SELECT * FROM nested_join_authors " + + "WHERE INNER JOIN nested_join_locations " + + "ON nested_join_locations.id = nested_join_authors.locationId) " + + "ON nested_join_authors.id = nested_join_books.authorId")); + + Map booksById = byId(booksQuery.toList()); assertThat(booksById.size(), is(3)); assertAuthorLocation(booksById.get(1000), 100, "Author1", "Moscow"); @@ -90,6 +96,36 @@ public void testLeftJoinWithNestedInnerJoin() { assertThat(booksById.get(1004).authors.size(), is(0)); } + @Test + public void testNestedLeftJoin() { + openNamespaces(); + insertFixture(); + + Query authors = db.query(AUTHORS_NS, Author.class) + .leftJoin(db.query(LOCATIONS_NS, Location.class) + .on("locationId", EQ, "id"), "locations") + .on("authorId", EQ, "id"); + + Query booksQuery = db.query(BOOKS_NS, Book.class) + .leftJoin(authors, "authors"); + assertThat(booksQuery.toString(), is( + "SELECT * FROM nested_join_books LEFT JOIN (SELECT * FROM nested_join_authors " + + "LEFT JOIN nested_join_locations " + + "ON nested_join_locations.id = nested_join_authors.locationId) " + + "ON nested_join_authors.id = nested_join_books.authorId")); + + Map booksById = byId(booksQuery.toList()); + + assertThat(booksById.size(), is(5)); + assertAuthorLocation(booksById.get(1000), 100, "Author1", "Moscow"); + assertThat(booksById.get(1001).authors.size(), is(0)); + assertAuthorLocation(booksById.get(1002), 100, "Author1", "Moscow"); + assertAuthorLocation(booksById.get(1003), 101, "Author2", "Paris"); + assertThat(booksById.get(1004).authors.size(), is(1)); + assertThat(booksById.get(1004).authors.get(0).name, is("AuthorNoLoc")); + assertThat(booksById.get(1004).authors.get(0).locations.size(), is(0)); + } + @Test public void testInnerJoinWithNestedEmptyLeftJoin() { openNamespaces();