From aae68d18e256786da6afdd12c7684dc3b97e787a Mon Sep 17 00:00:00 2001 From: stefanbaxter Date: Tue, 7 Jul 2026 20:18:13 +0000 Subject: [PATCH] fix(cubejs): strip emptyCube.emptyKey order placeholder in queryRewrite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The client query builder emits `order: {"emptyCube.emptyKey":"asc"}` as a "no order selected" sentinel. The legacy Cube planner silently ignored the unresolvable member; the Tesseract planner rejects it ("Cannot resolve: emptyCube"), 500-ing the query. Strip any `emptyCube.*` order entry in the queryRewrite hook before it reaches the planner (handles object + array order forms) — restores legacy tolerance and makes the backend robust to this placeholder regardless of client version. Verified locally on 1.6.68: the exact failing query (order with emptyCube.emptyKey) went 400 "Cannot resolve: emptyCube" → 200 OK on both legacy and Tesseract planners after the guard. Co-Authored-By: Claude Opus 4.8 --- services/cubejs/src/utils/queryRewrite.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/services/cubejs/src/utils/queryRewrite.js b/services/cubejs/src/utils/queryRewrite.js index 7c9a4ae7..0b719d96 100644 --- a/services/cubejs/src/utils/queryRewrite.js +++ b/services/cubejs/src/utils/queryRewrite.js @@ -179,6 +179,27 @@ const queryRewrite = async (query, { securityContext }) => { const { userScope } = securityContext; const { dataSourceAccessList, hasAccessList, role, teamProperties, memberProperties } = userScope; + // --- Step 0: Strip unresolvable "no order" placeholders --- + // The client query builder emits `emptyCube.emptyKey` as a sentinel for + // "no order selected". The legacy planner silently ignored the unresolvable + // member; the Tesseract planner rejects it ("Cannot resolve: emptyCube") and + // 500s the query. Removing it is safe — it just means "no explicit order". + if (query && query.order) { + const isPlaceholder = (m) => + typeof m === "string" && m.startsWith("emptyCube."); + if (Array.isArray(query.order)) { + query.order = query.order.filter((o) => + Array.isArray(o) + ? !isPlaceholder(o[0]) + : !isPlaceholder(o && (o.id || o.member)) + ); + } else if (typeof query.order === "object") { + for (const key of Object.keys(query.order)) { + if (isPlaceholder(key)) delete query.order[key]; + } + } + } + // --- Step 1: Rule-based row filtering (applies to ALL roles) --- const rules = await loadRules();