diff --git a/docs-mintlify/docs.json b/docs-mintlify/docs.json
index 89bed40b90722..7975aa69f7285 100644
--- a/docs-mintlify/docs.json
+++ b/docs-mintlify/docs.json
@@ -52,6 +52,7 @@
"root": "docs/explore-analyze/workbooks/index",
"pages": [
"docs/explore-analyze/workbooks/querying-data",
+ "docs/explore-analyze/workbooks/calculated-fields",
"docs/explore-analyze/workbooks/source-sql-tabs",
"docs/explore-analyze/workbooks/charts"
]
@@ -68,6 +69,13 @@
"docs/explore-analyze/notifications"
]
},
+ {
+ "group": "Organize content",
+ "pages": [
+ "docs/organize-content/folders",
+ "docs/organize-content/sharing"
+ ]
+ },
{
"group": "Data Modeling",
"pages": [
diff --git a/docs-mintlify/docs/data-modeling/ai-context.mdx b/docs-mintlify/docs/data-modeling/ai-context.mdx
index 4f2e557de7bee..aed182a8a95c0 100644
--- a/docs-mintlify/docs/data-modeling/ai-context.mdx
+++ b/docs-mintlify/docs/data-modeling/ai-context.mdx
@@ -1,6 +1,6 @@
---
title: AI context
-description: Optimize your data model for AI by using descriptions and the meta ai_context property to provide additional context.
+description: Improve AI accuracy and trust by enriching your semantic layer with descriptions and AI-specific context that helps agents generate better insights.
---
When using [Analytics Chat][ref-analytics-chat] or other AI-powered features,
diff --git a/docs-mintlify/docs/data-modeling/overview.mdx b/docs-mintlify/docs/data-modeling/overview.mdx
index 71c11245e1b5c..a0ab514241663 100644
--- a/docs-mintlify/docs/data-modeling/overview.mdx
+++ b/docs-mintlify/docs/data-modeling/overview.mdx
@@ -1,6 +1,6 @@
---
title: Getting started
-description: Introduces how Cube turns warehouse tables into a reusable semantic layer that powers metrics, dimensions, and API-driven analytics without ad hoc SQL per question.
+description: Build a reusable semantic layer that provides the shared context for AI agents, BI dashboards, and embedded analytics — turning warehouse tables into governed metrics and dimensions.
---
The data model is used to transform raw data into meaningful business
diff --git a/docs-mintlify/docs/explore-analyze/analytics-chat.mdx b/docs-mintlify/docs/explore-analyze/analytics-chat.mdx
index b08c289819079..b96d4b436b567 100644
--- a/docs-mintlify/docs/explore-analyze/analytics-chat.mdx
+++ b/docs-mintlify/docs/explore-analyze/analytics-chat.mdx
@@ -1,9 +1,9 @@
---
title: Analytics Chat
-description: Overview of the AI chat experience for asking natural-language questions against your semantic layer with optional embedding and Workbook handoff.
+description: Conversational analytics interface for asking plain-language questions and getting trusted, AI-powered insights from your semantic layer.
---
-Analytics Chat is a conversational interface that allows you to explore your data using natural language. Ask questions and get AI-powered insights without writing queries or building visualizations manually.
+Analytics Chat is Cube's conversational analytics experience — ask questions in plain language and get trusted, AI-powered insights without writing queries or building visualizations.
## How it works
diff --git a/docs-mintlify/docs/explore-analyze/explore.mdx b/docs-mintlify/docs/explore-analyze/explore.mdx
index 86a419ef647dc..8e637ed36469f 100644
--- a/docs-mintlify/docs/explore-analyze/explore.mdx
+++ b/docs-mintlify/docs/explore-analyze/explore.mdx
@@ -1,6 +1,6 @@
---
title: Explore
-description: Start governed, shareable explorations from Analytics Chat or dashboards without creating a workbook, then graduate analysis into Workbooks when needed.
+description: Self-serve data exploration from Analytics Chat, dashboards, or any semantic view — governed by your data model, shareable via URL.
---
Explore is a quick way to explore data in your semantic layer either by point and click or with an AI agent. Unlike workbooks, Explore doesn't require you to create a workbook—you can start exploring immediately from dashboard, analytics chat or any semantic view.
diff --git a/docs-mintlify/docs/explore-analyze/workbooks/calculated-fields.mdx b/docs-mintlify/docs/explore-analyze/workbooks/calculated-fields.mdx
new file mode 100644
index 0000000000000..14d3115f05620
--- /dev/null
+++ b/docs-mintlify/docs/explore-analyze/workbooks/calculated-fields.mdx
@@ -0,0 +1,89 @@
+---
+title: Calculated fields
+description: Create ad-hoc custom dimensions and measures with Semantic SQL in workbooks, with help from AI or the field picker.
+---
+
+Calculated fields are ad-hoc dimensions and measures you add only to the current
+workbook report. They do not change the shared data model.
+
+As described in [Semantic SQL](/docs/introduction#semantic-sql), Cube routes
+analysis through the semantic layer instead of sending arbitrary SQL straight to
+the warehouse. The runtime validates every request and applies your security
+policies. Semantic SQL builds on Postgres-compatible SQL—including the
+`MEASURE()` function—so you can express derived logic on top of existing
+semantic definitions with both flexibility and governance.
+
+Calculated fields are expressed as Semantic SQL and pushed down to the Cube
+backend for evaluation. The semantic layer compiles them with the rest of the
+query—rather than applying them only in the browser—so the same validation,
+governance, and warehouse execution path apply as for any other Semantic SQL
+analysis.
+
+## Using AI to create calculated fields
+
+You can ask the Cube AI agent to create custom calculations in natural language.
+The agent can add or refine calculated fields from different parts of the
+product—for example while exploring in **Analytics chat** or working in
+**Workbooks**—so you are not limited to a single entry point when you want a new
+metric or dimension for the analysis in front of you.
+
+## Creating calculated fields in UI
+
+You can also build and edit calculated fields directly in the workbook. New
+fields appear in the **Calculated fields** section of the field picker sidebar.
+
+### Aggregations from existing dimensions
+
+Right-click a dimension column header and choose an aggregation to create a
+calculated field automatically. Available aggregations depend on the column type:
+
+| Column type | Available aggregations |
+| --- | --- |
+| Number | Count Distinct, Sum, Average, Min, Max |
+| Time | Count Distinct, Min, Max |
+| String, Boolean | Count Distinct |
+
+### Calculations from existing measures
+
+Open the menu on a measure column header and use the **Calculations** submenu
+for derived calculations:
+
+| Calculation | Description |
+| --- | --- |
+| % of total | Ratio of the measure value to the total across all rows |
+| % of previous | Ratio of the measure value to the previous row's value |
+| % change from previous | Percentage change compared to the previous row |
+| Running total | Cumulative sum of the measure across rows |
+
+
+
+**% of previous**, **% change from previous**, and **Running total** require at
+least one dimension in the query.
+
+
+
+Which calculations are offered depends on the measure’s aggregation type:
+
+| Aggregation type | Available calculations |
+| --- | --- |
+| Count, Sum | All calculations |
+| Min, Max | Running total |
+| Average, Count Distinct | None |
+
+### Filtered measures
+
+When working with query **Results**, pivot so at least one dimension is on
+columns, then open the header menu on a **pivoted measure column** and choose
+**Create filtered measure**. Cube adds a calculated measure that applies the
+column’s slice—for example, from **Count** broken down by **Status**, you get a
+measure that only aggregates rows matching that status (such as completed
+orders only).
+
+The option appears only for **native** measures on pivoted columns, not for
+calculated fields. The same flow works in **Explore** when results are pivoted
+the same way.
+
+### Editing a calculated field
+
+Select a calculated field in the sidebar to open the editor. You can change its
+**name** and **SQL expression**, then choose **Update** to apply.
diff --git a/docs-mintlify/docs/explore-analyze/workbooks/index.mdx b/docs-mintlify/docs/explore-analyze/workbooks/index.mdx
index 68d878193ab91..9810fb8f9dc8e 100644
--- a/docs-mintlify/docs/explore-analyze/workbooks/index.mdx
+++ b/docs-mintlify/docs/explore-analyze/workbooks/index.mdx
@@ -1,10 +1,10 @@
---
-description: "Build reports with an AI agent, organize analyses across multiple tabs, and share insights with your team."
+description: "Build reports and explore data with AI agents, organize analyses across multiple tabs, and share trusted insights with your team."
title: Workbooks
---
-Workbooks allow you to build reports with an AI agent, organize the results of your
-analysis, and share insights with your team.
+Workbooks allow you to build reports and explore data with AI agents, organize the results of your
+analysis, and share trusted insights with your team.
## Tabs
diff --git a/docs-mintlify/docs/getting-started/embed-analytics.mdx b/docs-mintlify/docs/getting-started/embed-analytics.mdx
index 3a58973b8d00c..fa321ecbfe601 100644
--- a/docs-mintlify/docs/getting-started/embed-analytics.mdx
+++ b/docs-mintlify/docs/getting-started/embed-analytics.mdx
@@ -1,9 +1,9 @@
---
title: Embed analytics
-description: Choose among iframe embeds, the Analytics Chat API, and headless core APIs depending on how much UI and authentication control you need.
+description: Ship agentic embedded analytics in your product — choose among iframe embeds, the conversational Chat API, or headless core APIs for full control.
---
-Cube offers rich options for embedded analytics. You can embed [dashboards][ref-dashboards] and [analytics chat][ref-analytics-chat] as iframes, use the [Analytics Chat API][ref-chat-api] directly to create your own conversational analytics experience, or use [Core Data APIs][ref-core-apis] directly to build custom visualizations, reporting, and dashboarding experiences.
+Cube offers rich options for embedded analytics. You can embed [dashboards][ref-dashboards] and [analytics chat][ref-analytics-chat] as iframes, use the [Chat API][ref-chat-api] directly to create your own conversational analytics experience, or use [Core Data APIs][ref-core-apis] directly to build custom visualization, reporting, and dashboarding experiences.
## Embed with iframes
diff --git a/docs-mintlify/docs/introduction.mdx b/docs-mintlify/docs/introduction.mdx
index b3c564772a639..72278d408466b 100644
--- a/docs-mintlify/docs/introduction.mdx
+++ b/docs-mintlify/docs/introduction.mdx
@@ -1,10 +1,10 @@
---
title: Introduction
-description: Cube is the business intelligence platform powered by the open-source semantic layer.
+description: Cube is the agentic analytics platform for business intelligence and embedded analytics, powered by the open-source semantic layer.
hideTableOfContents: true
---
-Cube uses AI agents to build data models and enable data consumers to perform analysis. Use AI to quickly build semantic layer and fully control the analytics context.
+The AI analytics platform that combines self-serve conversational analytics, governed data modeling, and embedded analytics — all powered by an open-source semantic layer.
-Cube is a new generation of a business intelligence and embedded analytics platform built to be used by both humans and AI agents. It empowers different personas across your organization:
+Cube is an AI analytics platform for the whole organization — built to be used by both humans and AI agents. It combines self-serve conversational analytics, governed data modeling, and embedded analytics experiences, all on top of an open-source semantic layer. It empowers different personas across your organization:
-- **Data Engineers** can quickly curate data models with AI assistance, accelerating the development and maintenance of semantic layers
-- **Data Analysts** can perform deep analysis with AI assistance, diving into complex data relationships and patterns
-- **Business Users** benefit from workbooks and dashboards that Cube can automatically build and maintain
+- **Data Engineers** can quickly curate data models with AI assistance, accelerating development and reducing time-to-insight for the whole organization
+- **Data Analysts** can perform deep analysis with AI assistance, getting trusted answers without writing ad-hoc SQL
+- **Business Users** can self-serve with natural language questions, workbooks, and dashboards — no tickets to the data team required
## How is Cube different?
-At the foundation of Cube's agentic analytics platform is an [open-source semantic layer](https://github.com/cube-js/cube)—the critical infrastructure that enables both AI agents and humans to work with trusted, consistent data.
+At the foundation of Cube's agentic analytics platform is an [open-source semantic layer](https://github.com/cube-js/cube) — the shared context that enables both AI agents and humans to work with trusted, consistent data.
The semantic layer provides the governed data foundation that makes agentic analytics possible. It organizes data from your cloud data warehouses into centralized, consistent definitions that AI agents can reliably query, explore, and reason about. Without a semantic layer, AI agents would struggle with inconsistent metrics, scattered business logic, and ungoverned data access—making their outputs unreliable and potentially dangerous.
diff --git a/docs-mintlify/docs/organize-content/folders.mdx b/docs-mintlify/docs/organize-content/folders.mdx
new file mode 100644
index 0000000000000..0f244064286b6
--- /dev/null
+++ b/docs-mintlify/docs/organize-content/folders.mdx
@@ -0,0 +1,144 @@
+---
+title: Folders
+description: Organize workbooks, dashboards, and explorations into a hierarchical folder structure for easy navigation and access control.
+---
+
+As your team creates workbooks, dashboards, and explorations, folders help you
+keep content organized and easy to find. Folders provide a hierarchical
+structure that mirrors how your team thinks about its data — by team,
+project, domain, or any other grouping that makes sense.
+
+## Creating folders
+
+To create a folder:
+
+1. Navigate to the **Workspace** page.
+2. Click **New folder**.
+3. Enter a name for the folder (up to 255 characters).
+4. Click **Create**.
+
+You can also create folders inside existing folders to build a nested
+hierarchy. Open the parent folder first, then follow the same steps.
+
+{/* Screenshot: Workspace page with the "New folder" button visible in the
+toolbar and the folder name dialog open, showing the name input field and
+Create button. */}
+
+
+
+Folder names must be unique within the same parent folder.
+
+
+
+## Nesting folders
+
+Folders support up to **10 levels** of nesting, allowing you to build
+detailed hierarchies. For example:
+
+```text
+Workspace (root)
+└── Marketing
+ └── Campaigns
+ └── Q1 2025
+ └── Email Performance
+```
+
+Each level provides further categorization while keeping content accessible
+through the folder tree.
+
+## What can go in a folder
+
+Folders can contain the following content types:
+
+- **Workbooks** — multi-tab analyses built with the semantic layer or
+ source SQL
+- **Dashboards** — published views of workbook reports
+- **Explorations** — saved explorations from Analytics Chat or the Explore
+ page
+
+Content that is not placed in a folder appears at the root level of your
+workspace.
+
+{/* Screenshot: Workspace page showing a folder containing a mix of content
+types — at least one workbook, one dashboard, and one exploration visible
+in the list. */}
+
+## Moving content into folders
+
+To move a workbook, dashboard, or exploration into a folder, open the
+item's action menu and select **Move**. Choose the target folder from the
+folder picker and confirm. You can also move items to the root level by
+selecting the workspace root as the destination.
+
+{/* Screenshot: The folder picker dialog that appears after clicking "Move" on
+a workbook, showing the folder tree with nested folders to choose from and
+a confirm button. */}
+
+## Renaming folders
+
+To rename a folder:
+
+1. Open the folder's action menu.
+2. Select **Rename**.
+3. Enter the new name.
+4. Click **Save**.
+
+{/* Screenshot: A folder's action menu (three-dot / context menu) open, showing
+options including Rename, Move, Share, and Delete. */}
+
+
+
+The new name must be unique within the same parent folder.
+
+
+
+## Moving folders
+
+You can rearrange your folder hierarchy by moving folders to a different
+parent:
+
+1. Open the folder's action menu.
+2. Select **Move**.
+3. Choose the new parent folder (or the workspace root).
+4. Confirm the move.
+
+
+
+A folder cannot be moved into one of its own subfolders.
+
+
+
+## Deleting folders
+
+To delete a folder:
+
+1. Open the folder's action menu.
+2. Select **Delete**.
+
+
+
+A folder can only be deleted if it contains no subfolders. Move or delete
+any subfolders first.
+
+
+
+## Folder permissions
+
+Folder access is controlled through three permission levels:
+
+| Level | Allows |
+| --- | --- |
+| **Can view** | View the folder and its contents |
+| **Can edit** | Rename the folder, move content into it, and create subfolders |
+| **Full access** | Full control including moving and deleting the folder, and managing folder permissions |
+
+The creator of a folder automatically receives **Full access**.
+
+To learn how to share folders with users, groups, or your entire
+organization — and how permissions are inherited by content inside
+folders — see [Share content][ref-sharing].
+
+[ref-workbooks]: /docs/explore-analyze/workbooks
+[ref-dashboards]: /docs/explore-analyze/dashboards
+[ref-sharing]: /docs/organize-content/sharing
+[ref-roles]: /admin/users-and-permissions/roles-and-permissions
diff --git a/docs-mintlify/docs/organize-content/sharing.mdx b/docs-mintlify/docs/organize-content/sharing.mdx
new file mode 100644
index 0000000000000..192ba413f8c95
--- /dev/null
+++ b/docs-mintlify/docs/organize-content/sharing.mdx
@@ -0,0 +1,177 @@
+---
+title: Share content
+description: Share workbooks, dashboards, and explorations with specific users, groups, or your entire organization.
+---
+
+Sharing content with your team keeps everyone aligned on key metrics,
+reduces duplicated work, and ensures stakeholders have access to the
+insights they need. Cube lets you control exactly who can see and edit
+each piece of content — from individual users to your entire organization.
+
+## What can be shared
+
+You can share the following content types:
+
+- **Workbooks** — share the full workbook including all tabs and reports
+- **Dashboards** — share published dashboard views with stakeholders
+- **Explorations** — share saved explorations from Analytics Chat, the
+ Explore page, or dashboards
+
+
+
+Shared explorations are also available to users through the
+[Google Sheets][ref-google-sheets] and [Microsoft Excel][ref-excel]
+integrations, allowing them to pull exploration results directly into
+their spreadsheets.
+
+
+
+## Access levels
+
+Sharing permissions are organized into three levels:
+
+| Level | Description |
+| --- | --- |
+| **Can view** | View the content and its data |
+| **Can edit** | View and modify the content |
+| **Full access** | Full control including managing who the content is shared with |
+
+The creator of a piece of content automatically receives **Full access**.
+
+
+
+When sharing is set at the [folder level][ref-folders], content inside
+the folder inherits those permissions. See
+[Permission inheritance](#permission-inheritance) for details.
+
+
+
+## Sharing with users and groups
+
+To share content with specific people or groups:
+
+1. Open the workbook, dashboard, or exploration you want to share.
+2. Click the **Share** button.
+3. In the **Share** dialog, type a name or email in the search field to
+ find users or [user groups][ref-groups].
+4. Select the desired [access level](#access-levels) from the dropdown
+ next to the user or group.
+5. Click **Invite**.
+
+{/* Screenshot: Share dialog showing the user/group search input, access level
+dropdown, and Invite button. A user and a group should be visible in the
+"People with access" list with different access levels. */}
+
+Invited users and groups appear in the **People with access** list. You
+can change their access level or remove their access at any time from the
+same dialog.
+
+### Changing access for existing collaborators
+
+To update a collaborator's access level:
+
+1. Open the **Share** dialog for the content.
+2. Find the user or group in the **People with access** list.
+3. Select a new access level from the dropdown next to their name.
+
+To remove access entirely, select **Remove** from the dropdown.
+
+{/* Screenshot: Share dialog "People with access" list showing the access level
+dropdown open for one user, with "Can view", "Can edit", "Full access", and
+"Remove" options visible. */}
+
+## Sharing with your organization
+
+You can make content available to everyone in your Cube Cloud account:
+
+1. Open the **Share** dialog for the content.
+2. In the **General access** section, change the setting from **Only
+ people invited** to **Organization**.
+3. Select the access level that all organization members should receive.
+
+{/* Screenshot: Share dialog with the "General access" section expanded, showing
+the dropdown changed from "Only people invited" to "Organization" with a
+"Can view" access level selected. */}
+
+When organization-wide access is set, individual users and groups can
+still be granted higher access levels. For example, the organization
+might have **Can view** access while a specific team has **Can edit**.
+
+## Permission inheritance
+
+Permissions flow down through the [folder hierarchy][ref-folders]. When
+you share a folder, all content inside it — including workbooks,
+dashboards, explorations, and subfolders — inherits the folder's
+permissions.
+
+```text
+Marketing (Full access for Marketing team)
+├── Q1 Campaign Dashboard ← inherits Full access
+├── Revenue Workbook ← inherits Full access
+└── Weekly Reports ← inherits Full access
+ └── Week 1 Exploration ← inherits Full access
+```
+
+### How inheritance works
+
+- **Direct permissions override inherited ones.** If a workbook has an
+ explicit permission set, that takes priority over the folder permission.
+- **Closest folder wins.** When multiple ancestor folders have
+ permissions for the same user, the nearest folder in the hierarchy
+ determines the effective access level.
+- **Inherited permissions are visible in the Share dialog.** Users and
+ groups with inherited access appear in the collaborator list with an
+ indication of which folder the permission comes from.
+
+{/* Screenshot: Share dialog for a workbook inside a folder, showing the "People
+with access" list where some users have a "via Marketing folder" inherited
+access badge next to their access level, distinguished from users with direct
+access. */}
+
+### Folder visibility
+
+Users can see a folder if any of the following apply:
+
+- They have been granted direct access to the folder
+- They have access to a parent or child folder (ancestor folders are
+ visible for navigation)
+- They have access to content inside the folder
+
+This ensures users can always navigate to content they have permission to
+view, even if they don't have explicit access to every folder along the
+path.
+
+## Sharing folders
+
+Sharing a folder is the most efficient way to manage access for a
+collection of related content:
+
+1. Navigate to the folder in the **Workspace** page.
+2. Open the folder's action menu and select **Share** (or open the folder
+ and click the **Share** button).
+3. Add users, groups, or set organization-wide access as described above.
+
+{/* Screenshot: Workspace page showing a folder's action menu with the "Share"
+option highlighted. */}
+
+All current and future content added to the folder will inherit its
+permissions. This makes folders ideal for organizing content by team or
+project, where everyone on the team needs the same level of access.
+
+## Sharing explorations for spreadsheet integrations
+
+When you share an exploration with a user, that exploration becomes
+available in the [Google Sheets][ref-google-sheets] and
+[Microsoft Excel][ref-excel] add-ins. Users can browse their shared
+explorations directly from the add-in and pull the results into their
+spreadsheets.
+
+This lets analysts share governed, pre-built queries with spreadsheet
+users who can refresh the data on demand without needing to use the Cube
+interface.
+
+[ref-folders]: /docs/organize-content/folders
+[ref-groups]: /admin/users-and-permissions/user-groups
+[ref-roles]: /admin/users-and-permissions/roles-and-permissions
+[ref-google-sheets]: /docs/integrations/google-sheets
+[ref-excel]: /docs/integrations/microsoft-excel
diff --git a/docs-mintlify/embedding/index.mdx b/docs-mintlify/embedding/index.mdx
index d71523da19cd6..b434068aee78c 100644
--- a/docs-mintlify/embedding/index.mdx
+++ b/docs-mintlify/embedding/index.mdx
@@ -1,9 +1,9 @@
---
-description: Embed Cube analytics into your applications using iframes, the React SDK, or API-first approaches.
+description: Embed AI-powered analytics into your applications using iframes, the React SDK, or API-first approaches.
title: Overview
---
-Cube provides multiple approaches for embedding analytics into your applications. Choose the method that best fits your use case and technical requirements.
+Cube provides multiple approaches for embedding AI-powered analytics into your applications — from iframe embeds to fully custom embedded analytics experiences built on Cube's APIs.
## Iframe-based embedding
@@ -33,7 +33,7 @@ Build fully custom embedded analytics experiences using Cube APIs directly. This
### Chat API
-Use the [Chat API](/reference/embed-apis/chat-api) to build custom conversational analytics experiences. This API-first approach lets you programmatically integrate AI-powered conversations with your data, giving you full control over the chat interface and user experience.
+Use the [Chat API](/reference/embed-apis/chat-api) to build custom conversational analytics experiences. Let your users ask questions in plain language and get trusted answers powered by your semantic layer — without building your own AI infrastructure.
### Core Data APIs
diff --git a/packages/cubejs-api-gateway/src/gateway.ts b/packages/cubejs-api-gateway/src/gateway.ts
index c86d0d2b8a627..8b8bb95adfbe0 100644
--- a/packages/cubejs-api-gateway/src/gateway.ts
+++ b/packages/cubejs-api-gateway/src/gateway.ts
@@ -454,7 +454,7 @@ class ApiGateway {
try {
await this.assertApiScope('data', req.context?.securityContext);
- await this.sqlServer.execSql(req.body.query, res, req.context?.securityContext, req.body.cache, req.body.timezone, req.body.throwContinueWait);
+ await this.sqlServer.execSql(req.body.query, res, req.context?.securityContext, req.body.cache, req.body.timezone, req.body.throwContinueWait, req.context?.requestId);
} catch (e: any) {
// Quickfix for https://github.com/cube-js/cube/issues/10450,
// Right now, it's too complicated to fix the issue correctly, because
diff --git a/packages/cubejs-api-gateway/src/sql-server.ts b/packages/cubejs-api-gateway/src/sql-server.ts
index b121530dc22b7..fe59c4bfdc713 100644
--- a/packages/cubejs-api-gateway/src/sql-server.ts
+++ b/packages/cubejs-api-gateway/src/sql-server.ts
@@ -74,8 +74,8 @@ export class SQLServer {
return this.sqlInterfaceInstance;
}
- public async execSql(sqlQuery: string, stream: any, securityContext?: any, cacheMode?: CacheMode, timezone?: string, throwContinueWait?: boolean) {
- await execSql(this.getSqlInterfaceInstance(), sqlQuery, stream, securityContext, cacheMode, timezone, throwContinueWait);
+ public async execSql(sqlQuery: string, stream: any, securityContext?: any, cacheMode?: CacheMode, timezone?: string, throwContinueWait?: boolean, requestId?: string) {
+ await execSql(this.getSqlInterfaceInstance(), sqlQuery, stream, securityContext, cacheMode, timezone, throwContinueWait, requestId);
}
public async sql4sql(sqlQuery: string, disablePostProcessing: boolean, securityContext?: unknown): Promise {
diff --git a/packages/cubejs-api-gateway/test/index.test.ts b/packages/cubejs-api-gateway/test/index.test.ts
index 245daf2ae1830..d2edf5c1f7894 100644
--- a/packages/cubejs-api-gateway/test/index.test.ts
+++ b/packages/cubejs-api-gateway/test/index.test.ts
@@ -1224,6 +1224,7 @@ describe('API Gateway', () => {
undefined,
undefined,
undefined,
+ expect.any(String),
);
});
@@ -1264,6 +1265,7 @@ describe('API Gateway', () => {
'stale-while-revalidate',
'America/Los_Angeles',
undefined,
+ expect.any(String),
);
});
@@ -1300,7 +1302,75 @@ describe('API Gateway', () => {
undefined,
undefined,
true,
+ expect.any(String),
);
});
+
+ test('request id is propagated from x-request-id header', async () => {
+ const { app, apiGateway } = await createApiGateway();
+
+ const execSqlMock = jest.fn(async (query, stream, securityContext, cacheMode, timezone, throwContinueWait, requestId) => {
+ stream.write(`${JSON.stringify({
+ schema: [{ name: 'id', column_type: 'Int' }]
+ })}\n`);
+ stream.write(`${JSON.stringify({
+ data: [[1]]
+ })}\n`);
+ stream.end();
+ });
+
+ apiGateway.getSQLServer().execSql = execSqlMock;
+
+ await request(app)
+ .post('/cubejs-api/v1/cubesql')
+ .set('Content-type', 'application/json')
+ .set('Authorization', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.t-IDcSemACt8x4iTMCda8Yhe3iZaWbvV5XKSTbuAn0M')
+ .set('x-request-id', 'test-request-id-12345')
+ .send({
+ query: 'SELECT id FROM test LIMIT 1'
+ })
+ .responseType('text')
+ .expect(200);
+
+ expect(execSqlMock).toHaveBeenCalledWith(
+ 'SELECT id FROM test LIMIT 1',
+ expect.anything(),
+ {},
+ undefined,
+ undefined,
+ undefined,
+ 'test-request-id-12345',
+ );
+ });
+
+ test('request id is auto-generated when no header is provided', async () => {
+ const { app, apiGateway } = await createApiGateway();
+
+ const execSqlMock = jest.fn(async (query: any, stream: any, securityContext: any, cacheMode: any, timezone: any, throwContinueWait: any, requestId: any) => {
+ stream.write(`${JSON.stringify({
+ schema: [{ name: 'id', column_type: 'Int' }]
+ })}\n`);
+ stream.write(`${JSON.stringify({
+ data: [[1]]
+ })}\n`);
+ stream.end();
+ });
+
+ apiGateway.getSQLServer().execSql = execSqlMock;
+
+ await request(app)
+ .post('/cubejs-api/v1/cubesql')
+ .set('Content-type', 'application/json')
+ .set('Authorization', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.t-IDcSemACt8x4iTMCda8Yhe3iZaWbvV5XKSTbuAn0M')
+ .send({
+ query: 'SELECT id FROM test LIMIT 1'
+ })
+ .responseType('text')
+ .expect(200);
+
+ const requestId = execSqlMock.mock.calls[0][6];
+ expect(requestId).toBeDefined();
+ expect(requestId).toMatch(/^[0-9a-f-]+-span-1$/);
+ });
});
});
diff --git a/packages/cubejs-backend-native/js/index.ts b/packages/cubejs-backend-native/js/index.ts
index d085f65ef3e25..9abdde2e3b17a 100644
--- a/packages/cubejs-backend-native/js/index.ts
+++ b/packages/cubejs-backend-native/js/index.ts
@@ -438,10 +438,10 @@ export const shutdownInterface = async (instance: SqlInterfaceInstance, shutdown
await native.shutdownInterface(instance, shutdownMode);
};
-export const execSql = async (instance: SqlInterfaceInstance, sqlQuery: string, stream: any, securityContext?: any, cacheMode: CacheMode = 'stale-if-slow', timezone?: string, throwContinueWait?: boolean): Promise => {
+export const execSql = async (instance: SqlInterfaceInstance, sqlQuery: string, stream: any, securityContext?: any, cacheMode: CacheMode = 'stale-if-slow', timezone?: string, throwContinueWait?: boolean, requestId?: string): Promise => {
const native = loadNative();
- await native.execSql(instance, sqlQuery, stream, securityContext ? JSON.stringify(securityContext) : null, cacheMode, timezone, throwContinueWait);
+ await native.execSql(instance, sqlQuery, stream, securityContext ? JSON.stringify(securityContext) : null, cacheMode, timezone, throwContinueWait, requestId);
};
// TODO parse result from native code
diff --git a/packages/cubejs-backend-native/src/node_export.rs b/packages/cubejs-backend-native/src/node_export.rs
index 05a3850c6ff80..985b21e0fa22d 100644
--- a/packages/cubejs-backend-native/src/node_export.rs
+++ b/packages/cubejs-backend-native/src/node_export.rs
@@ -240,9 +240,10 @@ async fn handle_sql_query(
cache_mode: &str,
timezone: Option,
throw_continue_wait: bool,
+ request_id: Option,
) -> Result<(), CubeError> {
let span_id = Some(Arc::new(SpanId::new(
- Uuid::new_v4().to_string(),
+ request_id.unwrap_or_else(|| Uuid::new_v4().to_string()),
serde_json::json!({ "sql": sql_query }),
)));
@@ -528,6 +529,20 @@ fn exec_sql(mut cx: FunctionContext) -> JsResult {
Err(_) => false,
};
+ let request_id: Option = match cx.argument::(7) {
+ Ok(val) => {
+ if val.is_a::(&mut cx) || val.is_a::(&mut cx) {
+ None
+ } else {
+ match val.downcast::(&mut cx) {
+ Ok(v) => Some(v.value(&mut cx)),
+ Err(_) => None,
+ }
+ }
+ }
+ Err(_) => None,
+ };
+
let js_stream_on_fn = Arc::new(
node_stream
.get::(&mut cx, "on")?
@@ -578,6 +593,7 @@ fn exec_sql(mut cx: FunctionContext) -> JsResult {
&cache_mode,
timezone,
throw_continue_wait,
+ request_id,
)
.await;