From 00bba76505e608791998ed8de7ca5f5c7f14f3fa Mon Sep 17 00:00:00 2001 From: CheekyNox <299816805+CheekyNox@users.noreply.github.com> Date: Thu, 20 Aug 2026 21:45:10 +0300 Subject: [PATCH] Fix ClickHouse lookup pagination --- .../command/lookup/StandardLookupThread.java | 3 ++ .../net/coreprotect/database/LookupRaw.java | 45 ++++++++++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/coreprotect/command/lookup/StandardLookupThread.java b/src/main/java/net/coreprotect/command/lookup/StandardLookupThread.java index 9d3a9f6e7..a6c9526b8 100644 --- a/src/main/java/net/coreprotect/command/lookup/StandardLookupThread.java +++ b/src/main/java/net/coreprotect/command/lookup/StandardLookupThread.java @@ -319,6 +319,9 @@ else if (pageStart < rows) { List lookupList = lookupPage == null ? Lookup.performPartialLookup(statement, player, uuidList, userList, blockList, excludedBlocks, excludedUsers, actions, entityActionFilter, messageFilters, entityContext, finalLocation, radius, rowData, timeStart, timeEnd, (int) pageStart, displayResults, restrict_world, true, entityContainerId, rollbackState) : lookupPage.getRows(); + if (lookupList == null) { + return; + } Map entitySpawnRecords = Collections.emptyMap(); Map loadedEntityLocations = Collections.emptyMap(); diff --git a/src/main/java/net/coreprotect/database/LookupRaw.java b/src/main/java/net/coreprotect/database/LookupRaw.java index 80640bc16..91e4b0ed5 100644 --- a/src/main/java/net/coreprotect/database/LookupRaw.java +++ b/src/main/java/net/coreprotect/database/LookupRaw.java @@ -174,6 +174,25 @@ private static List performLookupRaw(Statement statement, CommandSende paused = true; } + if (ConfigHandler.databaseType.isClickHouse() && pageRows == null && limitOffset >= 0 && limitCount > 0) { + pageRows = new HashMap<>(); + try (ResultSet pageResults = rawLookupResultSet(statement, user, checkUuids, checkUsers, restrictList, excludeList, excludeUserList, actionList, entityActionFilter, messageFilters, entityContext, location, radius, rowData, startTime, endTime, limitOffset, limitCount, restrictWorld, lookup, false, entityContainerId, false, false, false, rollbackState, null, true)) { + if (pageResults == null) { + return null; + } + while (pageResults.next()) { + int source = pageResults.getInt("tbl"); + long rowId = pageResults.getLong("id"); + pageRows.computeIfAbsent(source, ignored -> new ArrayList<>()).add(rowId); + } + } + if (pageRows.isEmpty()) { + return list; + } + limitOffset = -1; + limitCount = -1; + } + ResultSet results = rawLookupResultSet(statement, user, checkUuids, checkUsers, restrictList, excludeList, excludeUserList, actionList, entityActionFilter, messageFilters, entityContext, location, radius, rowData, startTime, endTime, limitOffset, limitCount, restrictWorld, lookup, false, entityContainerId, false, false, false, rollbackState, pageRows, false); if (results == null) { return null; @@ -1148,13 +1167,23 @@ else if (actionList.contains(LookupActions.SIGN)) { } if (selectPageRows) { - query = buildDuckDBPageQuery(query, entityLocationCte, pageOffset, limitCount, knownTotalRows, cursor, queryOrder.contains("time DESC")); + if (ConfigHandler.databaseType.isClickHouse()) { + query = buildClickHousePageQuery(query, queryOrder, limitOffset, limitCount); + } + else { + query = buildDuckDBPageQuery(query, entityLocationCte, pageOffset, limitCount, knownTotalRows, cursor, queryOrder.contains("time DESC")); + } } else if (summary) { query = buildSummaryQuery(query, inventoryQuery, countGroups, includeGroupCount, limitOffset, limitCount); } else { - query = query + queryOrder + queryLimit + ""; + if (ConfigHandler.databaseType.isClickHouse() && query.contains(" UNION ALL ")) { + query = "SELECT * FROM (" + query + ") AS coreprotectLookupUnion" + queryOrder + queryLimit; + } + else { + query = query + queryOrder + queryLimit; + } } if (!selectPageRows && !entityLocationCte.isEmpty()) { query = "WITH " + entityLocationCte + " " + query; @@ -1339,6 +1368,18 @@ private static String buildDuckDBPageQuery(String sourceQuery, String entityLoca return query.toString(); } + private static String buildClickHousePageQuery(String sourceQuery, String queryOrder, int offset, int limit) { + if (limit <= 0) { + throw new IllegalArgumentException("ClickHouse lookup page size must be positive"); + } + if (offset < 0) { + throw new IllegalArgumentException("ClickHouse lookup page offset must not be negative"); + } + String candidateOrder = queryOrder.replace("rowid", "id"); + return "SELECT tbl,id FROM (" + sourceQuery + ") AS coreprotectLookupCandidates" + + candidateOrder + " LIMIT " + limit + " OFFSET " + offset; + } + private static String buildRollbackPredicate(LookupRollbackState rollbackState, boolean inventoryRollback) { if (rollbackState == null || rollbackState == LookupRollbackState.ANY) { return "";