diff --git a/CHANGELOG.md b/CHANGELOG.md index c9d34b6ea6223..8c6d69999a331 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +### Bug Fixes + +- **query-orchestrator:** keep Interactive priority on user query paths ([#11715](https://github.com/cube-js/cube/issues/11715)) ([39d3b20](https://github.com/cube-js/cube/commit/39d3b20d114d6003562e0cdad0e98389056bb3a5)) +- **tesseract:** calendar sql granularities crash every query; to_date ignores the calendar ([#11709](https://github.com/cube-js/cube/issues/11709)) ([4029495](https://github.com/cube-js/cube/commit/4029495feb0cadfee20c56f565d94430852f5b27)) +- **tesseract:** compose grain.include with rolling_window ([#11639](https://github.com/cube-js/cube/issues/11639)) ([9d3dd45](https://github.com/cube-js/cube/commit/9d3dd45814a7fec41b6c4e23233f38bd7a1af1c2)) +- **tesseract:** convert a view's raw time dimension timezone only once ([#11712](https://github.com/cube-js/cube/issues/11712)) ([1ae6452](https://github.com/cube-js/cube/commit/1ae6452a139d227935e3b68d9c8cc9283d982d3d)) + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) ### Bug Fixes diff --git a/lerna.json b/lerna.json index c47c3b5eccfaa..7ddf6945bc0c2 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "1.7.31", + "version": "1.7.32", "npmClient": "yarn", "command": { "bootstrap": { diff --git a/packages/cubejs-api-gateway/CHANGELOG.md b/packages/cubejs-api-gateway/CHANGELOG.md index 2ced4fa35f090..903bf07b7fcfc 100644 --- a/packages/cubejs-api-gateway/CHANGELOG.md +++ b/packages/cubejs-api-gateway/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/api-gateway + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/api-gateway diff --git a/packages/cubejs-api-gateway/package.json b/packages/cubejs-api-gateway/package.json index 67e0d0ac3c65d..2d4f7578a2b2a 100644 --- a/packages/cubejs-api-gateway/package.json +++ b/packages/cubejs-api-gateway/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/api-gateway", "description": "Cube API Gateway", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -27,9 +27,9 @@ "dist/src/*" ], "dependencies": { - "@cubejs-backend/native": "1.7.31", - "@cubejs-backend/query-orchestrator": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/native": "1.7.32", + "@cubejs-backend/query-orchestrator": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "@ungap/structured-clone": "^0.3.4", "assert-never": "^1.4.0", "body-parser": "^1.19.0", @@ -53,7 +53,7 @@ "zod": "^4.1.13" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.31", + "@cubejs-backend/linter": "1.7.32", "@types/express": "^4.17.21", "@types/jest": "^29", "@types/jsonwebtoken": "^9.0.2", diff --git a/packages/cubejs-api-gateway/test/normalize-query.test.ts b/packages/cubejs-api-gateway/test/normalize-query.test.ts index 75229aaa49d62..1899bd3811f84 100644 --- a/packages/cubejs-api-gateway/test/normalize-query.test.ts +++ b/packages/cubejs-api-gateway/test/normalize-query.test.ts @@ -161,3 +161,15 @@ describe('cubeSqlRequestSchema', () => { expect(cubeSqlRequestSchema.validate({ ...baseBody, timezone: tz }).error).toBeDefined(); }); }); + +describe('limit normalization', () => { + test('keeps an explicit limit of 0 instead of applying the default limit', () => { + const result = normalizeQuery({ ...baseQuery, limit: 0 }, false); + expect(result.limit).toBe(0); + }); + + test('applies the default limit when no limit is given', () => { + const result = normalizeQuery({ ...baseQuery }, false); + expect(result.limit).toBeGreaterThan(0); + }); +}); diff --git a/packages/cubejs-athena-driver/CHANGELOG.md b/packages/cubejs-athena-driver/CHANGELOG.md index 33bf14e29dd0b..e4731f0819c03 100644 --- a/packages/cubejs-athena-driver/CHANGELOG.md +++ b/packages/cubejs-athena-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/athena-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/athena-driver diff --git a/packages/cubejs-athena-driver/package.json b/packages/cubejs-athena-driver/package.json index 9625fed3494c2..9df08bb211a3b 100644 --- a/packages/cubejs-athena-driver/package.json +++ b/packages/cubejs-athena-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/athena-driver", "description": "Cube.js Athena database driver", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -31,12 +31,12 @@ "dependencies": { "@aws-sdk/client-athena": "^3.22.0", "@aws-sdk/credential-providers": "^3.22.0", - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/shared": "1.7.31" + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/shared": "1.7.32" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.31", - "@cubejs-backend/testing-shared": "1.7.31", + "@cubejs-backend/linter": "1.7.32", + "@cubejs-backend/testing-shared": "1.7.32", "@types/ramda": "^0.27.40", "typescript": "~5.2.2" }, diff --git a/packages/cubejs-backend-cloud/CHANGELOG.md b/packages/cubejs-backend-cloud/CHANGELOG.md index 106dfe6c4bedc..6e264c687486c 100644 --- a/packages/cubejs-backend-cloud/CHANGELOG.md +++ b/packages/cubejs-backend-cloud/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/cloud + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/cloud diff --git a/packages/cubejs-backend-cloud/package.json b/packages/cubejs-backend-cloud/package.json index fcff9a30e3a15..f31a00b403263 100644 --- a/packages/cubejs-backend-cloud/package.json +++ b/packages/cubejs-backend-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-backend/cloud", - "version": "1.7.31", + "version": "1.7.32", "description": "Cube Cloud package", "main": "dist/src/index.js", "typings": "dist/src/index.d.ts", @@ -30,7 +30,7 @@ "devDependencies": { "@babel/core": "^7.24.5", "@babel/preset-env": "^7.24.5", - "@cubejs-backend/linter": "1.7.31", + "@cubejs-backend/linter": "1.7.32", "@types/fs-extra": "^9.0.8", "@types/jest": "^29", "jest": "^29", @@ -38,7 +38,7 @@ }, "dependencies": { "@cubejs-backend/dotenv": "^9.0.2", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/shared": "1.7.32", "chokidar": "^3.5.1", "env-var": "^6.3.0", "form-data": "^4.0.0", diff --git a/packages/cubejs-backend-maven/CHANGELOG.md b/packages/cubejs-backend-maven/CHANGELOG.md index 8e7599f08b22e..51784717a8e94 100644 --- a/packages/cubejs-backend-maven/CHANGELOG.md +++ b/packages/cubejs-backend-maven/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/maven + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/maven diff --git a/packages/cubejs-backend-maven/package.json b/packages/cubejs-backend-maven/package.json index 73af82480e1fe..cc1d516ed2c9f 100644 --- a/packages/cubejs-backend-maven/package.json +++ b/packages/cubejs-backend-maven/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/maven", "description": "Cube.js Maven Wrapper for java dependencies downloading", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "license": "Apache-2.0", "repository": { "type": "git", @@ -31,12 +31,12 @@ "dist/src/*" ], "dependencies": { - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/shared": "1.7.32", "source-map-support": "^0.5.19", "xmlbuilder2": "^2.4.0" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.31", + "@cubejs-backend/linter": "1.7.32", "@types/jest": "^29", "@types/node": "^22", "jest": "^29", diff --git a/packages/cubejs-backend-native/CHANGELOG.md b/packages/cubejs-backend-native/CHANGELOG.md index 32cc71c63dfb6..1e1e5b9adda28 100644 --- a/packages/cubejs-backend-native/CHANGELOG.md +++ b/packages/cubejs-backend-native/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/native + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/native diff --git a/packages/cubejs-backend-native/package.json b/packages/cubejs-backend-native/package.json index b35df0ff223b4..4b5d97f45e240 100644 --- a/packages/cubejs-backend-native/package.json +++ b/packages/cubejs-backend-native/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-backend/native", - "version": "1.7.31", + "version": "1.7.32", "author": "Cube Dev, Inc.", "description": "Native module for Cube.js (binding to Rust codebase)", "main": "dist/js/index.js", @@ -39,7 +39,7 @@ "dist/js" ], "devDependencies": { - "@cubejs-backend/linter": "1.7.31", + "@cubejs-backend/linter": "1.7.32", "@types/jest": "^29", "@types/node": "^22", "cargo-cp-artifact": "^0.1.9", @@ -50,8 +50,8 @@ "uuid": "^11.1.1" }, "dependencies": { - "@cubejs-backend/cubesql": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/cubesql": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "@cubejs-infra/post-installer": "^0.1.2" }, "resources": { diff --git a/packages/cubejs-backend-shared/CHANGELOG.md b/packages/cubejs-backend-shared/CHANGELOG.md index 15c8089bc3bf9..1571ab31316c4 100644 --- a/packages/cubejs-backend-shared/CHANGELOG.md +++ b/packages/cubejs-backend-shared/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/shared + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) ### Features diff --git a/packages/cubejs-backend-shared/package.json b/packages/cubejs-backend-shared/package.json index 139dc874ca9ae..93dfee5a3d519 100644 --- a/packages/cubejs-backend-shared/package.json +++ b/packages/cubejs-backend-shared/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-backend/shared", - "version": "1.7.31", + "version": "1.7.32", "description": "Shared code for Cube.js backend packages", "main": "dist/src/index.js", "typings": "dist/src/index.d.ts", @@ -27,7 +27,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@cubejs-backend/linter": "1.7.31", + "@cubejs-backend/linter": "1.7.32", "@types/bytes": "^3.1.5", "@types/cli-progress": "^3.9.1", "@types/jest": "^29", diff --git a/packages/cubejs-base-driver/CHANGELOG.md b/packages/cubejs-base-driver/CHANGELOG.md index 66c2dbbcc3cc8..0fe949a2c0d2a 100644 --- a/packages/cubejs-base-driver/CHANGELOG.md +++ b/packages/cubejs-base-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/base-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/base-driver diff --git a/packages/cubejs-base-driver/package.json b/packages/cubejs-base-driver/package.json index ff861a150058b..338ddaa4e4810 100644 --- a/packages/cubejs-base-driver/package.json +++ b/packages/cubejs-base-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/base-driver", "description": "Cube.js Base Driver", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -33,11 +33,11 @@ "@aws-sdk/s3-request-presigner": "^3.49.0", "@azure/identity": "^4.4.1", "@azure/storage-blob": "^12.9.0", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/shared": "1.7.32", "@google-cloud/storage": "^7.13.0" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.31", + "@cubejs-backend/linter": "1.7.32", "@types/jest": "^29", "@types/node": "^22", "jest": "^29", diff --git a/packages/cubejs-bigquery-driver/CHANGELOG.md b/packages/cubejs-bigquery-driver/CHANGELOG.md index 139d01837c5a5..688d8347078fd 100644 --- a/packages/cubejs-bigquery-driver/CHANGELOG.md +++ b/packages/cubejs-bigquery-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/bigquery-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/bigquery-driver diff --git a/packages/cubejs-bigquery-driver/package.json b/packages/cubejs-bigquery-driver/package.json index 91e7e1470eef2..6169dd6ccc982 100644 --- a/packages/cubejs-bigquery-driver/package.json +++ b/packages/cubejs-bigquery-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/bigquery-driver", "description": "Cube.js BigQuery database driver", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -29,15 +29,15 @@ "main": "index.js", "types": "dist/src/index.d.ts", "dependencies": { - "@cubejs-backend/base-driver": "1.7.31", + "@cubejs-backend/base-driver": "1.7.32", "@cubejs-backend/dotenv": "^9.0.2", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/shared": "1.7.32", "@google-cloud/bigquery": "^7.7.0", "@google-cloud/storage": "^7.13.0", "ramda": "^0.27.2" }, "devDependencies": { - "@cubejs-backend/testing-shared": "1.7.31", + "@cubejs-backend/testing-shared": "1.7.32", "@types/big.js": "^6.2.2", "@types/dedent": "^0.7.0", "@types/jest": "^29", diff --git a/packages/cubejs-cli/CHANGELOG.md b/packages/cubejs-cli/CHANGELOG.md index d9b282f6e2354..4298e8dc205e4 100644 --- a/packages/cubejs-cli/CHANGELOG.md +++ b/packages/cubejs-cli/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package cubejs-cli + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package cubejs-cli diff --git a/packages/cubejs-cli/package.json b/packages/cubejs-cli/package.json index d4e5385c41874..90111ec05666f 100644 --- a/packages/cubejs-cli/package.json +++ b/packages/cubejs-cli/package.json @@ -2,7 +2,7 @@ "name": "cubejs-cli", "description": "Cube.js Command Line Interface", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -30,10 +30,10 @@ "LICENSE" ], "dependencies": { - "@cubejs-backend/cloud": "1.7.31", + "@cubejs-backend/cloud": "1.7.32", "@cubejs-backend/dotenv": "^9.0.2", - "@cubejs-backend/schema-compiler": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/schema-compiler": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "chalk": "^2.4.2", "cli-progress": "^3.10", "commander": "^2.19.0", @@ -50,8 +50,8 @@ "colors": "1.4.0" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.31", - "@cubejs-backend/server": "1.7.31", + "@cubejs-backend/linter": "1.7.32", + "@cubejs-backend/server": "1.7.32", "@oclif/command": "^1.8.0", "@types/cli-progress": "^3.8.0", "@types/cross-spawn": "^6.0.2", diff --git a/packages/cubejs-clickhouse-driver/CHANGELOG.md b/packages/cubejs-clickhouse-driver/CHANGELOG.md index 925759cf0a0b3..b2ed9502e2184 100644 --- a/packages/cubejs-clickhouse-driver/CHANGELOG.md +++ b/packages/cubejs-clickhouse-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/clickhouse-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/clickhouse-driver diff --git a/packages/cubejs-clickhouse-driver/package.json b/packages/cubejs-clickhouse-driver/package.json index 0dea34959f94f..bab6cee56d30e 100644 --- a/packages/cubejs-clickhouse-driver/package.json +++ b/packages/cubejs-clickhouse-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/clickhouse-driver", "description": "Cube.js ClickHouse database driver", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -29,16 +29,16 @@ }, "dependencies": { "@clickhouse/client": "^1.12.0", - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "moment": "^2.24.0", "sqlstring": "^2.3.1", "uuid": "^11.1.1" }, "license": "Apache-2.0", "devDependencies": { - "@cubejs-backend/linter": "1.7.31", - "@cubejs-backend/testing-shared": "1.7.31", + "@cubejs-backend/linter": "1.7.32", + "@cubejs-backend/testing-shared": "1.7.32", "@types/jest": "^29", "jest": "^29", "typescript": "~5.2.2" diff --git a/packages/cubejs-client-core/CHANGELOG.md b/packages/cubejs-client-core/CHANGELOG.md index a65cfdace21e7..c8679dbea86da 100644 --- a/packages/cubejs-client-core/CHANGELOG.md +++ b/packages/cubejs-client-core/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-client/core + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-client/core diff --git a/packages/cubejs-client-core/package.json b/packages/cubejs-client-core/package.json index b463365078b3d..879cc60d3ea99 100644 --- a/packages/cubejs-client-core/package.json +++ b/packages/cubejs-client-core/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-client/core", - "version": "1.7.31", + "version": "1.7.32", "engines": {}, "type": "module", "repository": { @@ -58,7 +58,7 @@ ], "license": "MIT", "devDependencies": { - "@cubejs-backend/linter": "1.7.31", + "@cubejs-backend/linter": "1.7.32", "@types/d3-format": "^3", "@types/d3-time-format": "^4", "@types/moment-range": "^4.0.0", diff --git a/packages/cubejs-client-dx/CHANGELOG.md b/packages/cubejs-client-dx/CHANGELOG.md index 9e0c2aa9b541d..f5e6be6de953e 100644 --- a/packages/cubejs-client-dx/CHANGELOG.md +++ b/packages/cubejs-client-dx/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-client/dx + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-client/dx diff --git a/packages/cubejs-client-dx/package.json b/packages/cubejs-client-dx/package.json index 67f3c4f3e5c29..e784dd94aabdd 100644 --- a/packages/cubejs-client-dx/package.json +++ b/packages/cubejs-client-dx/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-client/dx", - "version": "1.7.31", + "version": "1.7.32", "engines": {}, "repository": { "type": "git", diff --git a/packages/cubejs-client-ngx/CHANGELOG.md b/packages/cubejs-client-ngx/CHANGELOG.md index e0e880e5ba75e..87be39440a148 100644 --- a/packages/cubejs-client-ngx/CHANGELOG.md +++ b/packages/cubejs-client-ngx/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-client/ngx + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-client/ngx diff --git a/packages/cubejs-client-ngx/package.json b/packages/cubejs-client-ngx/package.json index 9d30fe40451d2..4e50b6a0592df 100644 --- a/packages/cubejs-client-ngx/package.json +++ b/packages/cubejs-client-ngx/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-client/ngx", - "version": "1.7.31", + "version": "1.7.32", "author": "Cube Dev, Inc.", "engines": {}, "repository": { diff --git a/packages/cubejs-client-react/CHANGELOG.md b/packages/cubejs-client-react/CHANGELOG.md index f8ffa5c5a3437..8ac4b985fd878 100644 --- a/packages/cubejs-client-react/CHANGELOG.md +++ b/packages/cubejs-client-react/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-client/react + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-client/react diff --git a/packages/cubejs-client-react/package.json b/packages/cubejs-client-react/package.json index b097d34e744d6..d081a7238cd61 100644 --- a/packages/cubejs-client-react/package.json +++ b/packages/cubejs-client-react/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-client/react", - "version": "1.7.31", + "version": "1.7.32", "author": "Cube Dev, Inc.", "license": "MIT", "engines": {}, @@ -26,7 +26,7 @@ ], "dependencies": { "@babel/runtime": "^7.1.2", - "@cubejs-client/core": "1.7.31", + "@cubejs-client/core": "1.7.32", "core-js": "^3.6.5", "ramda": "^0.27.2" }, diff --git a/packages/cubejs-client-vue3/CHANGELOG.md b/packages/cubejs-client-vue3/CHANGELOG.md index d20041878ee3d..7166ce797b8eb 100644 --- a/packages/cubejs-client-vue3/CHANGELOG.md +++ b/packages/cubejs-client-vue3/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-client/vue3 + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-client/vue3 diff --git a/packages/cubejs-client-vue3/package.json b/packages/cubejs-client-vue3/package.json index a56caac886f7b..9c3c35204911a 100644 --- a/packages/cubejs-client-vue3/package.json +++ b/packages/cubejs-client-vue3/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-client/vue3", - "version": "1.7.31", + "version": "1.7.32", "engines": {}, "repository": { "type": "git", @@ -27,7 +27,7 @@ "src" ], "dependencies": { - "@cubejs-client/core": "1.7.31", + "@cubejs-client/core": "1.7.32", "ramda": "^0.27.0" }, "devDependencies": { diff --git a/packages/cubejs-client-ws-transport/CHANGELOG.md b/packages/cubejs-client-ws-transport/CHANGELOG.md index c6f4ec82d7eed..08c8c2b1e82fe 100644 --- a/packages/cubejs-client-ws-transport/CHANGELOG.md +++ b/packages/cubejs-client-ws-transport/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-client/ws-transport + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-client/ws-transport diff --git a/packages/cubejs-client-ws-transport/package.json b/packages/cubejs-client-ws-transport/package.json index 5ee747f310f36..3babae69ad399 100644 --- a/packages/cubejs-client-ws-transport/package.json +++ b/packages/cubejs-client-ws-transport/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-client/ws-transport", - "version": "1.7.31", + "version": "1.7.32", "engines": {}, "repository": { "type": "git", @@ -20,7 +20,7 @@ }, "dependencies": { "@babel/runtime": "^7.1.2", - "@cubejs-client/core": "1.7.31", + "@cubejs-client/core": "1.7.32", "core-js": "^3.6.5", "isomorphic-ws": "^4.0.1", "ws": "^7.3.1" @@ -33,7 +33,7 @@ "@babel/core": "^7.3.3", "@babel/preset-env": "^7.3.1", "@babel/preset-typescript": "^7.12.1", - "@cubejs-backend/linter": "1.7.31", + "@cubejs-backend/linter": "1.7.32", "@types/ws": "^7.2.9", "typescript": "~5.2.2" }, diff --git a/packages/cubejs-crate-driver/CHANGELOG.md b/packages/cubejs-crate-driver/CHANGELOG.md index 455ca2ef7d784..12a6e83fdce77 100644 --- a/packages/cubejs-crate-driver/CHANGELOG.md +++ b/packages/cubejs-crate-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/crate-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/crate-driver diff --git a/packages/cubejs-crate-driver/package.json b/packages/cubejs-crate-driver/package.json index 07f7d8bddf822..7cee885a9f4ab 100644 --- a/packages/cubejs-crate-driver/package.json +++ b/packages/cubejs-crate-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/crate-driver", "description": "Cube.js Crate database driver", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -29,13 +29,13 @@ "lint:fix": "eslint --fix src/* --ext .ts" }, "dependencies": { - "@cubejs-backend/postgres-driver": "1.7.31", - "@cubejs-backend/shared": "1.7.31" + "@cubejs-backend/postgres-driver": "1.7.32", + "@cubejs-backend/shared": "1.7.32" }, "license": "Apache-2.0", "devDependencies": { - "@cubejs-backend/linter": "1.7.31", - "@cubejs-backend/testing-shared": "1.7.31", + "@cubejs-backend/linter": "1.7.32", + "@cubejs-backend/testing-shared": "1.7.32", "testcontainers": "^10.28.0", "typescript": "~5.2.2" }, diff --git a/packages/cubejs-cubestore-driver/CHANGELOG.md b/packages/cubejs-cubestore-driver/CHANGELOG.md index c912263b784cc..a7caab2a014f7 100644 --- a/packages/cubejs-cubestore-driver/CHANGELOG.md +++ b/packages/cubejs-cubestore-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/cubestore-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/cubestore-driver diff --git a/packages/cubejs-cubestore-driver/package.json b/packages/cubejs-cubestore-driver/package.json index 6e4c0e8270a3c..197e0dc5655cd 100644 --- a/packages/cubejs-cubestore-driver/package.json +++ b/packages/cubejs-cubestore-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/cubestore-driver", "description": "Cube Store driver", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -27,10 +27,10 @@ "unit": "jest --coverage" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/cubestore": "1.7.31", - "@cubejs-backend/native": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/cubestore": "1.7.32", + "@cubejs-backend/native": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "csv-write-stream": "^2.0.0", "flatbuffers": "25.9.23", "fs-extra": "^9.1.0", @@ -41,7 +41,7 @@ "ws": "^7.4.3" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.31", + "@cubejs-backend/linter": "1.7.32", "@types/csv-write-stream": "^2.0.0", "@types/jest": "^29", "@types/node": "^22", diff --git a/packages/cubejs-databricks-jdbc-driver/CHANGELOG.md b/packages/cubejs-databricks-jdbc-driver/CHANGELOG.md index d017227bb1901..0c8c6d8fc3af4 100644 --- a/packages/cubejs-databricks-jdbc-driver/CHANGELOG.md +++ b/packages/cubejs-databricks-jdbc-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/databricks-jdbc-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/databricks-jdbc-driver diff --git a/packages/cubejs-databricks-jdbc-driver/package.json b/packages/cubejs-databricks-jdbc-driver/package.json index 43ba30a838826..56e11b43c4dd9 100644 --- a/packages/cubejs-databricks-jdbc-driver/package.json +++ b/packages/cubejs-databricks-jdbc-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/databricks-jdbc-driver", "description": "Cube.js Databricks database driver", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "license": "Apache-2.0", "repository": { "type": "git", @@ -30,17 +30,17 @@ "bin" ], "dependencies": { - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/jdbc-driver": "1.7.31", - "@cubejs-backend/schema-compiler": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/jdbc-driver": "1.7.32", + "@cubejs-backend/schema-compiler": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "node-fetch": "^2.6.1", "ramda": "^0.27.2", "source-map-support": "^0.5.19", "uuid": "^11.1.1" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.31", + "@cubejs-backend/linter": "1.7.32", "@types/jest": "^29", "@types/node": "^22", "@types/ramda": "^0.27.34", diff --git a/packages/cubejs-dbt-schema-extension/CHANGELOG.md b/packages/cubejs-dbt-schema-extension/CHANGELOG.md index e6ec7c74b61a9..0f879e2ba5a1f 100644 --- a/packages/cubejs-dbt-schema-extension/CHANGELOG.md +++ b/packages/cubejs-dbt-schema-extension/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/dbt-schema-extension + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/dbt-schema-extension diff --git a/packages/cubejs-dbt-schema-extension/package.json b/packages/cubejs-dbt-schema-extension/package.json index 9b5e08b991ad7..f117e76984be6 100644 --- a/packages/cubejs-dbt-schema-extension/package.json +++ b/packages/cubejs-dbt-schema-extension/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/dbt-schema-extension", "description": "Cube.js dbt Schema Extension", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -25,14 +25,14 @@ "lint:fix": "eslint --fix src/* --ext .ts,.js" }, "dependencies": { - "@cubejs-backend/schema-compiler": "1.7.31", + "@cubejs-backend/schema-compiler": "1.7.32", "fs-extra": "^9.1.0", "inflection": "^1.12.0", "node-fetch": "^2.6.1" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.31", - "@cubejs-backend/testing": "1.7.31", + "@cubejs-backend/linter": "1.7.32", + "@cubejs-backend/testing": "1.7.32", "@types/jest": "^29", "jest": "^29", "stream-to-array": "^2.3.0", diff --git a/packages/cubejs-docker/CHANGELOG.md b/packages/cubejs-docker/CHANGELOG.md index 5432c52af3f24..95a231621c95b 100644 --- a/packages/cubejs-docker/CHANGELOG.md +++ b/packages/cubejs-docker/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/docker + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/docker diff --git a/packages/cubejs-docker/package.json b/packages/cubejs-docker/package.json index 3c1f447de511b..f257a6fd5aa12 100644 --- a/packages/cubejs-docker/package.json +++ b/packages/cubejs-docker/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-backend/docker", - "version": "1.7.31", + "version": "1.7.32", "description": "Cube.js In Docker (virtual package)", "author": "Cube Dev, Inc.", "license": "Apache-2.0", @@ -9,34 +9,34 @@ "node": ">=20.0.0" }, "dependencies": { - "@cubejs-backend/athena-driver": "1.7.31", - "@cubejs-backend/bigquery-driver": "1.7.31", - "@cubejs-backend/clickhouse-driver": "1.7.31", - "@cubejs-backend/crate-driver": "1.7.31", - "@cubejs-backend/databricks-jdbc-driver": "1.7.31", - "@cubejs-backend/dbt-schema-extension": "1.7.31", - "@cubejs-backend/dremio-driver": "1.7.31", - "@cubejs-backend/druid-driver": "1.7.31", - "@cubejs-backend/duckdb-driver": "1.7.31", - "@cubejs-backend/firebolt-driver": "1.7.31", - "@cubejs-backend/hive-driver": "1.7.31", - "@cubejs-backend/ksql-driver": "1.7.31", - "@cubejs-backend/materialize-driver": "1.7.31", - "@cubejs-backend/mongobi-driver": "1.7.31", - "@cubejs-backend/mssql-driver": "1.7.31", - "@cubejs-backend/mysql-driver": "1.7.31", - "@cubejs-backend/oracle-driver": "1.7.31", - "@cubejs-backend/pinot-driver": "1.7.31", - "@cubejs-backend/postgres-driver": "1.7.31", - "@cubejs-backend/prestodb-driver": "1.7.31", - "@cubejs-backend/questdb-driver": "1.7.31", - "@cubejs-backend/redshift-driver": "1.7.31", - "@cubejs-backend/server": "1.7.31", - "@cubejs-backend/snowflake-driver": "1.7.31", - "@cubejs-backend/sqlite-driver": "1.7.31", - "@cubejs-backend/trino-driver": "1.7.31", - "@cubejs-backend/vertica-driver": "1.7.31", - "cubejs-cli": "1.7.31", + "@cubejs-backend/athena-driver": "1.7.32", + "@cubejs-backend/bigquery-driver": "1.7.32", + "@cubejs-backend/clickhouse-driver": "1.7.32", + "@cubejs-backend/crate-driver": "1.7.32", + "@cubejs-backend/databricks-jdbc-driver": "1.7.32", + "@cubejs-backend/dbt-schema-extension": "1.7.32", + "@cubejs-backend/dremio-driver": "1.7.32", + "@cubejs-backend/druid-driver": "1.7.32", + "@cubejs-backend/duckdb-driver": "1.7.32", + "@cubejs-backend/firebolt-driver": "1.7.32", + "@cubejs-backend/hive-driver": "1.7.32", + "@cubejs-backend/ksql-driver": "1.7.32", + "@cubejs-backend/materialize-driver": "1.7.32", + "@cubejs-backend/mongobi-driver": "1.7.32", + "@cubejs-backend/mssql-driver": "1.7.32", + "@cubejs-backend/mysql-driver": "1.7.32", + "@cubejs-backend/oracle-driver": "1.7.32", + "@cubejs-backend/pinot-driver": "1.7.32", + "@cubejs-backend/postgres-driver": "1.7.32", + "@cubejs-backend/prestodb-driver": "1.7.32", + "@cubejs-backend/questdb-driver": "1.7.32", + "@cubejs-backend/redshift-driver": "1.7.32", + "@cubejs-backend/server": "1.7.32", + "@cubejs-backend/snowflake-driver": "1.7.32", + "@cubejs-backend/sqlite-driver": "1.7.32", + "@cubejs-backend/trino-driver": "1.7.32", + "@cubejs-backend/vertica-driver": "1.7.32", + "cubejs-cli": "1.7.32", "typescript": "~5.2.2" }, "resolutions": { diff --git a/packages/cubejs-dremio-driver/CHANGELOG.md b/packages/cubejs-dremio-driver/CHANGELOG.md index 53130e6163187..1403b096ada41 100644 --- a/packages/cubejs-dremio-driver/CHANGELOG.md +++ b/packages/cubejs-dremio-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/dremio-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/dremio-driver diff --git a/packages/cubejs-dremio-driver/package.json b/packages/cubejs-dremio-driver/package.json index deb04aed67b8b..f69653e67b2a8 100644 --- a/packages/cubejs-dremio-driver/package.json +++ b/packages/cubejs-dremio-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/dremio-driver", "description": "Cube.js Dremio driver", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -23,14 +23,14 @@ "lint:fix": "eslint driver/*.js" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/schema-compiler": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/schema-compiler": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "axios": "^1.8.3" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.31", - "@cubejs-backend/testing-shared": "1.7.31", + "@cubejs-backend/linter": "1.7.32", + "@cubejs-backend/testing-shared": "1.7.32", "jest": "^29" }, "license": "Apache-2.0", diff --git a/packages/cubejs-druid-driver/CHANGELOG.md b/packages/cubejs-druid-driver/CHANGELOG.md index 7d51d93d20972..8fed29ddb1c0c 100644 --- a/packages/cubejs-druid-driver/CHANGELOG.md +++ b/packages/cubejs-druid-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/druid-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/druid-driver diff --git a/packages/cubejs-druid-driver/package.json b/packages/cubejs-druid-driver/package.json index bbbb5102de26a..c89d4e3f7e95b 100644 --- a/packages/cubejs-druid-driver/package.json +++ b/packages/cubejs-druid-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/druid-driver", "description": "Cube.js Druid database driver", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "license": "Apache-2.0", "repository": { "type": "git", @@ -28,13 +28,13 @@ "dist/src/*" ], "dependencies": { - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/schema-compiler": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/schema-compiler": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "axios": "^1.8.3" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.31", + "@cubejs-backend/linter": "1.7.32", "@types/jest": "^29", "@types/node": "^22", "jest": "^29", diff --git a/packages/cubejs-duckdb-driver/CHANGELOG.md b/packages/cubejs-duckdb-driver/CHANGELOG.md index bcca4273319ca..adf8fcc42d417 100644 --- a/packages/cubejs-duckdb-driver/CHANGELOG.md +++ b/packages/cubejs-duckdb-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/duckdb-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/duckdb-driver diff --git a/packages/cubejs-duckdb-driver/package.json b/packages/cubejs-duckdb-driver/package.json index 1b4ba37d1805f..05a1df4e35981 100644 --- a/packages/cubejs-duckdb-driver/package.json +++ b/packages/cubejs-duckdb-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/duckdb-driver", "description": "Cube DuckDB database driver", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -28,15 +28,15 @@ "lint:fix": "eslint --fix src/* --ext .ts" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/schema-compiler": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/schema-compiler": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "duckdb": "^1.4.1" }, "license": "Apache-2.0", "devDependencies": { - "@cubejs-backend/linter": "1.7.31", - "@cubejs-backend/testing-shared": "1.7.31", + "@cubejs-backend/linter": "1.7.32", + "@cubejs-backend/testing-shared": "1.7.32", "@types/jest": "^29", "@types/node": "^22", "jest": "^29", diff --git a/packages/cubejs-firebolt-driver/CHANGELOG.md b/packages/cubejs-firebolt-driver/CHANGELOG.md index ee8dc7a54c262..67ae8a552a79c 100644 --- a/packages/cubejs-firebolt-driver/CHANGELOG.md +++ b/packages/cubejs-firebolt-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/firebolt-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/firebolt-driver diff --git a/packages/cubejs-firebolt-driver/package.json b/packages/cubejs-firebolt-driver/package.json index 5c951d1773f50..d3ee6d501c5ef 100644 --- a/packages/cubejs-firebolt-driver/package.json +++ b/packages/cubejs-firebolt-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/firebolt-driver", "description": "Cube.js Firebolt database driver", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -28,15 +28,15 @@ "lint:fix": "eslint --fix src/* --ext .ts" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/schema-compiler": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/schema-compiler": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "firebolt-sdk": "1.10.0" }, "license": "Apache-2.0", "devDependencies": { - "@cubejs-backend/linter": "1.7.31", - "@cubejs-backend/testing-shared": "1.7.31", + "@cubejs-backend/linter": "1.7.32", + "@cubejs-backend/testing-shared": "1.7.32", "typescript": "~5.2.2" }, "publishConfig": { diff --git a/packages/cubejs-hive-driver/CHANGELOG.md b/packages/cubejs-hive-driver/CHANGELOG.md index 1307ba987e863..3e83304fdd41a 100644 --- a/packages/cubejs-hive-driver/CHANGELOG.md +++ b/packages/cubejs-hive-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/hive-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/hive-driver diff --git a/packages/cubejs-hive-driver/package.json b/packages/cubejs-hive-driver/package.json index 3b1427d5009ef..e84374787d63a 100644 --- a/packages/cubejs-hive-driver/package.json +++ b/packages/cubejs-hive-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/hive-driver", "description": "Cube.js Hive database driver", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -17,8 +17,8 @@ "lint:fix": "eslint --fix src/* --ext .ts" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "jshs2": "^0.4.4", "sasl-plain": "^0.1.0", "saslmechanisms": "^0.1.1", @@ -27,7 +27,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@cubejs-backend/linter": "1.7.31" + "@cubejs-backend/linter": "1.7.32" }, "publishConfig": { "access": "public" diff --git a/packages/cubejs-jdbc-driver/CHANGELOG.md b/packages/cubejs-jdbc-driver/CHANGELOG.md index 35632e8b27775..bf0fe781418bc 100644 --- a/packages/cubejs-jdbc-driver/CHANGELOG.md +++ b/packages/cubejs-jdbc-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/jdbc-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/jdbc-driver diff --git a/packages/cubejs-jdbc-driver/package.json b/packages/cubejs-jdbc-driver/package.json index e8215f27f1314..c56117219b56d 100644 --- a/packages/cubejs-jdbc-driver/package.json +++ b/packages/cubejs-jdbc-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/jdbc-driver", "description": "Cube.js JDBC database driver", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -26,9 +26,9 @@ "index.js" ], "dependencies": { - "@cubejs-backend/base-driver": "1.7.31", + "@cubejs-backend/base-driver": "1.7.32", "@cubejs-backend/node-java-maven": "^0.1.3", - "@cubejs-backend/shared": "1.7.31" + "@cubejs-backend/shared": "1.7.32" }, "optionalDependencies": { "@cubejs-backend/jdbc": "^0.9.0", @@ -42,7 +42,7 @@ "testEnvironment": "node" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.31", + "@cubejs-backend/linter": "1.7.32", "@types/node": "^22", "typescript": "~5.2.2" } diff --git a/packages/cubejs-ksql-driver/CHANGELOG.md b/packages/cubejs-ksql-driver/CHANGELOG.md index dfcd969cb1c0c..b6784c7324b9e 100644 --- a/packages/cubejs-ksql-driver/CHANGELOG.md +++ b/packages/cubejs-ksql-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/ksql-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/ksql-driver diff --git a/packages/cubejs-ksql-driver/package.json b/packages/cubejs-ksql-driver/package.json index 7b1271d0a177c..395eafc5f5873 100644 --- a/packages/cubejs-ksql-driver/package.json +++ b/packages/cubejs-ksql-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/ksql-driver", "description": "Cube.js ksql database driver", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -26,9 +26,9 @@ "lint:fix": "eslint --fix src/* --ext .ts" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/schema-compiler": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/schema-compiler": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "async-mutex": "0.3.2", "axios": "^1.8.3", "kafkajs": "^2.2.3" @@ -41,7 +41,7 @@ "extends": "../cubejs-linter" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.31", + "@cubejs-backend/linter": "1.7.32", "typescript": "~5.2.2" } } diff --git a/packages/cubejs-linter/CHANGELOG.md b/packages/cubejs-linter/CHANGELOG.md index 16ab6611ab9ae..293ec107135eb 100644 --- a/packages/cubejs-linter/CHANGELOG.md +++ b/packages/cubejs-linter/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/linter + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/linter diff --git a/packages/cubejs-linter/package.json b/packages/cubejs-linter/package.json index 179331188571f..661e839fc151b 100644 --- a/packages/cubejs-linter/package.json +++ b/packages/cubejs-linter/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/linter", "description": "Cube.js ESLint (virtual package) for linting code", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", diff --git a/packages/cubejs-materialize-driver/CHANGELOG.md b/packages/cubejs-materialize-driver/CHANGELOG.md index 5e8a70d6685fa..94cb1ee2fbaca 100644 --- a/packages/cubejs-materialize-driver/CHANGELOG.md +++ b/packages/cubejs-materialize-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/materialize-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/materialize-driver diff --git a/packages/cubejs-materialize-driver/package.json b/packages/cubejs-materialize-driver/package.json index ccbcc7024e685..69d123b93e04a 100644 --- a/packages/cubejs-materialize-driver/package.json +++ b/packages/cubejs-materialize-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/materialize-driver", "description": "Cube.js Materialize database driver", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -27,15 +27,15 @@ "lint:fix": "eslint --fix src/* --ext .ts" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/postgres-driver": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/postgres-driver": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "semver": "^7.6.3" }, "license": "Apache-2.0", "devDependencies": { - "@cubejs-backend/linter": "1.7.31", - "@cubejs-backend/testing": "1.7.31", + "@cubejs-backend/linter": "1.7.32", + "@cubejs-backend/testing": "1.7.32", "typescript": "~5.2.2" }, "publishConfig": { diff --git a/packages/cubejs-mongobi-driver/CHANGELOG.md b/packages/cubejs-mongobi-driver/CHANGELOG.md index ac8b5e647e220..054351d937b3f 100644 --- a/packages/cubejs-mongobi-driver/CHANGELOG.md +++ b/packages/cubejs-mongobi-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/mongobi-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/mongobi-driver diff --git a/packages/cubejs-mongobi-driver/package.json b/packages/cubejs-mongobi-driver/package.json index 5f40fdf163ba1..b2a8470a1db37 100644 --- a/packages/cubejs-mongobi-driver/package.json +++ b/packages/cubejs-mongobi-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/mongobi-driver", "description": "Cube.js MongoBI driver", "author": "krunalsabnis@gmail.com", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -27,8 +27,8 @@ "integration:mongobi": "jest dist/test" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "@types/node": "^22", "moment": "^2.29.1", "mysql2": "^3.11.5" @@ -38,7 +38,7 @@ "access": "public" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.31", + "@cubejs-backend/linter": "1.7.32", "testcontainers": "^10.28.0", "typescript": "~5.2.2" }, diff --git a/packages/cubejs-mssql-driver/CHANGELOG.md b/packages/cubejs-mssql-driver/CHANGELOG.md index 41304f729ef6d..8437599606d61 100644 --- a/packages/cubejs-mssql-driver/CHANGELOG.md +++ b/packages/cubejs-mssql-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/mssql-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/mssql-driver diff --git a/packages/cubejs-mssql-driver/package.json b/packages/cubejs-mssql-driver/package.json index 89b554c2935ec..01717fe23b9e9 100644 --- a/packages/cubejs-mssql-driver/package.json +++ b/packages/cubejs-mssql-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/mssql-driver", "description": "Cube.js MS SQL database driver", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -25,8 +25,8 @@ "lint:fix": "eslint --fix src/* --ext .ts,.js" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "mssql": "^11.0.1" }, "devDependencies": { diff --git a/packages/cubejs-mysql-aurora-serverless-driver/CHANGELOG.md b/packages/cubejs-mysql-aurora-serverless-driver/CHANGELOG.md index 9c74a1df3351d..c494c672d73d3 100644 --- a/packages/cubejs-mysql-aurora-serverless-driver/CHANGELOG.md +++ b/packages/cubejs-mysql-aurora-serverless-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/mysql-aurora-serverless-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/mysql-aurora-serverless-driver diff --git a/packages/cubejs-mysql-aurora-serverless-driver/package.json b/packages/cubejs-mysql-aurora-serverless-driver/package.json index 5432b76bb31d5..c3bc7bf31e16c 100644 --- a/packages/cubejs-mysql-aurora-serverless-driver/package.json +++ b/packages/cubejs-mysql-aurora-serverless-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/mysql-aurora-serverless-driver", "description": "Cube.js Aurora Serverless Mysql database driver", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -21,14 +21,14 @@ "lint": "eslint driver/*.js test/*.js" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "@types/mysql": "^2.15.15", "aws-sdk": "^2.787.0", "data-api-client": "^1.1.0" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.31", + "@cubejs-backend/linter": "1.7.32", "@types/data-api-client": "^1.2.1", "@types/jest": "^29", "jest": "^29", diff --git a/packages/cubejs-mysql-driver/CHANGELOG.md b/packages/cubejs-mysql-driver/CHANGELOG.md index a0eb094bd71db..7d86b46ac13ef 100644 --- a/packages/cubejs-mysql-driver/CHANGELOG.md +++ b/packages/cubejs-mysql-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/mysql-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/mysql-driver diff --git a/packages/cubejs-mysql-driver/package.json b/packages/cubejs-mysql-driver/package.json index 9b5a3d6c6b4af..e3df4e6595780 100644 --- a/packages/cubejs-mysql-driver/package.json +++ b/packages/cubejs-mysql-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/mysql-driver", "description": "Cube.js Mysql database driver", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -27,13 +27,13 @@ "lint:fix": "eslint --fix src/* test/* --ext .ts,.js" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "mysql2": "^3.16.1" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.31", - "@cubejs-backend/testing-shared": "1.7.31", + "@cubejs-backend/linter": "1.7.32", + "@cubejs-backend/testing-shared": "1.7.32", "@types/jest": "^29", "jest": "^29", "stream-to-array": "^2.3.0", diff --git a/packages/cubejs-oracle-driver/CHANGELOG.md b/packages/cubejs-oracle-driver/CHANGELOG.md index 3f123d52a9caa..6ba4f8f2970a0 100644 --- a/packages/cubejs-oracle-driver/CHANGELOG.md +++ b/packages/cubejs-oracle-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/oracle-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/oracle-driver diff --git a/packages/cubejs-oracle-driver/package.json b/packages/cubejs-oracle-driver/package.json index 28e17085cc236..f03ddb377c358 100644 --- a/packages/cubejs-oracle-driver/package.json +++ b/packages/cubejs-oracle-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/oracle-driver", "description": "Cube.js oracle database driver", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -13,8 +13,8 @@ }, "main": "driver/OracleDriver.js", "dependencies": { - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "ramda": "^0.27.0" }, "devDependencies": { diff --git a/packages/cubejs-pinot-driver/CHANGELOG.md b/packages/cubejs-pinot-driver/CHANGELOG.md index e6b58888f5a23..9de614e746761 100644 --- a/packages/cubejs-pinot-driver/CHANGELOG.md +++ b/packages/cubejs-pinot-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/pinot-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/pinot-driver diff --git a/packages/cubejs-pinot-driver/package.json b/packages/cubejs-pinot-driver/package.json index e07bae208368f..6fd7de42a124b 100644 --- a/packages/cubejs-pinot-driver/package.json +++ b/packages/cubejs-pinot-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/pinot-driver", "description": "Cube.js Pinot database driver", "author": "Julian Ronsse, InTheMemory, Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -28,9 +28,9 @@ "lint:fix": "eslint --fix src/* --ext .ts" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/schema-compiler": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/schema-compiler": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "node-fetch": "^2.6.1", "ramda": "^0.27.2" }, @@ -39,7 +39,7 @@ "access": "public" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.31", + "@cubejs-backend/linter": "1.7.32", "@types/jest": "^29", "jest": "^29", "should": "^13.2.3", diff --git a/packages/cubejs-playground/CHANGELOG.md b/packages/cubejs-playground/CHANGELOG.md index e77044c92eb3c..89be0859cb8ee 100644 --- a/packages/cubejs-playground/CHANGELOG.md +++ b/packages/cubejs-playground/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-client/playground + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-client/playground diff --git a/packages/cubejs-playground/package.json b/packages/cubejs-playground/package.json index 1a07bf5a56b5a..7d7db8bacfbb3 100644 --- a/packages/cubejs-playground/package.json +++ b/packages/cubejs-playground/package.json @@ -1,7 +1,7 @@ { "name": "@cubejs-client/playground", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "engines": {}, "repository": { "type": "git", @@ -68,8 +68,8 @@ "@ant-design/compatible": "^1.0.1", "@ant-design/icons": "^5.3.5", "@cube-dev/ui-kit": "0.52.3", - "@cubejs-client/core": "1.7.31", - "@cubejs-client/react": "1.7.31", + "@cubejs-client/core": "1.7.32", + "@cubejs-client/react": "1.7.32", "@types/flexsearch": "^0.7.3", "@types/node": "^22", "@types/react": "^18.3.4", diff --git a/packages/cubejs-postgres-driver/CHANGELOG.md b/packages/cubejs-postgres-driver/CHANGELOG.md index 0c17972f03f3e..7794f4a2eb158 100644 --- a/packages/cubejs-postgres-driver/CHANGELOG.md +++ b/packages/cubejs-postgres-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/postgres-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/postgres-driver diff --git a/packages/cubejs-postgres-driver/package.json b/packages/cubejs-postgres-driver/package.json index 04dca7c34c292..8994d295895fc 100644 --- a/packages/cubejs-postgres-driver/package.json +++ b/packages/cubejs-postgres-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/postgres-driver", "description": "Cube.js Postgres database driver", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -27,8 +27,8 @@ "lint:fix": "eslint --fix src/* --ext .ts" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "@types/pg": "^8.16.0", "@types/pg-query-stream": "^1.0.3", "pg": "^8.18.0", @@ -36,8 +36,8 @@ }, "license": "Apache-2.0", "devDependencies": { - "@cubejs-backend/linter": "1.7.31", - "@cubejs-backend/testing-shared": "1.7.31", + "@cubejs-backend/linter": "1.7.32", + "@cubejs-backend/testing-shared": "1.7.32", "testcontainers": "^10.28.0", "typescript": "~5.2.2" }, diff --git a/packages/cubejs-prestodb-driver/CHANGELOG.md b/packages/cubejs-prestodb-driver/CHANGELOG.md index cf00ac153c875..a60cef1a77f60 100644 --- a/packages/cubejs-prestodb-driver/CHANGELOG.md +++ b/packages/cubejs-prestodb-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/prestodb-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/prestodb-driver diff --git a/packages/cubejs-prestodb-driver/package.json b/packages/cubejs-prestodb-driver/package.json index d65fb71cc1e81..df0e296c9ec42 100644 --- a/packages/cubejs-prestodb-driver/package.json +++ b/packages/cubejs-prestodb-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/prestodb-driver", "description": "Cube.js Presto database driver", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -28,8 +28,8 @@ "lint:fix": "eslint --fix src/* --ext .ts" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "presto-client": "1.2.0", "ramda": "^0.27.0" }, @@ -38,7 +38,7 @@ "access": "public" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.31", + "@cubejs-backend/linter": "1.7.32", "@types/jest": "^29", "jest": "^29", "should": "^13.2.3", diff --git a/packages/cubejs-query-orchestrator/CHANGELOG.md b/packages/cubejs-query-orchestrator/CHANGELOG.md index fd3148d697652..2bd577f239d49 100644 --- a/packages/cubejs-query-orchestrator/CHANGELOG.md +++ b/packages/cubejs-query-orchestrator/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +### Bug Fixes + +- **query-orchestrator:** keep Interactive priority on user query paths ([#11715](https://github.com/cube-js/cube/issues/11715)) ([39d3b20](https://github.com/cube-js/cube/commit/39d3b20d114d6003562e0cdad0e98389056bb3a5)) + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) ### Bug Fixes diff --git a/packages/cubejs-query-orchestrator/package.json b/packages/cubejs-query-orchestrator/package.json index 801d6ab50fbef..d37bcacaa2e2d 100644 --- a/packages/cubejs-query-orchestrator/package.json +++ b/packages/cubejs-query-orchestrator/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/query-orchestrator", "description": "Cube.js Query Orchestrator and Cache", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -30,15 +30,15 @@ "dist/src/*" ], "dependencies": { - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/cubestore-driver": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/cubestore-driver": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "csv-write-stream": "^2.0.0", "lru-cache": "^11.1.0", "ramda": "^0.27.2" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.31", + "@cubejs-backend/linter": "1.7.32", "@types/jest": "^29", "@types/node": "^22", "@types/ramda": "^0.27.32", diff --git a/packages/cubejs-query-orchestrator/src/orchestrator/PreAggregationPartitionRangeLoader.ts b/packages/cubejs-query-orchestrator/src/orchestrator/PreAggregationPartitionRangeLoader.ts index a19e1924f9220..33b3183ede8c8 100644 --- a/packages/cubejs-query-orchestrator/src/orchestrator/PreAggregationPartitionRangeLoader.ts +++ b/packages/cubejs-query-orchestrator/src/orchestrator/PreAggregationPartitionRangeLoader.ts @@ -393,6 +393,7 @@ export class PreAggregationPartitionRangeLoader { { requestId: this.requestId, skipRefreshKeyWaitForRenew: false, + priority: this.priority(QueuePriority.Interactive), dataSource: this.dataSource, external: false, useCsvQuery: true, diff --git a/packages/cubejs-query-orchestrator/src/orchestrator/QueryCache.ts b/packages/cubejs-query-orchestrator/src/orchestrator/QueryCache.ts index c0d4d726e1bc6..09423af332ab5 100644 --- a/packages/cubejs-query-orchestrator/src/orchestrator/QueryCache.ts +++ b/packages/cubejs-query-orchestrator/src/orchestrator/QueryCache.ts @@ -86,6 +86,8 @@ export type QueryWithParams = [ export type LoadRefreshKeyOptions = { requestId?: string; skipRefreshKeyWaitForRenew?: boolean; + /** Inherited from the query the keys are refreshed for: a blocked request waits on them too */ + priority?: number; dataSource: string }; @@ -340,6 +342,7 @@ export class QueryCache { values, { cacheKey: [query, values], + priority: queuePriority, external: queryBody.external, requestId: queryBody.requestId, dataSource: queryBody.dataSource, @@ -362,6 +365,7 @@ export class QueryCache { renewalThreshold, { forceNoCache, + priority: queuePriority, external: queryBody.external, requestId: queryBody.requestId, dataSource: queryBody.dataSource, @@ -381,6 +385,7 @@ export class QueryCache { renewalThreshold, { forceNoCache, + priority: queuePriority, external: queryBody.external, requestId: queryBody.requestId, dataSource: queryBody.dataSource, @@ -391,6 +396,8 @@ export class QueryCache { // Keep the cycle after the foreground renewal: concurrent passes race on a cold cache. // It remains necessary when skipRefreshKeyWaitForRenew serves a stale key from a warm cache. + // It re-runs the same query at Background while the renewal above ran at the request's own + // priority, because the request is no longer blocked on the result by the time it fires. this.startRenewCycle( query, values, @@ -921,6 +928,7 @@ export class QueryCache { options: { requestId?: string, skipRefreshKeyWaitForRenew?: boolean, + priority?: number, external?: boolean, forceNoCache?: boolean, dataSource: string, @@ -957,6 +965,7 @@ export class QueryCache { ], waitForRenew: true, forceNoCache: options.forceNoCache, + priority: options.priority, external: options.external, requestId: options.requestId, dataSource: options.dataSource, @@ -1001,6 +1010,7 @@ export class QueryCache { expireSecs, { waitForRenew: !options.skipRefreshKeyWaitForRenew, + priority: options.priority, requestId: options.requestId, dataSource: options.dataSource, }, diff --git a/packages/cubejs-query-orchestrator/test/unit/QueryCache.abstract.ts b/packages/cubejs-query-orchestrator/test/unit/QueryCache.abstract.ts index d68d43a91ac2a..ea81b80dc185e 100644 --- a/packages/cubejs-query-orchestrator/test/unit/QueryCache.abstract.ts +++ b/packages/cubejs-query-orchestrator/test/unit/QueryCache.abstract.ts @@ -1,5 +1,6 @@ import crypto from 'crypto'; -import { createCancelablePromise, pausePromise } from '@cubejs-backend/shared'; +import { CacheMode, createCancelablePromise, pausePromise } from '@cubejs-backend/shared'; +import { QueuePriority } from '@cubejs-backend/base-driver'; import { CacheKey, CacheKeyItem, ContinueWaitError, QueryCache, QueryCacheOptions } from '../../src'; @@ -468,6 +469,90 @@ export const QueryCacheTest = (name: string, options: QueryCacheTestOptions) => renewQuerySpy.mockRestore(); } }); + + // The queue fast track only engages at `QueuePriority.Interactive`, so a request-blocked + // query that loses its priority on the way down silently falls back to the slow path. + it.each<{ type: string, cacheMode?: CacheMode, queuePriority?: number, expected: number }>([ + { type: 'the default', cacheMode: undefined, queuePriority: undefined, expected: QueuePriority.Interactive }, + { type: 'an explicit queuePriority', cacheMode: undefined, queuePriority: 42, expected: 42 }, + { type: 'must-revalidate', cacheMode: 'must-revalidate', queuePriority: undefined, expected: QueuePriority.Interactive }, + ])('submits the main query and its refresh key with $type priority', async ({ cacheMode, queuePriority, expected }) => { + const suffix = crypto.randomBytes(8).toString('hex'); + const mainQuery = `SELECT priority-main-${suffix}`; + const cacheKeyQuery = `SELECT priority-refresh-key-${suffix}`; + + const querySpy = jest.spyOn(cache, 'queryWithRetryAndRelease').mockImplementation(async (query) => { + if (query === mainQuery) { + return [{ result: 'ok' }]; + } + + return [{ refresh_key: suffix }]; + }); + const renewCycleSpy = jest.spyOn(cache, 'startRenewCycle').mockImplementation(() => undefined); + + try { + await cache.cachedQueryResult( + { + query: mainQuery, + values: [], + cacheMode, + queuePriority, + cacheKeyQueries: [[cacheKeyQuery, []]], + requestId: `priority-req-${suffix}`, + dataSource: 'default', + }, + [], + ); + + const priorityOf = (targetQuery: string) => querySpy.mock.calls + .filter(([query]) => query === targetQuery) + .map(([, , queryOptions]) => queryOptions.priority); + + expect(priorityOf(mainQuery)).toEqual([expected]); + expect(priorityOf(cacheKeyQuery)).toEqual([expected]); + } finally { + renewCycleSpy.mockRestore(); + querySpy.mockRestore(); + } + }); + + // The branch that bypasses the cache: `cacheKeyQueriesFrom` always returns an array, so + // an empty `cacheKeyQueries` still renews — only these two query shapes reach it. + it.each([ + { type: 'an external query that skips the cache and queue', queryBody: { external: true } }, + { type: 'a persistent query', queryBody: { persistent: true } }, + ])('submits $type with Interactive priority', async ({ queryBody }) => { + const localCache = new QueryCacheOpened( + crypto.randomBytes(16).toString('hex'), + () => { + throw new Error('driverFactory is not implemented, mock should be used...'); + }, + jest.fn(), + { ...options, skipExternalCacheAndQueue: true }, + ); + const querySpy = jest.spyOn(localCache, 'queryWithRetryAndRelease') + .mockImplementation(async () => [{ result: 'ok' }]); + + try { + await localCache.cachedQueryResult( + { + ...queryBody, + query: 'SELECT skip-cache-main', + values: [], + cacheKeyQueries: [], + requestId: 'skip-cache-req', + dataSource: 'default', + }, + [], + ); + + expect(querySpy.mock.calls.map(([, , queryOptions]) => queryOptions.priority)) + .toEqual([QueuePriority.Interactive]); + } finally { + querySpy.mockRestore(); + await localCache.cleanup(); + } + }); }); describe('local refresh key', () => { diff --git a/packages/cubejs-questdb-driver/CHANGELOG.md b/packages/cubejs-questdb-driver/CHANGELOG.md index b80ef909d9c8b..a9cff656c2615 100644 --- a/packages/cubejs-questdb-driver/CHANGELOG.md +++ b/packages/cubejs-questdb-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/questdb-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/questdb-driver diff --git a/packages/cubejs-questdb-driver/package.json b/packages/cubejs-questdb-driver/package.json index 10aa65c14cafa..99761209cb379 100644 --- a/packages/cubejs-questdb-driver/package.json +++ b/packages/cubejs-questdb-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/questdb-driver", "description": "Cube.js QuestDB database driver", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -27,9 +27,9 @@ "lint:fix": "eslint --fix src/* --ext .ts" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/schema-compiler": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/schema-compiler": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "@types/pg": "^8.6.0", "moment": "^2.24.0", "pg": "^8.7.0", @@ -37,8 +37,8 @@ }, "license": "Apache-2.0", "devDependencies": { - "@cubejs-backend/linter": "1.7.31", - "@cubejs-backend/testing-shared": "1.7.31", + "@cubejs-backend/linter": "1.7.32", + "@cubejs-backend/testing-shared": "1.7.32", "testcontainers": "^10.28.0", "typescript": "~5.2.2" }, diff --git a/packages/cubejs-redshift-driver/CHANGELOG.md b/packages/cubejs-redshift-driver/CHANGELOG.md index 137099af75d9f..d7e0ed5d183e9 100644 --- a/packages/cubejs-redshift-driver/CHANGELOG.md +++ b/packages/cubejs-redshift-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/redshift-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/redshift-driver diff --git a/packages/cubejs-redshift-driver/package.json b/packages/cubejs-redshift-driver/package.json index 4af1d9ce2069c..abe459f4bb94c 100644 --- a/packages/cubejs-redshift-driver/package.json +++ b/packages/cubejs-redshift-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/redshift-driver", "description": "Cube.js Redshift database driver", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -27,13 +27,13 @@ "dependencies": { "@aws-sdk/client-redshift": "^3.22.0", "@aws-sdk/credential-providers": "^3.22.0", - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/postgres-driver": "1.7.31", - "@cubejs-backend/shared": "1.7.31" + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/postgres-driver": "1.7.32", + "@cubejs-backend/shared": "1.7.32" }, "license": "Apache-2.0", "devDependencies": { - "@cubejs-backend/linter": "1.7.31", + "@cubejs-backend/linter": "1.7.32", "typescript": "~5.2.2" }, "publishConfig": { diff --git a/packages/cubejs-schema-compiler/CHANGELOG.md b/packages/cubejs-schema-compiler/CHANGELOG.md index 9184e9c967a2c..78f099e7de2b3 100644 --- a/packages/cubejs-schema-compiler/CHANGELOG.md +++ b/packages/cubejs-schema-compiler/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +### Bug Fixes + +- **tesseract:** calendar sql granularities crash every query; to_date ignores the calendar ([#11709](https://github.com/cube-js/cube/issues/11709)) ([4029495](https://github.com/cube-js/cube/commit/4029495feb0cadfee20c56f565d94430852f5b27)) +- **tesseract:** compose grain.include with rolling_window ([#11639](https://github.com/cube-js/cube/issues/11639)) ([9d3dd45](https://github.com/cube-js/cube/commit/9d3dd45814a7fec41b6c4e23233f38bd7a1af1c2)) +- **tesseract:** convert a view's raw time dimension timezone only once ([#11712](https://github.com/cube-js/cube/issues/11712)) ([1ae6452](https://github.com/cube-js/cube/commit/1ae6452a139d227935e3b68d9c8cc9283d982d3d)) + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) ### Bug Fixes diff --git a/packages/cubejs-schema-compiler/package.json b/packages/cubejs-schema-compiler/package.json index 9044322bad769..8cb20eeb6dffa 100644 --- a/packages/cubejs-schema-compiler/package.json +++ b/packages/cubejs-schema-compiler/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/schema-compiler", "description": "Cube schema compiler", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -40,8 +40,8 @@ "@babel/standalone": "^7.24", "@babel/traverse": "^7.24", "@babel/types": "^7.24", - "@cubejs-backend/native": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/native": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "antlr4": "^4.13.2", "camelcase": "^6.2.0", "cron-parser": "^4.9.0", @@ -60,9 +60,9 @@ }, "devDependencies": { "@clickhouse/client": "^1.12.0", - "@cubejs-backend/linter": "1.7.31", - "@cubejs-backend/mssql-driver": "1.7.31", - "@cubejs-backend/query-orchestrator": "1.7.31", + "@cubejs-backend/linter": "1.7.32", + "@cubejs-backend/mssql-driver": "1.7.32", + "@cubejs-backend/query-orchestrator": "1.7.32", "@types/babel__code-frame": "^7.0.6", "@types/babel__generator": "^7.6.8", "@types/babel__traverse": "^7.20.5", diff --git a/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js b/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js index 5f478fbaf9b11..171e30f3457f2 100644 --- a/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js +++ b/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js @@ -962,8 +962,10 @@ export class BaseQuery { securityContext: this.contextSymbols.securityContext, order, filters: this.options.filters, - limit: this.options.limit ? this.options.limit.toString() : null, - rowLimit: this.options.rowLimit ? this.options.rowLimit.toString() : null, + limit: this.options.limit != null ? this.options.limit.toString() : null, + // `rowLimit: 0` is a valid limit (BI tools use `LIMIT 0` as a schema probe), + // so it must not be collapsed into `null` (no limit) here + rowLimit: this.options.rowLimit != null ? this.options.rowLimit.toString() : null, offset: this.options.offset ? this.options.offset.toString() : null, baseTools: this, ungrouped: this.options.ungrouped, @@ -1019,8 +1021,10 @@ export class BaseQuery { cubeEvaluator: this.cubeEvaluator, order, filters: this.options.filters, - limit: this.options.limit ? this.options.limit.toString() : null, - rowLimit: this.options.rowLimit ? this.options.rowLimit.toString() : null, + limit: this.options.limit != null ? this.options.limit.toString() : null, + // `rowLimit: 0` is a valid limit (BI tools use `LIMIT 0` as a schema probe), + // so it must not be collapsed into `null` (no limit) here + rowLimit: this.options.rowLimit != null ? this.options.rowLimit.toString() : null, offset: this.options.offset ? this.options.offset.toString() : null, baseTools: this, ungrouped: this.options.ungrouped, @@ -1917,11 +1921,21 @@ export class BaseQuery { // If we have custom granularities in time dimension if (td.granularities) { for (const granularityName of Object.keys(td.granularities)) { - const grObj = new Granularity(this, { dimension: dimensionKey, granularity: granularityName }); - hierarchies[`${dimensionKey}.${granularityName}`] = [ - granularityName, - ...standardGranularitiesParents[grObj.minGranularity()], - ]; + const granularity = this.cubeEvaluator.resolveGranularity([cube, tdName, 'granularities', granularityName]); + + // A granularity that only overrides the SQL of its time dimension has no + // interval to derive a hierarchy from. Such a granularity can only be + // matched by a rollup declaring it by name, which is what a missing + // hierarchy entry already means. + // An unresolvable granularity is a different matter and keeps + // reporting itself from the constructor below. + if (!granularity || granularity.interval) { + const grObj = new Granularity(this, { dimension: dimensionKey, granularity: granularityName }); + hierarchies[`${dimensionKey}.${granularityName}`] = [ + granularityName, + ...standardGranularitiesParents[grObj.minGranularity()], + ]; + } } } } @@ -3216,6 +3230,33 @@ export class BaseQuery { return ''; } + /** + * Row limit as a number, or `null` when it is not set at all. Unlike a truthy check + * this keeps `0` (a valid limit that returns no rows) distinct from "no limit", and + * unlike a bare `parseInt` it keeps a non-numeric `rowLimit` out of the rendered SQL. + * @protected + * @returns {number|null} + */ + parsedRowLimit() { + if (this.rowLimit == null) { + return null; + } + const parsed = parseInt(this.rowLimit, 10); + return Number.isNaN(parsed) ? null : parsed; + } + + /** + * Leading row-limit clause for statements that do not render `topLimit()` -- the legacy + * rollup query in `PreAggregations` is the one such statement. Only dialects that cannot + * express a zero row limit as a trailing clause (T-SQL, where FETCH NEXT must be >= 1) + * return anything here; every other dialect renders `LIMIT 0` and gets `''`. + * @public + * @returns {string} + */ + zeroRowLimitTopClause() { + return ''; + } + baseSelect() { return R.flatten(this.forSelect().map(s => s.selectColumns())).filter(s => !!s).join(', '); } diff --git a/packages/cubejs-schema-compiler/src/adapter/Granularity.ts b/packages/cubejs-schema-compiler/src/adapter/Granularity.ts index 4e7d452fd6d99..98b0a75b96d71 100644 --- a/packages/cubejs-schema-compiler/src/adapter/Granularity.ts +++ b/packages/cubejs-schema-compiler/src/adapter/Granularity.ts @@ -48,6 +48,13 @@ export class Granularity { throw new UserError(`Granularity "${timeDimension.granularity}" does not exist in dimension ${timeDimension.dimension}`); } + if (!customGranularity.interval) { + const cause = customGranularity.sql + ? 'is defined with \'sql\', which is only supported for predefined granularities' + : 'has no interval'; + throw new UserError(`Granularity "${this.granularity}" of dimension ${timeDimension.dimension} ${cause}`); + } + this.granularityInterval = customGranularity.interval; if (customGranularity.origin) { diff --git a/packages/cubejs-schema-compiler/src/adapter/MssqlQuery.ts b/packages/cubejs-schema-compiler/src/adapter/MssqlQuery.ts index fb3481b23c80c..0f9503df9d391 100644 --- a/packages/cubejs-schema-compiler/src/adapter/MssqlQuery.ts +++ b/packages/cubejs-schema-compiler/src/adapter/MssqlQuery.ts @@ -152,6 +152,11 @@ export class MssqlQuery extends BaseQuery { // TODO replace with limitOffsetClause override public groupByDimensionLimit() { + // T-SQL requires FETCH NEXT to be greater than zero, so a zero row limit is + // rendered as `TOP 0` by topLimit() instead, and OFFSET is redundant for it + if (this.parsedRowLimit() === 0) { + return ''; + } if (this.rowLimit) { return this.offset ? ` OFFSET ${parseInt(this.offset, 10)} ROWS FETCH NEXT ${parseInt(this.rowLimit, 10)} ROWS ONLY` : ''; } else { @@ -160,10 +165,32 @@ export class MssqlQuery extends BaseQuery { } public topLimit() { + // Deliberately a strict null check: an explicit `rowLimit: null` means "no limit", + // while an absent one keeps the historical TOP 10000 default below, since T-SQL has + // no LIMIT clause to fall back on + if (this.rowLimit === null) { + return ''; + } + const rowLimit = this.parsedRowLimit(); + // `TOP 0` is the only way to express an empty result in T-SQL, and it takes + // precedence over the offset branch below: OFFSET without FETCH would return rows + if (rowLimit === 0) { + return ' TOP 0'; + } if (this.offset) { return ''; } - return this.rowLimit === null ? '' : ` TOP ${this.rowLimit && parseInt(this.rowLimit, 10) || 10000}`; + return ` TOP ${rowLimit ?? 10000}`; + } + + /** + * The legacy rollup query in `PreAggregations` renders no `topLimit()`, so a zero row + * limit would otherwise emit no row-limiting clause there at all (groupByDimensionLimit() + * cannot express it: FETCH NEXT must be >= 1 in T-SQL) and scan the whole rollup. + * @override + */ + public zeroRowLimitTopClause() { + return this.parsedRowLimit() === 0 ? ' TOP 0' : ''; } /** @@ -348,7 +375,9 @@ export class MssqlQuery extends BaseQuery { templates.statements.select = '{% if ctes %} WITH \n' + '{{ ctes | join(\',\n\') }}\n' + '{% endif %}' + - 'SELECT {% if limit is not none and not order_by %}TOP {{ limit }} {% endif %}{% if distinct %}DISTINCT {% endif %}' + + // T-SQL clause order is SELECT [ALL | DISTINCT] [TOP (expr)], so DISTINCT has to come + // first: `SELECT TOP 0 DISTINCT ...` is a syntax error + 'SELECT {% if distinct %}DISTINCT {% endif %}{% if limit is not none and (not order_by or limit == 0) %}TOP {{ limit }} {% endif %}' + '{{ select_concat | map(attribute=\'aliased\') | join(\', \') }} {% if from %}\n' + 'FROM (\n' + '{{ from | indent(2, true) }}\n' + @@ -358,8 +387,13 @@ export class MssqlQuery extends BaseQuery { '{% if filter %}\nWHERE {{ filter }}{% endif %}' + '{% if group_by %}\nGROUP BY {{ group_by }}{% endif %}' + '{% if having %}\nHAVING {{ having }}{% endif %}' + - '{% if order_by %}\nORDER BY {{ order_by | map(attribute=\'expr\') | join(\', \') }}\nOFFSET {% if offset is not none %}{{ offset }}{% else %}0{% endif %} ROWS' + - '\nFETCH NEXT {% if limit is not none %}{{ limit }}{% else %}2147483647{% endif %} ROWS ONLY{% endif %}' + + '{% if order_by %}\nORDER BY {{ order_by | map(attribute=\'expr\') | join(\', \') }}' + + // FETCH NEXT must be greater than zero in T-SQL, so `LIMIT 0` is rendered as + // `TOP 0` above and the OFFSET/FETCH tail is dropped entirely. `limit` is always a + // number here (both renderers pass Option); `limit | int` would not work as a + // guard, since `none | int` is 0 and that would drop the 2147483647 fallback below + '{% if limit != 0 %}\nOFFSET {% if offset is not none %}{{ offset }}{% else %}0{% endif %} ROWS' + + '\nFETCH NEXT {% if limit is not none %}{{ limit }}{% else %}2147483647{% endif %} ROWS ONLY{% endif %}{% endif %}' + '{% if ctes %}\nOPTION (MAXRECURSION 0){% endif %}'; // T-SQL has no LIMIT, and neither TOP nor OFFSET/FETCH can be attached to a set // operation directly (OFFSET/FETCH also requires an ORDER BY), so a bounded set diff --git a/packages/cubejs-schema-compiler/src/adapter/OracleQuery.ts b/packages/cubejs-schema-compiler/src/adapter/OracleQuery.ts index 2f8dd5dde8e97..f21ab49bf8583 100644 --- a/packages/cubejs-schema-compiler/src/adapter/OracleQuery.ts +++ b/packages/cubejs-schema-compiler/src/adapter/OracleQuery.ts @@ -36,7 +36,11 @@ export class OracleQuery extends BaseQuery { * TODO replace with limitOffsetClause override */ public groupByDimensionLimit() { - const limitClause = this.rowLimit === null ? '' : ` FETCH NEXT ${this.rowLimit && parseInt(this.rowLimit, 10) || 10000} ROWS ONLY`; + // `rowLimit: 0` is a valid limit that returns no rows, so it must not fall back to the + // default below the way a truthy check would. Same null policy as MssqlQuery#topLimit: + // an explicit `rowLimit: null` means "no limit", an absent one keeps the 10000 default + const rowLimit = this.parsedRowLimit() ?? 10000; + const limitClause = this.rowLimit === null ? '' : ` FETCH NEXT ${rowLimit} ROWS ONLY`; const offsetClause = this.offset ? ` OFFSET ${parseInt(this.offset, 10)} ROWS` : ''; return `${offsetClause}${limitClause}`; } diff --git a/packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts b/packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts index 5fa4174ebbb44..212032f98b1df 100644 --- a/packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts +++ b/packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts @@ -1624,8 +1624,10 @@ export class PreAggregations { return this.query.evaluateSymbolSqlWithContext( () => { + // zeroRowLimitTopClause() is empty for every dialect that can express a zero row + // limit as a trailing clause; T-SQL can not, and this statement has no topLimit() // eslint-disable-next-line prefer-template - const query = `SELECT ${this.query.selectAllDimensionsAndMeasures(measures)} FROM ${from} ${this.query.baseWhere(replacedFilters)}` + + const query = `SELECT${this.query.zeroRowLimitTopClause()} ${this.query.selectAllDimensionsAndMeasures(measures)} FROM ${from} ${this.query.baseWhere(replacedFilters)}` + this.query.groupByClause(); return isFullSimpleQuery ? this.query.baseHaving(query, this.query.measureFilters) + diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeValidator.ts b/packages/cubejs-schema-compiler/src/compiler/CubeValidator.ts index a7452ea2bff33..5ac0cfd00d911 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeValidator.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeValidator.ts @@ -1,5 +1,6 @@ import Joi from 'joi'; import cronParser from 'cron-parser'; +import { isPredefinedGranularity, TIME_SERIES } from '@cubejs-backend/shared'; import { CubeSymbols, CubeDefinition, ToString } from './CubeSymbols'; import type { ErrorReporter } from './ErrorReporter'; @@ -109,6 +110,8 @@ const everyCronTimeZone = Joi.string().custom((value, helper) => { } }); +const PREDEFINED_GRANULARITY_NAMES = Object.keys(TIME_SERIES).sort(); + const GranularityInterval = Joi.string().pattern(/^\d+\s+(second|minute|hour|day|week|month|quarter|year)s?(\s\d+\s+(second|minute|hour|day|week|month|quarter|year)s?){0,7}$/, 'granularity interval'); // Do not allow negative intervals for granularities, while offsets could be negative const GranularityOffset = Joi.string().pattern(/^-?(\d+\s+)(second|minute|hour|day|week|month|quarter|year)s?(\s-?\d+\s+(second|minute|hour|day|week|month|quarter|year)s?){0,7}$/, 'granularity offset'); @@ -1417,20 +1420,48 @@ export class CubeValidator implements CompilerInterface { }; const result = cube.isView ? viewSchema.validate(cube, options) : cubeSchema.validate(cube, options); + let valid = result.error == null; + if (cube.isView) { // We need to verify that leaf cubes in view are present only once this.validateUniqueLeafCubes(cube.name, cube.cubes, errorReporter); + } else if (!this.validateGranularitySql(cube, errorReporter)) { + valid = false; } if (result.error != null) { errorReporter.error(formatErrorMessage(result.error)); - } else { + } + + if (valid) { this.validCubes.set(cube.name, true); } return result; } + // Reported outside the cube schema so the message stands on its own: a + // granularity rejected by the schema is listed among the reasons every other + // dimension alternative failed, which buries it. + private validateGranularitySql(cube, errorReporter: ErrorReporter): boolean { + let valid = true; + + for (const [dimensionName, dimension] of Object.entries(cube.dimensions || {})) { + for (const [name, granularity] of Object.entries(dimension?.granularities || {})) { + // Predefined names are resolved case-insensitively, so `Week` names a + // granularity that resolves and must keep doing so. + if (granularity?.sql && !isPredefinedGranularity(name.toLowerCase())) { + errorReporter.error( + `dimensions.${dimensionName}.granularities.${name}: a granularity defined with 'sql' must be named after one of the predefined granularities (${PREDEFINED_GRANULARITY_NAMES.join(', ')}). Define '${name}' with 'interval' instead` + ); + valid = false; + } + } + } + + return valid; + } + public validateViewGroup(viewGroup, errorReporter: ErrorReporter) { const options = { nonEnumerables: true, diff --git a/packages/cubejs-schema-compiler/test/integration/postgres/calendar-to-date.test.ts b/packages/cubejs-schema-compiler/test/integration/postgres/calendar-to-date.test.ts new file mode 100644 index 0000000000000..dffe2ec780a32 --- /dev/null +++ b/packages/cubejs-schema-compiler/test/integration/postgres/calendar-to-date.test.ts @@ -0,0 +1,188 @@ +import { getEnv } from '@cubejs-backend/shared'; +import { PostgresQuery } from '../../../src/adapter'; +import { prepareYamlCompiler } from '../../unit/PrepareCompiler'; +import { dbRunner } from './PostgresDBRunner'; + +describe('Calendar cube to-date rolling window', () => { + jest.setTimeout(200000); + + // Fiscal weeks start on Sunday 2023-12-17 and run for 7 days, so they line up + // with neither the ISO week nor any interval anchored at the start of a year. + // language=YAML + const { compiler, joinGraph, cubeEvaluator } = prepareYamlCompiler(` +cubes: + - name: fiscal_calendar + calendar: true + sql: > + SELECT (DATE '2023-12-17' + (gs.n - 1))::date AS cal_date, + (DATE '2023-12-17' + ((gs.n - 1) / 7) * 7)::date AS wk_start_dt, + CASE + WHEN gs.n - 1 < 28 THEN DATE '2023-12-17' + WHEN gs.n - 1 < 63 THEN DATE '2024-01-14' + ELSE DATE '2024-02-18' + END::date AS mo_start_dt + FROM generate_series(1, 91) AS gs(n) + dimensions: + - name: date_key + sql: cal_date + type: time + primary_key: true + - name: date + sql: cal_date + type: time + granularities: + - name: week + sql: "{CUBE}.wk_start_dt" + - name: month + sql: "{CUBE}.mo_start_dt" + + - name: sales + sql: > + SELECT gs.n::int AS id, + (DATE '2023-12-17' + (gs.n - 1))::date AS date, + 10 AS amount + FROM generate_series(1, 91) AS gs(n) + joins: + - name: fiscal_calendar + sql: "{CUBE}.date = {fiscal_calendar.date_key}" + relationship: many_to_one + dimensions: + - name: id + sql: id + type: number + primary_key: true + measures: + - name: wtd_amount + sql: amount + type: sum + rolling_window: + type: to_date + granularity: week + + - name: mtd_amount + sql: amount + type: sum + rolling_window: + type: to_date + granularity: month + + - name: trailing_amount + sql: amount + type: sum + rolling_window: + trailing: 3 day + offset: end +`); + + async function runQueryTest(q: any, expectedResult: any) { + // Calendars are working only with Tesseract SQL planner + if (!getEnv('nativeSqlPlanner')) { + return; + } + + await compiler.compile(); + const query = new PostgresQuery( + { joinGraph, cubeEvaluator, compiler }, + { ...q, timezone: 'UTC', preAggregationsSchema: '' } + ); + + const res = await dbRunner.testQuery(query.buildSqlAndParams()); + + expect(res).toEqual(expectedResult); + } + + it('accumulates within the calendar week, not within a natural one', async () => runQueryTest({ + measures: ['sales.wtd_amount'], + timeDimensions: [{ + dimension: 'fiscal_calendar.date', + granularity: 'day', + dateRange: ['2023-12-19', '2023-12-26'], + }], + order: [{ id: 'fiscal_calendar.date' }], + }, [ + // Fiscal week of 2023-12-17 accumulates through 2023-12-23... + { fiscal_calendar__date_day: '2023-12-19T00:00:00.000Z', sales__wtd_amount: '30' }, + { fiscal_calendar__date_day: '2023-12-20T00:00:00.000Z', sales__wtd_amount: '40' }, + { fiscal_calendar__date_day: '2023-12-21T00:00:00.000Z', sales__wtd_amount: '50' }, + { fiscal_calendar__date_day: '2023-12-22T00:00:00.000Z', sales__wtd_amount: '60' }, + { fiscal_calendar__date_day: '2023-12-23T00:00:00.000Z', sales__wtd_amount: '70' }, + // ...and resets on 2023-12-24, where the next fiscal week starts. + { fiscal_calendar__date_day: '2023-12-24T00:00:00.000Z', sales__wtd_amount: '10' }, + { fiscal_calendar__date_day: '2023-12-25T00:00:00.000Z', sales__wtd_amount: '20' }, + { fiscal_calendar__date_day: '2023-12-26T00:00:00.000Z', sales__wtd_amount: '30' }, + ])); + + it('bounds each window by its own calendar period', async () => runQueryTest({ + measures: ['sales.wtd_amount', 'sales.mtd_amount'], + timeDimensions: [{ + dimension: 'fiscal_calendar.date', + granularity: 'day', + dateRange: ['2023-12-23', '2023-12-25'], + }], + order: [{ id: 'fiscal_calendar.date' }], + }, [ + // Fiscal weeks are 7 days; the fiscal month running from 2023-12-17 is 28. + { fiscal_calendar__date_day: '2023-12-23T00:00:00.000Z', sales__wtd_amount: '70', sales__mtd_amount: '70' }, + { fiscal_calendar__date_day: '2023-12-24T00:00:00.000Z', sales__wtd_amount: '10', sales__mtd_amount: '80' }, + { fiscal_calendar__date_day: '2023-12-25T00:00:00.000Z', sales__wtd_amount: '20', sales__mtd_amount: '90' }, + ])); + + it('ends a period where the calendar ends it, not one nominal interval later', async () => runQueryTest({ + measures: ['sales.mtd_amount'], + timeDimensions: [{ + dimension: 'fiscal_calendar.date', + granularity: 'month', + dateRange: ['2023-12-17', '2024-03-16'], + }], + order: [{ id: 'fiscal_calendar.date' }], + }, [ + // 28, 35 and 28 days at 10 a day. A nominal `1 month` upper bound would + // reach past the 28-day period and fold the next one into it. + { fiscal_calendar__date_month: '2023-12-17T00:00:00.000Z', sales__mtd_amount: '280' }, + { fiscal_calendar__date_month: '2024-01-14T00:00:00.000Z', sales__mtd_amount: '350' }, + { fiscal_calendar__date_month: '2024-02-18T00:00:00.000Z', sales__mtd_amount: '280' }, + ])); + + it('leaves a regular window on the same series alone', async () => runQueryTest({ + measures: ['sales.trailing_amount', 'sales.mtd_amount'], + timeDimensions: [{ + dimension: 'fiscal_calendar.date', + granularity: 'day', + dateRange: ['2023-12-22', '2023-12-25'], + }], + order: [{ id: 'fiscal_calendar.date' }], + }, [ + // The trailing window keeps counting across the fiscal boundary the + // to-date window resets on. + { fiscal_calendar__date_day: '2023-12-22T00:00:00.000Z', sales__trailing_amount: '30', sales__mtd_amount: '60' }, + { fiscal_calendar__date_day: '2023-12-23T00:00:00.000Z', sales__trailing_amount: '30', sales__mtd_amount: '70' }, + { fiscal_calendar__date_day: '2023-12-24T00:00:00.000Z', sales__trailing_amount: '30', sales__mtd_amount: '80' }, + { fiscal_calendar__date_day: '2023-12-25T00:00:00.000Z', sales__trailing_amount: '30', sales__mtd_amount: '90' }, + ])); + + it('ends the last period in range on the calendar too', async () => runQueryTest({ + measures: ['sales.mtd_amount'], + timeDimensions: [{ + dimension: 'fiscal_calendar.date', + granularity: 'month', + dateRange: ['2024-01-14', '2024-02-17'], + }], + }, [ + // The range ends exactly where this 35-day period does, so the period that + // bounds it is outside the range: reading the end off the next series point + // only works if the series looks past its own restriction. + { fiscal_calendar__date_month: '2024-01-14T00:00:00.000Z', sales__mtd_amount: '350' }, + ])); + + it('resolves the series range at query time when none is given', async () => runQueryTest({ + measures: ['sales.wtd_amount'], + timeDimensions: [{ + dimension: 'fiscal_calendar.date', + granularity: 'week', + }], + order: [{ id: 'fiscal_calendar.date' }], + }, Array.from({ length: 13 }, (_, i) => ({ + fiscal_calendar__date_week: new Date(Date.UTC(2023, 11, 17 + i * 7)).toISOString(), + sales__wtd_amount: '70', + })))); +}); diff --git a/packages/cubejs-schema-compiler/test/unit/calendar-sql-granularity.test.ts b/packages/cubejs-schema-compiler/test/unit/calendar-sql-granularity.test.ts new file mode 100644 index 0000000000000..6e3caa96aca03 --- /dev/null +++ b/packages/cubejs-schema-compiler/test/unit/calendar-sql-granularity.test.ts @@ -0,0 +1,144 @@ +import { PostgresQuery } from '../../src/adapter/PostgresQuery'; +import { prepareYamlCompiler } from './PrepareCompiler'; + +describe('Calendar cube granularities defined with sql', () => { + // language=YAML + const modelWith = (granularity: string) => ` +cubes: + - name: fiscal_calendar + calendar: true + sql: > + SELECT '2023-12-17'::DATE AS cal_date, '2023-12-17'::DATE AS wk_start_dt UNION ALL + SELECT '2023-12-24'::DATE, '2023-12-24'::DATE + dimensions: + - name: date_key + sql: cal_date + type: time + primary_key: true + - name: date + sql: cal_date + type: time + granularities: +${granularity} + + - name: sales + sql: > + SELECT 1 AS id, '2023-12-24'::DATE AS date, 100 AS amount + joins: + - name: fiscal_calendar + sql: "{CUBE}.date = {fiscal_calendar.date_key}" + relationship: many_to_one + dimensions: + - name: id + sql: id + type: number + primary_key: true + measures: + - name: total_amount + sql: amount + type: sum +`; + + const sqlOverride = (name: string) => ` - name: ${name} + sql: "{CUBE}.wk_start_dt"`; + + const compile = async (granularity: string) => { + const compilers = prepareYamlCompiler(modelWith(granularity)); + await compilers.compiler.compile(); + return compilers; + }; + + it('rejects a sql override named after a non-predefined granularity', async () => { + await expect(compile(sqlOverride('fiscal_week'))).rejects.toThrow( + /granularity defined with 'sql' must be named after one of the predefined granularities/ + ); + }); + + it('accepts a sql override whose predefined name differs in case', async () => { + // Predefined names resolve case-insensitively, so this one resolves today. + await expect(compile(sqlOverride('Week'))).resolves.toBeDefined(); + }); + + describe.each([ + ['legacy planner', false], + ['native planner', true], + ])('%s', (_name, useNativeSqlPlanner) => { + const newQuery = (compilers: any, query: any) => new PostgresQuery( + compilers, + { ...query, timezone: 'UTC', useNativeSqlPlanner } + ); + + it('keeps queries that do not touch the calendar cube working', async () => { + const compilers = await compile(sqlOverride('week')); + const query = newQuery(compilers, { measures: ['sales.total_amount'] }); + + expect(() => query.preAggregations.canUseTransformedQuery()).not.toThrow(); + expect(() => query.buildSqlAndParams()).not.toThrow(); + }); + + it('keeps queries using the overridden granularity working', async () => { + const compilers = await compile(sqlOverride('week')); + const query = newQuery(compilers, { + measures: ['sales.total_amount'], + timeDimensions: [{ dimension: 'fiscal_calendar.date', granularity: 'week' }], + }); + + expect(() => query.preAggregations.canUseTransformedQuery()).not.toThrow(); + expect(() => query.buildSqlAndParams()).not.toThrow(); + }); + + it('keeps hierarchies of interval based granularities', async () => { + const compilers = await compile(` - name: fortnight + interval: 2 week + origin: "2025-01-01"`); + const query = newQuery(compilers, { measures: ['sales.total_amount'] }); + + expect(query.granularityHierarchies()['fiscal_calendar.date.fortnight']) + .toEqual(['fortnight', 'day', 'hour', 'minute', 'second']); + }); + }); + + describe('granularity without an interval reaching the query', () => { + // Validation rejects such a granularity, so it is injected into the compiled + // model to check that it stays contained to the queries that ask for it. + const compileWithInjected = async () => { + const compilers = await compile(sqlOverride('week')); + const dimension: any = compilers.cubeEvaluator.symbols.fiscal_calendar.date; + dimension.granularities.fiscal_week = { sql: () => 'wk_start_dt' }; + return compilers; + }; + + it('does not affect queries that do not use it', async () => { + const compilers = await compileWithInjected(); + const query = new PostgresQuery(compilers, { + measures: ['sales.total_amount'], + timezone: 'UTC', + }); + + expect(() => query.preAggregations.canUseTransformedQuery()).not.toThrow(); + expect(query.granularityHierarchies()['fiscal_calendar.date.fiscal_week']).toBeUndefined(); + }); + + it('fails with a readable error when a query uses it', async () => { + const compilers = await compileWithInjected(); + + expect(() => new PostgresQuery(compilers, { + measures: ['sales.total_amount'], + timeDimensions: [{ dimension: 'fiscal_calendar.date', granularity: 'fiscal_week' }], + timezone: 'UTC', + })).toThrow(/is defined with 'sql', which is only supported for predefined granularities/); + }); + }); + + it('renders the calendar sql for the overridden granularity', async () => { + const compilers = await compile(sqlOverride('week')); + const query = new PostgresQuery(compilers, { + measures: ['sales.total_amount'], + timeDimensions: [{ dimension: 'fiscal_calendar.date', granularity: 'week' }], + timezone: 'UTC', + useNativeSqlPlanner: true, + }); + + expect(query.buildSqlAndParams()[0]).toContain('wk_start_dt'); + }); +}); diff --git a/packages/cubejs-schema-compiler/test/unit/mssql-query.test.ts b/packages/cubejs-schema-compiler/test/unit/mssql-query.test.ts index 4d13071990f3b..8dd435f19f506 100644 --- a/packages/cubejs-schema-compiler/test/unit/mssql-query.test.ts +++ b/packages/cubejs-schema-compiler/test/unit/mssql-query.test.ts @@ -196,6 +196,194 @@ describe('MssqlQuery', () => { expect(/GROUP BY/.test(queryString)).toEqual(false); })); + it('renders rowLimit: 0 as TOP 0 without an invalid FETCH NEXT 0', async () => { + await compiler.compile(); + + // With an ORDER BY the template would normally emit OFFSET/FETCH NEXT, but T-SQL + // rejects `FETCH NEXT 0 ROWS ONLY`, so a zero limit has to go through TOP + const query = new MssqlQuery({ joinGraph, cubeEvaluator, compiler }, { + measures: ['visitors.count'], + dimensions: ['visitors.source'], + order: [{ id: 'visitors.source', desc: false }], + timezone: 'UTC', + rowLimit: 0, + }); + + const sql = query.buildSqlAndParams()[0]; + + expect(sql).toContain('TOP 0'); + expect(sql).not.toContain('FETCH NEXT'); + }); + + it('renders rowLimit: 0 with an offset as TOP 0 and no OFFSET tail', async () => { + await compiler.compile(); + + // T-SQL forbids TOP together with OFFSET/FETCH, and a zero limit yields no rows + // whatever the offset is, so the whole OFFSET/FETCH tail has to go + const query = new MssqlQuery({ joinGraph, cubeEvaluator, compiler }, { + measures: ['visitors.count'], + dimensions: ['visitors.source'], + order: [{ id: 'visitors.source', desc: false }], + timezone: 'UTC', + rowLimit: 0, + offset: 10, + }); + + const sql = query.buildSqlAndParams()[0]; + + expect(sql).toContain('TOP 0'); + expect(sql).not.toContain('FETCH NEXT'); + expect(sql).not.toContain('OFFSET'); + }); + + it('still renders OFFSET/FETCH NEXT for a non-zero rowLimit with an offset', async () => { + await compiler.compile(); + + const query = new MssqlQuery({ joinGraph, cubeEvaluator, compiler }, { + measures: ['visitors.count'], + dimensions: ['visitors.source'], + order: [{ id: 'visitors.source', desc: false }], + timezone: 'UTC', + rowLimit: 5, + offset: 10, + }); + + const sql = query.buildSqlAndParams()[0]; + + expect(sql).toContain('OFFSET 10 ROWS'); + expect(sql).toContain('FETCH NEXT 5 ROWS ONLY'); + expect(sql).not.toContain('TOP'); + }); + + it('renders DISTINCT before TOP in the select template', async () => { + await compiler.compile(); + + const query = new MssqlQuery({ joinGraph, cubeEvaluator, compiler }, { + measures: ['visitors.count'], + timezone: 'UTC', + rowLimit: 0, + }); + + // T-SQL clause order is SELECT [ALL | DISTINCT] [TOP (expr)], so `SELECT TOP 0 DISTINCT` + // is a syntax error. A single select carrying both is reachable through the cubesql + // wrapper (`SELECT DISTINCT ... LIMIT 0`), which can't be built from here, so the + // template itself is what gets pinned + const { select } = query.sqlTemplates().statements; + + expect(select).toContain('DISTINCT'); + expect(select).toContain('TOP'); + expect(select.indexOf('DISTINCT')).toBeLessThan(select.indexOf('TOP')); + }); + + it('keeps rowLimit: 0 out of the legacy limit clauses', async () => { + await compiler.compile(); + + const query = new MssqlQuery({ joinGraph, cubeEvaluator, compiler }, { + measures: ['visitors.count'], + timezone: 'UTC', + rowLimit: 0, + offset: 10, + }); + + expect(query.topLimit()).toEqual(' TOP 0'); + expect(query.groupByDimensionLimit()).toEqual(''); + // The legacy rollup query in PreAggregations renders no topLimit(), so the zero limit + // has to come from this hook or that statement would scan the whole rollup + expect(query.zeroRowLimitTopClause()).toEqual(' TOP 0'); + }); + + it('renders no leading zero-limit clause for a non-zero rowLimit', async () => { + await compiler.compile(); + + const query = new MssqlQuery({ joinGraph, cubeEvaluator, compiler }, { + measures: ['visitors.count'], + timezone: 'UTC', + rowLimit: 5, + }); + + expect(query.zeroRowLimitTopClause()).toEqual(''); + }); + + it('renders TOP 0 in the legacy-planner pre-aggregation rollup query', async () => { + // The rollup statement in PreAggregations renders no topLimit(), and T-SQL cannot put a + // zero limit in a trailing clause, so without zeroRowLimitTopClause() a `rowLimit: 0` + // query served from a pre-aggregation would scan the whole rollup + const preAggCompilers = prepareJsCompiler(` + cube('visits', { + sql: 'SELECT * FROM visits', + + preAggregations: { + bySource: { + measures: [CUBE.count], + dimensions: [CUBE.source], + }, + }, + + measures: { + count: { type: 'count' }, + }, + + dimensions: { + id: { sql: 'id', type: 'number', primaryKey: true }, + source: { sql: 'source', type: 'string' }, + }, + }); + `); + await preAggCompilers.compiler.compile(); + + const queryOptions = { + measures: ['visits.count'], + dimensions: ['visits.source'], + timezone: 'UTC', + useNativeSqlPlanner: false, + preAggregationsSchema: '', + }; + + const zeroLimit = new MssqlQuery({ + joinGraph: preAggCompilers.joinGraph, + cubeEvaluator: preAggCompilers.cubeEvaluator, + compiler: preAggCompilers.compiler, + }, { ...queryOptions, rowLimit: 0 }); + + const zeroLimitSql = zeroLimit.buildSqlAndParams()[0]; + + expect(zeroLimit.preAggregations.findPreAggregationForQuery()).toBeDefined(); + expect(zeroLimitSql).toContain('TOP 0'); + + const nonZeroLimit = new MssqlQuery({ + joinGraph: preAggCompilers.joinGraph, + cubeEvaluator: preAggCompilers.cubeEvaluator, + compiler: preAggCompilers.compiler, + }, { ...queryOptions, rowLimit: 5 }); + + // Non-zero limits keep their existing rendering on this path + expect(nonZeroLimit.buildSqlAndParams()[0]).not.toContain('TOP 0'); + }); + + it('keeps DISTINCT and TOP 0 in a valid order for a multiplied-measure query', async () => { + await joinedSchemaCompilers.compiler.compile(); + + // Multiplied measures make the full-key-aggregate path emit DISTINCT keys sub-selects + // alongside the TOP 0 outer select + const query = new MssqlQuery({ + joinGraph: joinedSchemaCompilers.joinGraph, + cubeEvaluator: joinedSchemaCompilers.cubeEvaluator, + compiler: joinedSchemaCompilers.compiler, + }, { + measures: ['B.bval_sum', 'C.count'], + dimensions: ['B.bid'], + order: [{ id: 'B.bid', desc: false }], + timezone: 'UTC', + rowLimit: 0, + }); + + const sql = query.buildSqlAndParams()[0]; + + expect(sql).toContain('TOP 0'); + expect(sql).toContain('DISTINCT'); + expect(sql).not.toMatch(/TOP\s+0\s+DISTINCT/); + }); + it('aggregating on top of sub-queries', async () => { await joinedSchemaCompilers.compiler.compile(); const query = new MssqlQuery({ diff --git a/packages/cubejs-schema-compiler/test/unit/oracle-query.test.ts b/packages/cubejs-schema-compiler/test/unit/oracle-query.test.ts index 58bf4f2696d70..78c1921dfb427 100644 --- a/packages/cubejs-schema-compiler/test/unit/oracle-query.test.ts +++ b/packages/cubejs-schema-compiler/test/unit/oracle-query.test.ts @@ -236,6 +236,39 @@ describe('OracleQuery', () => { expect(sql).not.toContain('LIMIT'); }); + it('renders rowLimit: 0 as FETCH NEXT 0 ROWS ONLY', async () => { + await compiler.compile(); + + const query = new OracleQuery({ joinGraph, cubeEvaluator, compiler }, { + measures: [ + 'visitors.count' + ], + timezone: 'UTC', + rowLimit: 0 + }); + + const sql = query.buildSqlAndParams()[0]; + + expect(sql).toContain('FETCH NEXT 0 ROWS ONLY'); + // A truthy check on rowLimit used to fall back to the default limit here + expect(sql).not.toContain('10000'); + expect(query.groupByDimensionLimit()).toEqual(' FETCH NEXT 0 ROWS ONLY'); + }); + + it('keeps the documented null policy for rowLimit', async () => { + await compiler.compile(); + + const newQuery = (rowLimit?: number | null) => new OracleQuery( + { joinGraph, cubeEvaluator, compiler }, + { measures: ['visitors.count'], timezone: 'UTC', ...(rowLimit === undefined ? {} : { rowLimit }) } + ); + + // An absent rowLimit keeps the historical default, an explicit null means no limit + expect(newQuery().groupByDimensionLimit()).toEqual(' FETCH NEXT 10000 ROWS ONLY'); + expect(newQuery(null).groupByDimensionLimit()).toEqual(''); + expect(newQuery(0).groupByDimensionLimit()).toEqual(' FETCH NEXT 0 ROWS ONLY'); + }); + it('uses FETCH NEXT syntax with subqueries and rolling windows', async () => { await compiler.compile(); diff --git a/packages/cubejs-schema-compiler/test/unit/postgres-query.test.ts b/packages/cubejs-schema-compiler/test/unit/postgres-query.test.ts index cf59f4c39eab3..40591351629e5 100644 --- a/packages/cubejs-schema-compiler/test/unit/postgres-query.test.ts +++ b/packages/cubejs-schema-compiler/test/unit/postgres-query.test.ts @@ -384,6 +384,24 @@ describe('PostgresQuery', () => { expect(sql).toMatch(/WHERE/i); }); + it('pushes down rowLimit: 0 as LIMIT 0', async () => { + await compiler.compile(); + + const query = new PostgresQuery({ joinGraph, cubeEvaluator, compiler }, { + measures: [ + 'visitors.count' + ], + dimensions: [ + 'visitors.name' + ], + timezone: 'UTC', + rowLimit: 0, + }); + + const queryAndParams = query.buildSqlAndParams(); + expect(queryAndParams[0]).toContain('LIMIT 0'); + }); + it('uses AS keyword in subquery aliases (regression test)', async () => { await compiler.compile(); diff --git a/packages/cubejs-schema-compiler/test/unit/raw-time-dimension-timezone.test.ts b/packages/cubejs-schema-compiler/test/unit/raw-time-dimension-timezone.test.ts new file mode 100644 index 0000000000000..d4236d46c5da5 --- /dev/null +++ b/packages/cubejs-schema-compiler/test/unit/raw-time-dimension-timezone.test.ts @@ -0,0 +1,154 @@ +import { BigqueryQuery } from '../../src/adapter/BigqueryQuery'; +import { prepareJsCompiler } from './PrepareCompiler'; + +// A `type: time` dimension asked for without a granularity is converted into the +// query timezone once. A view re-exposing that dimension is not an extra +// conversion site: the value is read (and converted) on the owning cube. +const model = [ + 'cube(\'orders\', {', + ' sql: `SELECT * FROM orders`,', + ' measures: {', + ' count: {', + ' type: `count`', + ' }', + ' },', + ' dimensions: {', + ' id: {', + ' sql: `id`,', + ' type: `number`,', + ' primaryKey: true', + ' },', + ' createdAt: {', + ' sql: `created_at`,', + ' type: `time`', + ' },', + ' updatedAt: {', + ' sql: `updated_at`,', + ' type: `time`', + ' },', + ' epochSeconds: {', + ' sql: `epoch_seconds`,', + ' type: `number`', + ' },', + // Builds a timestamp out of a non-time member: the value is composed here, + // not read from a column of this cube. + ' fromEpoch: {', + // eslint-disable-next-line no-template-curly-in-string + ' sql: `TIMESTAMP_SECONDS(${CUBE.epochSeconds})`,', + ' type: `time`', + ' }', + ' }', + '});', + '', + 'view(\'ordersView\', {', + ' cubes: [{', + ' joinPath: orders,', + ' includes: [`count`, `createdAt`, `updatedAt`, `fromEpoch`]', + ' }]', + '});', +].join('\n'); + +const TIMEZONE = 'America/Chicago'; + +async function queryFor(options = {}) { + const { compiler, joinGraph, cubeEvaluator } = prepareJsCompiler(model); + await compiler.compile(); + + return new BigqueryQuery({ joinGraph, cubeEvaluator, compiler }, { + timezone: TIMEZONE, + convertTzForRawTimeDimension: true, + ...options, + }); +} + +// BigqueryQuery.convertTz wraps the field in `TIMESTAMP(DATETIME(field, ''))`, +// so the timezone literal appears once per conversion applied to the column. +// Counting the literal rather than matching the wrapper catches nested wrapping, +// where the outer call is no longer a flat `DATETIME(, '')`. +function conversionsCount(sql: string) { + return (sql.match(new RegExp(`'${TIMEZONE}'`, 'g')) || []).length; +} + +describe('raw time dimension timezone conversion', () => { + it('converts a cube time dimension once', async () => { + const query = await queryFor({ + measures: ['orders.count'], + dimensions: ['orders.createdAt'], + }); + const [sql] = query.buildSqlAndParams(); + + expect(conversionsCount(sql)).toEqual(1); + }); + + it('converts a view time dimension once', async () => { + const query = await queryFor({ + measures: ['ordersView.count'], + dimensions: ['ordersView.createdAt'], + }); + const [sql] = query.buildSqlAndParams(); + + expect(conversionsCount(sql)).toEqual(1); + }); + + it('converts a view time dimension with granularity once', async () => { + const query = await queryFor({ + measures: ['ordersView.count'], + timeDimensions: [{ + dimension: 'ordersView.createdAt', + granularity: 'day', + }], + }); + const [sql] = query.buildSqlAndParams(); + + expect(conversionsCount(sql)).toEqual(1); + }); + + it('converts each of two view time dimensions once', async () => { + const query = await queryFor({ + measures: ['ordersView.count'], + dimensions: ['ordersView.createdAt', 'ordersView.updatedAt'], + }); + const [sql] = query.buildSqlAndParams(); + + expect(conversionsCount(sql)).toEqual(2); + }); + + // A dimension composed out of another member is not read from a column of + // its own, so there is nothing to convert at this level. + it('does not convert a dimension composed from a non-time member', async () => { + const query = await queryFor({ + measures: ['ordersView.count'], + dimensions: ['ordersView.fromEpoch'], + }); + const [sql] = query.buildSqlAndParams(); + + expect(conversionsCount(sql)).toEqual(0); + }); + + // Filter bounds are normalized to the database timezone instead, so the + // filtered column is compared as it is stored. + it('does not convert a filtered column', async () => { + const query = await queryFor({ + measures: ['ordersView.count'], + filters: [{ + member: 'ordersView.createdAt', + operator: 'afterDate', + values: ['2026-08-04'], + }], + }); + const [sql] = query.buildSqlAndParams(); + + expect(conversionsCount(sql)).toEqual(0); + }); + + it('does not convert without a timezone conversion request', async () => { + const query = await queryFor({ + measures: ['ordersView.count'], + dimensions: ['ordersView.createdAt'], + convertTzForRawTimeDimension: false, + }); + const [sql] = query.buildSqlAndParams(); + + expect(conversionsCount(sql)).toEqual(0); + }); +}); diff --git a/packages/cubejs-server-core/CHANGELOG.md b/packages/cubejs-server-core/CHANGELOG.md index 6e93921974177..8c9658481bc06 100644 --- a/packages/cubejs-server-core/CHANGELOG.md +++ b/packages/cubejs-server-core/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/server-core + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) ### Features diff --git a/packages/cubejs-server-core/package.json b/packages/cubejs-server-core/package.json index 76fc9a0161ce3..23de6199bb457 100644 --- a/packages/cubejs-server-core/package.json +++ b/packages/cubejs-server-core/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/server-core", "description": "Cube.js base component to wire all backend components together", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -29,16 +29,16 @@ "unit": "jest --runInBand --forceExit --coverage dist/test" }, "dependencies": { - "@cubejs-backend/api-gateway": "1.7.31", - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/cloud": "1.7.31", - "@cubejs-backend/cubestore-driver": "1.7.31", + "@cubejs-backend/api-gateway": "1.7.32", + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/cloud": "1.7.32", + "@cubejs-backend/cubestore-driver": "1.7.32", "@cubejs-backend/dotenv": "^9.0.2", - "@cubejs-backend/native": "1.7.31", - "@cubejs-backend/query-orchestrator": "1.7.31", - "@cubejs-backend/schema-compiler": "1.7.31", - "@cubejs-backend/shared": "1.7.31", - "@cubejs-backend/templates": "1.7.31", + "@cubejs-backend/native": "1.7.32", + "@cubejs-backend/query-orchestrator": "1.7.32", + "@cubejs-backend/schema-compiler": "1.7.32", + "@cubejs-backend/shared": "1.7.32", + "@cubejs-backend/templates": "1.7.32", "codesandbox-import-utils": "^2.1.12", "cross-spawn": "^7.0.1", "fs-extra": "^8.1.0", @@ -62,8 +62,8 @@ "ws": "^7.5.3" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.31", - "@cubejs-client/playground": "1.7.31", + "@cubejs-backend/linter": "1.7.32", + "@cubejs-client/playground": "1.7.32", "@types/cross-spawn": "^6.0.2", "@types/express": "^4.17.21", "@types/fs-extra": "^9.0.8", diff --git a/packages/cubejs-server/CHANGELOG.md b/packages/cubejs-server/CHANGELOG.md index b80c84a4c8bc9..faa8b54557740 100644 --- a/packages/cubejs-server/CHANGELOG.md +++ b/packages/cubejs-server/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/server + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/server diff --git a/packages/cubejs-server/package.json b/packages/cubejs-server/package.json index d643b0edf2754..0226b3dc24d7a 100644 --- a/packages/cubejs-server/package.json +++ b/packages/cubejs-server/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/server", "description": "Cube.js all-in-one server", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "types": "index.d.ts", "repository": { "type": "git", @@ -40,11 +40,11 @@ "jest:shapshot": "jest --updateSnapshot test" }, "dependencies": { - "@cubejs-backend/cubestore-driver": "1.7.31", + "@cubejs-backend/cubestore-driver": "1.7.32", "@cubejs-backend/dotenv": "^9.0.2", - "@cubejs-backend/native": "1.7.31", - "@cubejs-backend/server-core": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/native": "1.7.32", + "@cubejs-backend/server-core": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "@oclif/color": "^1.0.0", "@oclif/command": "^1.8.13", "@oclif/config": "^1.18.2", @@ -61,8 +61,8 @@ "ws": "^7.1.2" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.31", - "@cubejs-backend/query-orchestrator": "1.7.31", + "@cubejs-backend/linter": "1.7.32", + "@cubejs-backend/query-orchestrator": "1.7.32", "@oclif/dev-cli": "^1.23.1", "@types/body-parser": "^1.19.0", "@types/cors": "^2.8.8", diff --git a/packages/cubejs-snowflake-driver/CHANGELOG.md b/packages/cubejs-snowflake-driver/CHANGELOG.md index a967dc36dfcc6..7c71539e60760 100644 --- a/packages/cubejs-snowflake-driver/CHANGELOG.md +++ b/packages/cubejs-snowflake-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/snowflake-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/snowflake-driver diff --git a/packages/cubejs-snowflake-driver/package.json b/packages/cubejs-snowflake-driver/package.json index 676c6695abf63..402dda221eb30 100644 --- a/packages/cubejs-snowflake-driver/package.json +++ b/packages/cubejs-snowflake-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/snowflake-driver", "description": "Cube.js Snowflake database driver", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -29,8 +29,8 @@ }, "dependencies": { "@aws-sdk/client-s3": "^3.726.0", - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "snowflake-sdk": "^2.4.0" }, "license": "Apache-2.0", @@ -41,7 +41,7 @@ "extends": "../cubejs-linter" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.31", + "@cubejs-backend/linter": "1.7.32", "typescript": "~5.2.2", "vitest": "^4" } diff --git a/packages/cubejs-sqlite-driver/CHANGELOG.md b/packages/cubejs-sqlite-driver/CHANGELOG.md index bd33db79ae477..99b8ffb1996a3 100644 --- a/packages/cubejs-sqlite-driver/CHANGELOG.md +++ b/packages/cubejs-sqlite-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/sqlite-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/sqlite-driver diff --git a/packages/cubejs-sqlite-driver/package.json b/packages/cubejs-sqlite-driver/package.json index 3d536acd9a176..eb2d6318093e1 100644 --- a/packages/cubejs-sqlite-driver/package.json +++ b/packages/cubejs-sqlite-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/sqlite-driver", "description": "Cube.js Sqlite database driver", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -18,13 +18,13 @@ "unit": "jest" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "sqlite3": "^5.1.7" }, "license": "Apache-2.0", "devDependencies": { - "@cubejs-backend/linter": "1.7.31", + "@cubejs-backend/linter": "1.7.32", "jest": "^29" }, "publishConfig": { diff --git a/packages/cubejs-templates/CHANGELOG.md b/packages/cubejs-templates/CHANGELOG.md index 5473e5f3bec07..1780f6d89eddb 100644 --- a/packages/cubejs-templates/CHANGELOG.md +++ b/packages/cubejs-templates/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/templates + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/templates diff --git a/packages/cubejs-templates/package.json b/packages/cubejs-templates/package.json index 02a949c3f2466..21dccfbfe6dad 100644 --- a/packages/cubejs-templates/package.json +++ b/packages/cubejs-templates/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-backend/templates", - "version": "1.7.31", + "version": "1.7.32", "description": "Cube.js Templates helpers", "author": "Cube Dev, Inc.", "repository": { @@ -31,7 +31,7 @@ "extends": "../cubejs-linter" }, "dependencies": { - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/shared": "1.7.32", "cross-spawn": "^7.0.3", "fs-extra": "^9.1.0", "node-fetch": "^2.6.1", @@ -40,7 +40,7 @@ "tar": "^7.5.22" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.31", + "@cubejs-backend/linter": "1.7.32", "typescript": "~5.2.2" } } diff --git a/packages/cubejs-testing-drivers/CHANGELOG.md b/packages/cubejs-testing-drivers/CHANGELOG.md index afebdfb4b995d..6a0ef8df433d5 100644 --- a/packages/cubejs-testing-drivers/CHANGELOG.md +++ b/packages/cubejs-testing-drivers/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/testing-drivers + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/testing-drivers diff --git a/packages/cubejs-testing-drivers/package.json b/packages/cubejs-testing-drivers/package.json index 99d1bb776d5d7..44b27057dc174 100644 --- a/packages/cubejs-testing-drivers/package.json +++ b/packages/cubejs-testing-drivers/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-backend/testing-drivers", - "version": "1.7.31", + "version": "1.7.32", "description": "Cube.js drivers test suite", "author": "Cube Dev, Inc.", "repository": { @@ -87,29 +87,29 @@ "dist/src" ], "dependencies": { - "@cubejs-backend/athena-driver": "1.7.31", - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/bigquery-driver": "1.7.31", - "@cubejs-backend/clickhouse-driver": "1.7.31", - "@cubejs-backend/crate-driver": "1.7.31", - "@cubejs-backend/cubestore-driver": "1.7.31", - "@cubejs-backend/databricks-jdbc-driver": "1.7.31", + "@cubejs-backend/athena-driver": "1.7.32", + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/bigquery-driver": "1.7.32", + "@cubejs-backend/clickhouse-driver": "1.7.32", + "@cubejs-backend/crate-driver": "1.7.32", + "@cubejs-backend/cubestore-driver": "1.7.32", + "@cubejs-backend/databricks-jdbc-driver": "1.7.32", "@cubejs-backend/dotenv": "^9.0.2", - "@cubejs-backend/linter": "1.7.31", - "@cubejs-backend/mssql-driver": "1.7.31", - "@cubejs-backend/mysql-driver": "1.7.31", - "@cubejs-backend/oracle-driver": "1.7.31", - "@cubejs-backend/pinot-driver": "1.7.31", - "@cubejs-backend/postgres-driver": "1.7.31", - "@cubejs-backend/query-orchestrator": "1.7.31", - "@cubejs-backend/questdb-driver": "1.7.31", - "@cubejs-backend/server-core": "1.7.31", - "@cubejs-backend/shared": "1.7.31", - "@cubejs-backend/snowflake-driver": "1.7.31", - "@cubejs-backend/testing-shared": "1.7.31", - "@cubejs-backend/trino-driver": "1.7.31", - "@cubejs-client/core": "1.7.31", - "@cubejs-client/ws-transport": "1.7.31", + "@cubejs-backend/linter": "1.7.32", + "@cubejs-backend/mssql-driver": "1.7.32", + "@cubejs-backend/mysql-driver": "1.7.32", + "@cubejs-backend/oracle-driver": "1.7.32", + "@cubejs-backend/pinot-driver": "1.7.32", + "@cubejs-backend/postgres-driver": "1.7.32", + "@cubejs-backend/query-orchestrator": "1.7.32", + "@cubejs-backend/questdb-driver": "1.7.32", + "@cubejs-backend/server-core": "1.7.32", + "@cubejs-backend/shared": "1.7.32", + "@cubejs-backend/snowflake-driver": "1.7.32", + "@cubejs-backend/testing-shared": "1.7.32", + "@cubejs-backend/trino-driver": "1.7.32", + "@cubejs-client/core": "1.7.32", + "@cubejs-client/ws-transport": "1.7.32", "@jest/globals": "^29", "@types/jest": "^29", "@types/node": "^22", diff --git a/packages/cubejs-testing-shared/CHANGELOG.md b/packages/cubejs-testing-shared/CHANGELOG.md index 44c565adf37d8..525dd9da71d0c 100644 --- a/packages/cubejs-testing-shared/CHANGELOG.md +++ b/packages/cubejs-testing-shared/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/testing-shared + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) ### Bug Fixes diff --git a/packages/cubejs-testing-shared/package.json b/packages/cubejs-testing-shared/package.json index 2ce65227996fa..aab62e81b0874 100644 --- a/packages/cubejs-testing-shared/package.json +++ b/packages/cubejs-testing-shared/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-backend/testing-shared", - "version": "1.7.31", + "version": "1.7.32", "description": "Cube.js Testing Helpers", "author": "Cube Dev, Inc.", "repository": { @@ -26,16 +26,16 @@ ], "dependencies": { "@cubejs-backend/dotenv": "^9.0.2", - "@cubejs-backend/query-orchestrator": "1.7.31", - "@cubejs-backend/schema-compiler": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/query-orchestrator": "1.7.32", + "@cubejs-backend/schema-compiler": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "@testcontainers/kafka": "~10.28.0", "dedent": "^0.7.0", "node-fetch": "^2.6.7", "testcontainers": "^10.28.0" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.31", + "@cubejs-backend/linter": "1.7.32", "@jest/globals": "^29", "@types/dedent": "^0.7.0", "@types/jest": "^29", diff --git a/packages/cubejs-testing/CHANGELOG.md b/packages/cubejs-testing/CHANGELOG.md index bb9d27eb309a2..3759f78cc385b 100644 --- a/packages/cubejs-testing/CHANGELOG.md +++ b/packages/cubejs-testing/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/testing + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/testing diff --git a/packages/cubejs-testing/package.json b/packages/cubejs-testing/package.json index eb759b5c53074..b9b692c931fd9 100644 --- a/packages/cubejs-testing/package.json +++ b/packages/cubejs-testing/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-backend/testing", - "version": "1.7.31", + "version": "1.7.32", "description": "Cube.js e2e tests", "author": "Cube Dev, Inc.", "repository": { @@ -92,15 +92,15 @@ "birdbox-fixtures" ], "dependencies": { - "@cubejs-backend/cubestore-driver": "1.7.31", + "@cubejs-backend/cubestore-driver": "1.7.32", "@cubejs-backend/dotenv": "^9.0.2", - "@cubejs-backend/ksql-driver": "1.7.31", - "@cubejs-backend/postgres-driver": "1.7.31", - "@cubejs-backend/query-orchestrator": "1.7.31", - "@cubejs-backend/schema-compiler": "1.7.31", - "@cubejs-backend/shared": "1.7.31", - "@cubejs-backend/testing-shared": "1.7.31", - "@cubejs-client/ws-transport": "1.7.31", + "@cubejs-backend/ksql-driver": "1.7.32", + "@cubejs-backend/postgres-driver": "1.7.32", + "@cubejs-backend/query-orchestrator": "1.7.32", + "@cubejs-backend/schema-compiler": "1.7.32", + "@cubejs-backend/shared": "1.7.32", + "@cubejs-backend/testing-shared": "1.7.32", + "@cubejs-client/ws-transport": "1.7.32", "dedent": "^0.7.0", "fs-extra": "^8.1.0", "http-proxy": "^1.18.1", @@ -111,8 +111,8 @@ }, "devDependencies": { "@4tw/cypress-drag-drop": "^1.6.0", - "@cubejs-backend/linter": "1.7.31", - "@cubejs-client/core": "1.7.31", + "@cubejs-backend/linter": "1.7.32", + "@cubejs-client/core": "1.7.32", "@jest/globals": "^29", "@types/dedent": "^0.7.0", "@types/http-proxy": "^1.17.5", diff --git a/packages/cubejs-trino-driver/CHANGELOG.md b/packages/cubejs-trino-driver/CHANGELOG.md index f04447edc4794..d5f517fa9bfb3 100644 --- a/packages/cubejs-trino-driver/CHANGELOG.md +++ b/packages/cubejs-trino-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/trino-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/trino-driver diff --git a/packages/cubejs-trino-driver/package.json b/packages/cubejs-trino-driver/package.json index c742f72c1377a..df9bf85893aa1 100644 --- a/packages/cubejs-trino-driver/package.json +++ b/packages/cubejs-trino-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/trino-driver", "description": "Cube.js Trino database driver", "author": "Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -28,10 +28,10 @@ "lint:fix": "eslint --fix src/* --ext .ts" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/prestodb-driver": "1.7.31", - "@cubejs-backend/schema-compiler": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/prestodb-driver": "1.7.32", + "@cubejs-backend/schema-compiler": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "node-fetch": "^2.6.1", "presto-client": "^1.2.0" }, @@ -40,7 +40,7 @@ "access": "public" }, "devDependencies": { - "@cubejs-backend/linter": "1.7.31", + "@cubejs-backend/linter": "1.7.32", "@types/jest": "^29", "jest": "^29", "testcontainers": "^10.28.0", diff --git a/packages/cubejs-vertica-driver/CHANGELOG.md b/packages/cubejs-vertica-driver/CHANGELOG.md index 23da6e1dd563b..689d69b3fe203 100644 --- a/packages/cubejs-vertica-driver/CHANGELOG.md +++ b/packages/cubejs-vertica-driver/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/vertica-driver + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) **Note:** Version bump only for package @cubejs-backend/vertica-driver diff --git a/packages/cubejs-vertica-driver/package.json b/packages/cubejs-vertica-driver/package.json index e611a3a235699..6cc8f53e31546 100644 --- a/packages/cubejs-vertica-driver/package.json +++ b/packages/cubejs-vertica-driver/package.json @@ -2,7 +2,7 @@ "name": "@cubejs-backend/vertica-driver", "description": "Cube.js Vertica database driver", "author": "Eduard Karacharov, Tim Brown, Cube Dev, Inc.", - "version": "1.7.31", + "version": "1.7.32", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", @@ -19,16 +19,16 @@ "lint:fix": "eslint --fix **/*.js" }, "dependencies": { - "@cubejs-backend/base-driver": "1.7.31", - "@cubejs-backend/query-orchestrator": "1.7.31", - "@cubejs-backend/schema-compiler": "1.7.31", - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/base-driver": "1.7.32", + "@cubejs-backend/query-orchestrator": "1.7.32", + "@cubejs-backend/schema-compiler": "1.7.32", + "@cubejs-backend/shared": "1.7.32", "vertica-nodejs": "^1.0.3" }, "license": "Apache-2.0", "devDependencies": { - "@cubejs-backend/linter": "1.7.31", - "@cubejs-backend/testing-shared": "1.7.31", + "@cubejs-backend/linter": "1.7.32", + "@cubejs-backend/testing-shared": "1.7.32", "@types/jest": "^29", "jest": "^29", "testcontainers": "^10.28.0" diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/time_series.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/time_series.rs index 10864a5d1ef20..308e037f2c15b 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/time_series.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/time_series.rs @@ -15,6 +15,15 @@ pub struct MultiStageTimeSeries { date_range: Option>, #[builder(default)] get_date_range_multistage_ref: Option, + /// Query over the calendar cube supplying the period each series point + /// belongs to, for `to_date` windows whose granularity defines its own SQL. + /// `None` when every window on this series bounds itself by interval math. + #[builder(default)] + calendar_source: Option>, + /// The time dimension at each granularity `calendar_source` projects a + /// period for. + #[builder(default)] + period_dimensions: Vec>, } impl MultiStageTimeSeries { @@ -29,6 +38,14 @@ impl MultiStageTimeSeries { pub fn get_date_range_multistage_ref(&self) -> &Option { &self.get_date_range_multistage_ref } + + pub fn calendar_source(&self) -> &Option> { + &self.calendar_source + } + + pub fn period_dimensions(&self) -> &Vec> { + &self.period_dimensions + } } impl PrettyPrint for MultiStageTimeSeries { @@ -45,6 +62,23 @@ impl PrettyPrint for MultiStageTimeSeries { &state, ); } + if !self.period_dimensions.is_empty() { + result.println( + &format!( + "period_dimensions: {}", + self.period_dimensions + .iter() + .map(|d| d.full_name()) + .collect::>() + .join(", ") + ), + &state, + ); + } + if let Some(calendar_source) = self.calendar_source() { + result.println("calendar_source:", &state); + calendar_source.pretty_print(result, &state.new_level()); + } if let Some(get_date_range_multistage_ref) = self.get_date_range_multistage_ref() { result.println( &format!( @@ -63,12 +97,26 @@ impl LogicalNode for MultiStageTimeSeries { } fn inputs(&self) -> Vec { - vec![] // MultiStageTimeSeries has no inputs + self.calendar_source + .iter() + .map(|source| source.as_plan_node()) + .collect() } fn with_inputs(self: Rc, inputs: Vec) -> Result, CubeError> { - check_inputs_len(&inputs, 0, self.node_name())?; - Ok(self) + let expected = if self.calendar_source.is_some() { 1 } else { 0 }; + check_inputs_len(&inputs, expected, self.node_name())?; + if let Some(source) = inputs.into_iter().next() { + Ok(Rc::new(Self { + time_dimension: self.time_dimension.clone(), + date_range: self.date_range.clone(), + get_date_range_multistage_ref: self.get_date_range_multistage_ref.clone(), + calendar_source: Some(source.into_logical_node()?), + period_dimensions: self.period_dimensions.clone(), + })) + } else { + Ok(self) + } } fn referenced_cte_names(&self) -> Vec { diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/filter/operators/filter_sql_context.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/filter/operators/filter_sql_context.rs index 473a5f8db9804..6784f31b998dc 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/filter/operators/filter_sql_context.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/filter/operators/filter_sql_context.rs @@ -182,44 +182,33 @@ impl<'a> FilterSqlContext<'a> { } pub fn date_range_from_time_series(&self) -> Result<(String, String), CubeError> { - let from_expr = format!( - "min({})", - self.plan_templates.quote_identifier("date_from")? + Ok(( + self.time_series_bound("min", "date_from")?, + self.time_series_bound("max", "date_to")?, + )) + } + + /// Scalar sub-select of `aggregate(column)` over the time series driving + /// this rolling window. + pub fn time_series_bound(&self, aggregate: &str, column: &str) -> Result { + let expr = format!( + "{}({})", + aggregate, + self.plan_templates.quote_identifier(column)? ); - let to_expr = format!("max({})", self.plan_templates.quote_identifier("date_to")?); - let from_expr = self.plan_templates.series_bounds_cast(&from_expr)?; - let to_expr = self.plan_templates.series_bounds_cast(&to_expr)?; + let expr = self.plan_templates.series_bounds_cast(&expr)?; let alias = "value".to_string(); - let time_series_cte_name = "time_series".to_string(); - let from_column = TemplateProjectionColumn { - expr: from_expr.clone(), - alias: alias.clone(), - aliased: self.plan_templates.column_aliased(&from_expr, &alias)?, - }; - let to_column = TemplateProjectionColumn { - expr: to_expr.clone(), + let projection = TemplateProjectionColumn { + expr: expr.clone(), alias: alias.clone(), - aliased: self.plan_templates.column_aliased(&to_expr, &alias)?, + aliased: self.plan_templates.column_aliased(&expr, &alias)?, }; - let from = self.plan_templates.select( - vec![], - &time_series_cte_name, - vec![from_column], - None, - vec![], - None, - vec![], - None, - None, - false, - false, - )?; - let to = self.plan_templates.select( + let select = self.plan_templates.select( vec![], - &time_series_cte_name, - vec![to_column], + "time_series", + vec![projection], None, vec![], None, @@ -229,7 +218,7 @@ impl<'a> FilterSqlContext<'a> { false, false, )?; - Ok((format!("({})", from), format!("({})", to))) + Ok(format!("({})", select)) } pub fn extend_date_range_bound( diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/filter/operators/to_date_rolling_window.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/filter/operators/to_date_rolling_window.rs index 69ea9a7bb1144..cd1502501d9dd 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/filter/operators/to_date_rolling_window.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/filter/operators/to_date_rolling_window.rs @@ -1,4 +1,5 @@ use super::{FilterOperationSql, FilterSqlContext}; +use crate::physical_plan::TimeSeries; use crate::planner::filter::operators::to_date_rolling_window::ToDateRollingWindowOp; use cubenativeutils::CubeError; @@ -6,9 +7,17 @@ impl FilterOperationSql for ToDateRollingWindowOp { fn to_sql(&self, ctx: &FilterSqlContext) -> Result { let (from, to) = ctx.date_range_from_time_series()?; - let from = self - .granularity - .apply_to_input_sql(ctx.plan_templates, from)?; + // A calendar period cannot be derived from a timestamp — the series + // carries the boundary it read off the calendar cube. + let from = if self.granularity.calendar_sql().is_some() { + ctx.time_series_bound( + "min", + &TimeSeries::period_start_column(self.granularity.granularity()), + )? + } else { + self.granularity + .apply_to_input_sql(ctx.plan_templates, from)? + }; let date_field = ctx.convert_tz(ctx.member_sql())?; ctx.plan_templates.time_range_filter(date_field, from, to) diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/join.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/join.rs index b97fb8d68cfe3..62bf279a529d7 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/join.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/join.rs @@ -1,4 +1,4 @@ -use super::{Expr, SingleAliasedSource, VisitorContext}; +use super::{Expr, SingleAliasedSource, TimeSeries, VisitorContext}; use crate::planner::query_tools::QueryTools; use crate::planner::sql_templates::PlanSqlTemplates; use crate::planner::{BaseJoinCondition, Granularity}; @@ -127,7 +127,17 @@ impl ToDateRollingWindowJoinCondition { templates.column_reference(&Some(self.time_series_source.clone()), "date_to")?; let date_from = templates.rolling_window_expr_timestamp_cast(&date_from)?; let date_to = templates.rolling_window_expr_timestamp_cast(&date_to)?; - let grouped_from = self.granularity.apply_to_input_sql(templates, date_from)?; + // A calendar period cannot be derived from a timestamp — the series + // carries the boundary it read off the calendar cube. + let grouped_from = if self.granularity.calendar_sql().is_some() { + let period_start = templates.column_reference( + &Some(self.time_series_source.clone()), + &TimeSeries::period_start_column(self.granularity.granularity()), + )?; + templates.rolling_window_expr_timestamp_cast(&period_start)? + } else { + self.granularity.apply_to_input_sql(templates, date_from)? + }; let result = format!("{date_column} >= {grouped_from} and {date_column} <= {date_to}"); Ok(result) } diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/mod.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/mod.rs index a08db407095c0..db94dc96cd6b5 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/mod.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/mod.rs @@ -33,6 +33,6 @@ pub use references_builder::ReferencesBuilder; pub use schema::{QualifiedColumnName, Schema, SchemaColumn}; pub use select::{AliasedExpr, Select}; pub use sql_visitor::SqlEvaluatorVisitor; -pub use time_series::{TimeSeries, TimeSeriesDateRange}; +pub use time_series::{CalendarPeriodSource, TimeSeries, TimeSeriesDateRange, TimeSeriesSource}; pub use union::Union; pub use visitor_context::{evaluate_sql_call_with_context, evaluate_with_context, VisitorContext}; diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/time_dimension.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/time_dimension.rs index 9631e7c7c827c..f898f57cd1369 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/time_dimension.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/sql_nodes/time_dimension.rs @@ -71,9 +71,15 @@ impl SqlNode for TimeDimensionNode { } } MemberSymbol::Dimension(ev) => { + // Only the cube owning the column reads it from the database, so + // only there is a conversion needed. A dimension that merely + // references another one renders through the owning symbol, + // which converts on its own; converting here as well would + // shift the value twice. let wraps_convert_tz = !visitor.ignore_tz_convert() && query_tools.convert_tz_for_raw_time_dimension() - && ev.dimension_type() == "time"; + && ev.dimension_type() == "time" + && ev.owned_by_cube(); if wraps_convert_tz { let inner_visitor = visitor.with_arg_needs_paren_safe(false); let input_sql = self.input.to_sql( diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/time_series.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/time_series.rs index 5205e40a00d9e..ffd85a8808286 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/time_series.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan/time_series.rs @@ -1,4 +1,5 @@ -use super::{Schema, SchemaColumn}; +use super::{QueryPlan, Schema, SchemaColumn}; +use crate::planner::sql_templates::TemplateProjectionColumn; use crate::planner::{sql_templates::PlanSqlTemplates, Granularity, MemberSymbol, QueryTimeSeries}; use cubenativeutils::CubeError; use std::rc::Rc; @@ -6,7 +7,7 @@ use std::rc::Rc; pub struct TimeSeries { #[allow(dead_code)] time_dimension_name: String, - date_range: TimeSeriesDateRange, + source: TimeSeriesSource, granularity: Granularity, schema: Rc, } @@ -16,10 +17,45 @@ pub enum TimeSeriesDateRange { Generated(String), // Name of cte with min/max dates } +/// Series points read off a calendar cube, each paired with the period it falls +/// into. Bounds a `to_date` window whose granularity defines its own SQL, which +/// no interval math can reproduce. +pub struct CalendarPeriodSource { + source: Rc, + date_from_alias: String, + /// Granularity name paired with the column `source` projects it as. + period_aliases: Vec<(String, String)>, + /// The range the series is restricted to. Applied outside the select that + /// derives the period bounds, so that select still sees the period + /// following the last one in range. + range: TimeSeriesDateRange, +} + +impl CalendarPeriodSource { + pub fn new( + source: Rc, + date_from_alias: String, + period_aliases: Vec<(String, String)>, + range: TimeSeriesDateRange, + ) -> Self { + Self { + source, + date_from_alias, + period_aliases, + range, + } + } +} + +pub enum TimeSeriesSource { + Range(TimeSeriesDateRange), + Calendar(CalendarPeriodSource), +} + impl TimeSeries { pub fn new( time_dimension: &Rc, - date_range: TimeSeriesDateRange, + source: TimeSeriesSource, granularity: Granularity, ) -> Self { let column = SchemaColumn::new(format!("date_from"), Some(time_dimension.clone())); @@ -27,7 +63,7 @@ impl TimeSeries { Self { time_dimension_name: time_dimension.full_name(), granularity, - date_range, + source, schema, } } @@ -36,7 +72,18 @@ impl TimeSeries { self.schema.clone() } + /// Column the series exposes the start of `granularity`'s period as. + pub fn period_start_column(granularity: &str) -> String { + format!("date_period_start_{}", granularity) + } + pub fn to_sql(&self, templates: &PlanSqlTemplates) -> Result { + let date_range = match &self.source { + TimeSeriesSource::Calendar(calendar) => { + return self.calendar_to_sql(calendar, templates) + } + TimeSeriesSource::Range(date_range) => date_range, + }; if templates.supports_generated_time_series(self.granularity.is_predefined_granularity())? { let interval_description = templates .interval_and_minimal_time_unit(self.granularity.granularity_interval().to_sql())?; @@ -47,7 +94,7 @@ impl TimeSeries { } let interval = interval_description[0].clone(); let minimal_time_unit = interval_description[1].clone(); - match &self.date_range { + match date_range { TimeSeriesDateRange::Filter(from_date, to_date) => { let start = templates.quote_string(from_date)?; let date_field = templates.quote_identifier("d")?; @@ -83,7 +130,7 @@ impl TimeSeries { } } } else { - let (from_date, to_date, raw_from_date, raw_to_date) = match &self.date_range { + let (from_date, to_date, raw_from_date, raw_to_date) = match date_range { TimeSeriesDateRange::Filter(from_date, to_date) => ( format!("'{}'", from_date), format!("'{}'", to_date), @@ -115,4 +162,149 @@ impl TimeSeries { templates.time_series_select(from_date.clone(), to_date.clone(), series) } } + + fn calendar_to_sql( + &self, + calendar: &CalendarPeriodSource, + templates: &PlanSqlTemplates, + ) -> Result { + let bounds_alias = "calendar_series".to_string(); + let bounds = self.calendar_period_bounds_to_sql(calendar, templates, &bounds_alias)?; + + let date_from = templates.column_reference(&Some(bounds_alias.clone()), "date_from")?; + let date_to = templates.column_reference(&Some(bounds_alias.clone()), "date_to")?; + let mut columns = vec![ + Self::projection_column(templates, &date_from, "date_from")?, + Self::projection_column(templates, &date_to, "date_to")?, + ]; + for (granularity, _) in calendar.period_aliases.iter() { + let name = Self::period_start_column(granularity); + let column = templates.column_reference(&Some(bounds_alias.clone()), &name)?; + columns.push(Self::projection_column(templates, &column, &name)?); + } + + let (range_from, range_to) = match &calendar.range { + TimeSeriesDateRange::Filter(from_date, to_date) => ( + templates.time_stamp_cast(templates.quote_string(from_date)?)?, + templates.time_stamp_cast(templates.quote_string(to_date)?)?, + ), + TimeSeriesDateRange::Generated(range_cte) => ( + Self::range_cte_bound(templates, range_cte, "min_date")?, + Self::range_cte_bound(templates, range_cte, "max_date")?, + ), + }; + + templates.select( + vec![], + &templates.query_aliased(&format!("({})", bounds), &bounds_alias)?, + columns, + // Kept by overlap rather than by where it starts: the period a range + // opens inside of opens before the range does. + Some(format!( + "{date_to} >= {range_from} AND {date_from} <= {range_to}" + )), + vec![], + None, + vec![], + None, + None, + false, + false, + ) + } + + /// Pairs every point of the calendar with the end of the period it opens. + /// Deliberately unrestricted by the query range: a period ends where the + /// next one starts, so the point past the range is what bounds the last one + /// inside it. + fn calendar_period_bounds_to_sql( + &self, + calendar: &CalendarPeriodSource, + templates: &PlanSqlTemplates, + outer_alias: &str, + ) -> Result { + let interval_description = templates + .interval_and_minimal_time_unit(self.granularity.granularity_interval().to_sql())?; + if interval_description.len() != 2 { + return Err(CubeError::internal( + "Interval description must have 2 elements".to_string(), + )); + } + let interval = interval_description[0].clone(); + + let source_alias = format!("{}_source", outer_alias); + let date_from = + templates.column_reference(&Some(source_alias.clone()), &calendar.date_from_alias)?; + let date_from = templates.time_stamp_cast(date_from)?; + + let nominal_end = format!("({})", templates.add_interval(date_from.clone(), interval)?); + // Only the genuine last point of the calendar has no next period to end + // on, and a nominal interval is all that is left to bound it by. + let period_end = if self.granularity.calendar_sql().is_some() { + let next_start = templates.window_function( + &format!("LEAD({})", date_from), + "", + &format!("{} ASC", date_from), + "", + )?; + format!("COALESCE({}, {})", next_start, nominal_end) + } else { + nominal_end + }; + let date_to = templates.subtract_interval(period_end, "1 millisecond".to_string())?; + + let mut columns = vec![ + Self::projection_column(templates, &date_from, "date_from")?, + Self::projection_column(templates, &date_to, "date_to")?, + ]; + for (granularity, alias) in calendar.period_aliases.iter() { + let column = templates.column_reference(&Some(source_alias.clone()), alias)?; + columns.push(Self::projection_column( + templates, + &column, + &Self::period_start_column(granularity), + )?); + } + + templates.select( + vec![], + &templates.query_aliased( + &format!("({})", calendar.source.to_sql(templates)?), + &source_alias, + )?, + columns, + None, + vec![], + None, + vec![], + None, + None, + false, + false, + ) + } + + fn projection_column( + templates: &PlanSqlTemplates, + expr: &str, + alias: &str, + ) -> Result { + Ok(TemplateProjectionColumn { + expr: expr.to_string(), + alias: alias.to_string(), + aliased: templates.column_aliased(expr, alias)?, + }) + } + + fn range_cte_bound( + templates: &PlanSqlTemplates, + range_cte: &str, + column: &str, + ) -> Result { + Ok(format!( + "(SELECT {} FROM {})", + templates.quote_identifier(column)?, + range_cte + )) + } } diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/processors/multi_stage_time_series.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/processors/multi_stage_time_series.rs index d801d51e50170..8ed4b206b41ff 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/processors/multi_stage_time_series.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/processors/multi_stage_time_series.rs @@ -1,7 +1,9 @@ use super::super::context::PushDownBuilderContext; use super::super::{LogicalNodeProcessor, ProcessableNode}; use crate::logical_plan::MultiStageTimeSeries; -use crate::physical_plan::{QueryPlan, TimeSeries, TimeSeriesDateRange}; +use crate::physical_plan::{ + CalendarPeriodSource, QueryPlan, TimeSeries, TimeSeriesDateRange, TimeSeriesSource, +}; use crate::physical_plan_builder::PhysicalPlanBuilder; use cubenativeutils::CubeError; use std::rc::Rc; @@ -19,7 +21,7 @@ impl<'a> LogicalNodeProcessor<'a, MultiStageTimeSeries> for MultiStageTimeSeries fn process( &self, time_series: &MultiStageTimeSeries, - _context: &PushDownBuilderContext, + context: &PushDownBuilderContext, ) -> Result { let (query_tools, plan_sql_templates) = self.builder.qtools_and_templates(); let time_dimension = time_series.time_dimension().clone(); @@ -34,6 +36,59 @@ impl<'a> LogicalNodeProcessor<'a, MultiStageTimeSeries> for MultiStageTimeSeries )); }; + if let Some(calendar_source) = time_series.calendar_source() { + let source = self + .builder + .process_node(calendar_source.as_ref(), context)?; + let schema = source.schema(); + let date_from_alias = schema.resolve_member_alias(&time_dimension); + let period_aliases = time_series + .period_dimensions() + .iter() + .map(|dimension| { + let Some(granularity) = dimension.as_time_dimension()?.granularity().clone() + else { + return Err(CubeError::internal(format!( + "Calendar period dimension '{}' must have a granularity", + dimension.full_name() + ))); + }; + Ok((granularity, schema.resolve_member_alias(dimension))) + }) + .collect::, CubeError>>()?; + + // Taken raw: the aligned range snaps its start to the granularity's + // interval, which for a calendar period means an arbitrary point + // inside the period the range opens in. + let range = if let Some(date_range) = &date_range { + if date_range.len() != 2 { + return Err(CubeError::user(format!( + "Invalid date range: {:?}", + date_range + ))); + } + TimeSeriesDateRange::Filter(date_range[0].clone(), date_range[1].clone()) + } else if let Some(date_range_cte) = time_series.get_date_range_multistage_ref() { + TimeSeriesDateRange::Generated(date_range_cte.clone()) + } else { + return Err(CubeError::internal( + "Date range cte is required for time series without date range".to_string(), + )); + }; + + let time_series = TimeSeries::new( + &time_dimension, + TimeSeriesSource::Calendar(CalendarPeriodSource::new( + Rc::new(QueryPlan::Select(source)), + date_from_alias, + period_aliases, + range, + )), + granularity_obj, + ); + return Ok(QueryPlan::TimeSeries(Rc::new(time_series))); + } + let ts_date_range = if plan_sql_templates .supports_generated_time_series(granularity_obj.is_predefined_granularity())? { @@ -60,7 +115,11 @@ impl<'a> LogicalNodeProcessor<'a, MultiStageTimeSeries> for MultiStageTimeSeries } }; - let time_series = TimeSeries::new(&time_dimension, ts_date_range, granularity_obj); + let time_series = TimeSeries::new( + &time_dimension, + TimeSeriesSource::Range(ts_date_range), + granularity_obj, + ); let query_plan = QueryPlan::TimeSeries(Rc::new(time_series)); Ok(query_plan) } diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member.rs index 044a33a07d2f7..9479ab51f1e0a 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member.rs @@ -1,4 +1,5 @@ use crate::planner::{MeasureTimeShifts, MemberSymbol, MultiStageGrain}; +use std::cell::RefCell; use std::rc::Rc; /// Description of the time-series CTE driving a rolling-window @@ -8,6 +9,16 @@ use std::rc::Rc; pub struct TimeSeriesDescription { pub time_dimension: Rc, pub date_range_cte: Option, + /// Granularities of the `to_date` rolling windows driven by this series + /// whose period boundary is a calendar column rather than interval math. + /// The series carries one boundary column per granularity, and the list + /// grows as further rolling windows attach to the same series. + /// + /// Load-bearing: every description is built before any is planned, so a + /// window registering here is always visible to the series. Planning a + /// description as soon as it is built would drop the boundary columns of + /// every window registered after it. + pub calendar_period_granularities: Rc>>, } /// Kind of leaf CTE in a multi-stage chain: a base measure query, diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member_query_planner.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member_query_planner.rs index f5d4849cad38e..5afa41ddaacf3 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member_query_planner.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member_query_planner.rs @@ -9,6 +9,7 @@ use crate::planner::symbols::transforms; use crate::planner::GranularityHelper; use crate::planner::MemberSymbol; use crate::planner::MultiStageGrain; +use crate::planner::TimeDimensionSymbol; use crate::planner::{OrderByItem, QueryProperties}; use cubenativeutils::CubeError; @@ -60,7 +61,7 @@ impl MultiStageMemberQueryPlanner { MultiStageMemberType::Leaf(node) => match node { super::MultiStageLeafMemberType::Measure => self.plan_for_leaf_cte_query(scope), super::MultiStageLeafMemberType::TimeSeries(time_dimension) => { - self.plan_time_series_query(time_dimension.clone()) + self.plan_time_series_query(time_dimension.clone(), scope) } super::MultiStageLeafMemberType::TimeSeriesGetRange(time_dimension) => { self.plan_time_series_get_range_query(time_dimension.clone(), scope) @@ -112,12 +113,21 @@ impl MultiStageMemberQueryPlanner { fn plan_time_series_query( &self, time_series_description: Rc, + scope: &mut PlanningScope, ) -> Result, CubeError> { let time_dimension = time_series_description.time_dimension.clone(); + let period_dimensions = self.calendar_period_dimensions(&time_series_description)?; + let calendar_source = if period_dimensions.is_empty() { + None + } else { + Some(self.plan_calendar_period_source(&time_dimension, &period_dimensions, scope)?) + }; let result = MultiStageTimeSeries::builder() .time_dimension(time_dimension.clone()) .date_range(time_dimension.as_time_dimension()?.date_range_vec()) .get_date_range_multistage_ref(time_series_description.date_range_cte.clone()) + .calendar_source(calendar_source) + .period_dimensions(period_dimensions) .build(); Ok(Rc::new(LogicalMultiStageMember { name: self.description.alias().clone(), @@ -125,6 +135,77 @@ impl MultiStageMemberQueryPlanner { })) } + /// The series time dimension re-granularized to each calendar granularity + /// a `to_date` window on this series bounds itself by. + fn calendar_period_dimensions( + &self, + time_series_description: &Rc, + ) -> Result>, CubeError> { + let time_dimension = time_series_description.time_dimension.as_time_dimension()?; + let granularities = time_series_description + .calendar_period_granularities + .borrow(); + + let evaluator_compiler_cell = self.query_tools.compiler().clone(); + let mut evaluator_compiler = evaluator_compiler_cell.borrow_mut(); + + granularities + .iter() + .map(|granularity| { + let granularity_obj = GranularityHelper::make_granularity_obj( + self.query_tools.cube_evaluator().clone(), + &mut evaluator_compiler, + &time_dimension.cube_name(), + &time_dimension.name(), + Some(granularity.clone()), + )?; + Ok(MemberSymbol::new_time_dimension(TimeDimensionSymbol::new( + time_dimension.base_symbol().clone(), + Some(granularity.clone()), + granularity_obj, + time_dimension + .date_range_vec() + .map(|range| (range[0].clone(), range[1].clone())), + ))) + }) + .collect() + } + + /// A query over the calendar cube pairing every point of the series with + /// the period it falls into, so a `to_date` window can bound itself by the + /// calendar instead of by interval math. + fn plan_calendar_period_source( + &self, + time_dimension: &Rc, + period_dimensions: &[Rc], + scope: &mut PlanningScope, + ) -> Result, CubeError> { + // The series may already be granularized to the period a window bounds + // itself by; projecting it twice makes the column ambiguous. + let mut time_dimensions = vec![time_dimension.clone()]; + for period_dimension in period_dimensions { + if !time_dimensions + .iter() + .any(|dimension| dimension.full_name() == period_dimension.full_name()) + { + time_dimensions.push(period_dimension.clone()); + } + } + + // Left unfiltered on purpose: the series restricts itself to the query + // range only after it has read each period's end off the next point. + let cte_query_properties = QueryProperties::builder() + .query_tools(self.query_tools.clone()) + .time_dimensions(time_dimensions) + .ignore_cumulative(true) + .disable_external_pre_aggregations( + self.query_properties.disable_external_pre_aggregations(), + ) + .build()?; + + SimpleQueryPlanner::new(self.query_tools.clone(), cte_query_properties).plan(scope) + } + /// Builds the rolling-window CTE that combines a time-series /// input with a measure input, dispatching on /// `RollingWindowDescription` into the regular / to-date / diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/multi_stage_query_planner.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/multi_stage_query_planner.rs index 817ea556ff402..c885e4f8ee52b 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/multi_stage_query_planner.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/multi_stage_query_planner.rs @@ -30,6 +30,7 @@ use crate::planner::QueryProperties; use cubenativeutils::CubeError; use indexmap::IndexMap; use itertools::Itertools; +use std::cell::RefCell; use std::collections::HashSet; use std::rc::Rc; @@ -135,6 +136,9 @@ impl MultiStageQueryPlanner { } } + // Planning only after every description exists is load-bearing: a + // description may still be collecting requirements from its siblings, + // as a time series does from the rolling windows it drives. for descr in descriptions.into_iter() { let planner = MultiStageMemberQueryPlanner::new( self.query_tools.clone(), @@ -1003,8 +1007,15 @@ impl MultiStageQueryPlanner { Rc::new(extended) }; - let time_series = - self.add_time_series(time_dimension.clone(), state.clone(), descriptions)?; + let calendar_period_granularity = + self.calendar_to_date_granularity(&rolling_window, &time_dimension)?; + + let time_series = self.add_time_series( + time_dimension.clone(), + calendar_period_granularity, + state.clone(), + descriptions, + )?; let rolling_base = if !measure.is_multi_stage() { self.add_rolling_window_base( @@ -1116,12 +1127,22 @@ impl MultiStageQueryPlanner { fn add_time_series( &self, time_dimension: Rc, + calendar_period_granularity: Option, state: Rc, descriptions: &mut Vec>, ) -> Result, CubeError> { let description = if let Some(description) = descriptions.iter().find(|d| d.alias() == "time_series") { + // Rolling windows share one series, so a window joining an existing + // one has to register its own boundary column on it. + if let Some(granularity) = &calendar_period_granularity { + let granularities = Self::time_series_calendar_granularities(description)?; + let mut granularities = granularities.borrow_mut(); + if !granularities.contains(granularity) { + granularities.push(granularity.clone()); + } + } description.clone() } else { let get_range_query_description = if time_dimension @@ -1143,6 +1164,9 @@ impl MultiStageQueryPlanner { TimeSeriesDescription { time_dimension: time_dimension.clone(), date_range_cte: get_range_query_description.map(|d| d.alias().clone()), + calendar_period_granularities: Rc::new(RefCell::new( + calendar_period_granularity.into_iter().collect(), + )), }, ))), time_dimension.clone(), @@ -1189,6 +1213,47 @@ impl MultiStageQueryPlanner { Ok(description) } + /// The granularity of a `to_date` rolling window whose period boundary is + /// defined by a calendar column. `None` for a boundary that interval math + /// can compute on its own. + fn calendar_to_date_granularity( + &self, + rolling_window: &RollingWindow, + time_dimension: &Rc, + ) -> Result, CubeError> { + let Some(granularity) = self.get_to_date_rolling_granularity(rolling_window)? else { + return Ok(None); + }; + let time_dimension = time_dimension.as_time_dimension()?; + + let compiler_cell = self.query_tools.compiler().clone(); + let mut compiler = compiler_cell.borrow_mut(); + let granularity_obj = GranularityHelper::make_granularity_obj( + self.query_tools.cube_evaluator().clone(), + &mut compiler, + &time_dimension.cube_name(), + &time_dimension.name(), + Some(granularity.clone()), + )?; + + Ok(granularity_obj + .filter(|obj| obj.calendar_sql().is_some()) + .map(|_| granularity)) + } + + fn time_series_calendar_granularities( + description: &Rc, + ) -> Result>>, CubeError> { + match description.member().member_type() { + MultiStageMemberType::Leaf(MultiStageLeafMemberType::TimeSeries(time_series)) => { + Ok(time_series.calendar_period_granularities.clone()) + } + _ => Err(CubeError::internal( + "Time series description expected for the `time_series` cte".to_string(), + )), + } + } + /// Returns the granularity of a `to_date` rolling window. Errors /// if the window is declared as `to_date` without a granularity, /// and returns `None` for window kinds that don't carry one. diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_templates/plan.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_templates/plan.rs index c671d13f51af0..ada460f786f07 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_templates/plan.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_templates/plan.rs @@ -243,6 +243,24 @@ impl PlanSqlTemplates { ) } + pub fn window_function( + &self, + fun_call: &str, + partition_by_concat: &str, + order_by_concat: &str, + window_frame: &str, + ) -> Result { + self.render.render_template( + "expressions/window_function", + context! { + fun_call => fun_call, + partition_by_concat => partition_by_concat, + order_by_concat => order_by_concat, + window_frame => window_frame, + }, + ) + } + pub fn query_aliased(&self, query: &str, alias: &str) -> Result { let quoted_alias = self.quote_identifier(alias)?; self.render.render_template( diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/common/integration_calendar.yaml b/rust/cube/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/common/integration_calendar.yaml index 5adfd111fa52c..de7661fb063fe 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/common/integration_calendar.yaml +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/test_fixtures/schemas/yaml_files/common/integration_calendar.yaml @@ -90,6 +90,10 @@ cubes: - name: year sql: "{CUBE.retail_year_begin_date}" + # 4 or 5 weeks long, so no interval can reproduce it + - name: month + sql: "{CUBE}.retail_month_begin_date" + - name: week sql: "{CUBE.retail_week_begin_date}" @@ -223,3 +227,15 @@ cubes: sql: "{count}" time_shift: - name: one_year_common_interval + + - name: count_week_to_date + type: count + rolling_window: + type: to_date + granularity: week + + - name: count_month_to_date + type: count + rolling_window: + type: to_date + granularity: month diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/calendar.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/calendar.rs index 82058f6832ce8..30539c6eed199 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/calendar.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/calendar.rs @@ -327,3 +327,163 @@ async fn test_two_named_shifts() { insta::assert_snapshot!(result); } } + +// --- to_date windows bounded by the calendar --- +// +// The retail calendar starts 2024-02-04 with 7-day weeks and 4-5-4 months, so +// month 1 runs 2024-02-04..2024-03-02, month 2 2024-03-03..2024-04-06 (35 days) +// and month 3 2024-04-07..2024-05-04. None of those lengths is reachable from a +// nominal interval anchored anywhere. + +#[tokio::test(flavor = "multi_thread")] +async fn test_to_date_retail_week_by_day() { + let ctx = create_context(); + + let query = indoc! {r#" + measures: + - calendar_orders.count_week_to_date + time_dimensions: + - dimension: custom_calendar.date_val + granularity: day + dateRange: + - "2024-02-08" + - "2024-02-18" + order: + - id: custom_calendar.date_val + "#}; + + ctx.build_sql(query).unwrap(); + + if let Some(result) = ctx.try_execute_pg(query, SEED).await { + insta::assert_snapshot!(result); + } +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_to_date_retail_month_by_day() { + let ctx = create_context(); + + let query = indoc! {r#" + measures: + - calendar_orders.count_month_to_date + time_dimensions: + - dimension: custom_calendar.date_val + granularity: day + dateRange: + - "2024-02-29" + - "2024-03-09" + order: + - id: custom_calendar.date_val + "#}; + + ctx.build_sql(query).unwrap(); + + if let Some(result) = ctx.try_execute_pg(query, SEED).await { + insta::assert_snapshot!(result); + } +} + +/// A 5-week retail month grouped by itself: a nominal `1 month` upper bound +/// reaches past its end and folds the next month in. +#[tokio::test(flavor = "multi_thread")] +async fn test_to_date_retail_month_by_retail_month() { + let ctx = create_context(); + + let query = indoc! {r#" + measures: + - calendar_orders.count_month_to_date + - calendar_orders.count + time_dimensions: + - dimension: custom_calendar.date_val + granularity: month + dateRange: + - "2024-02-04" + - "2024-05-04" + order: + - id: custom_calendar.date_val + "#}; + + ctx.build_sql(query).unwrap(); + + if let Some(result) = ctx.try_execute_pg(query, SEED).await { + insta::assert_snapshot!(result); + } +} + +/// The range ends exactly where the 5-week month does, so the point that bounds +/// it lies outside the range. +#[tokio::test(flavor = "multi_thread")] +async fn test_to_date_range_ending_on_a_period_end() { + let ctx = create_context(); + + let query = indoc! {r#" + measures: + - calendar_orders.count_month_to_date + - calendar_orders.count + time_dimensions: + - dimension: custom_calendar.date_val + granularity: month + dateRange: + - "2024-03-03" + - "2024-04-06" + "#}; + + ctx.build_sql(query).unwrap(); + + if let Some(result) = ctx.try_execute_pg(query, SEED).await { + insta::assert_snapshot!(result); + } +} + +/// A range opening mid-period still has to see the period it opens inside. +#[tokio::test(flavor = "multi_thread")] +async fn test_to_date_range_opening_mid_period() { + let ctx = create_context(); + + let query = indoc! {r#" + measures: + - calendar_orders.count_month_to_date + - calendar_orders.count + time_dimensions: + - dimension: custom_calendar.date_val + granularity: month + dateRange: + - "2024-04-01" + - "2024-04-20" + order: + - id: custom_calendar.date_val + "#}; + + ctx.build_sql(query).unwrap(); + + if let Some(result) = ctx.try_execute_pg(query, SEED).await { + insta::assert_snapshot!(result); + } +} + +/// Both windows are driven by the same series, each by its own period: the +/// weekly one restarts on 2024-03-10 while the monthly one keeps accumulating. +#[tokio::test(flavor = "multi_thread")] +async fn test_to_date_week_and_month_together() { + let ctx = create_context(); + + let query = indoc! {r#" + measures: + - calendar_orders.count_week_to_date + - calendar_orders.count_month_to_date + time_dimensions: + - dimension: custom_calendar.date_val + granularity: day + dateRange: + - "2024-03-08" + - "2024-03-16" + order: + - id: custom_calendar.date_val + "#}; + + ctx.build_sql(query).unwrap(); + + if let Some(result) = ctx.try_execute_pg(query, SEED).await { + insta::assert_snapshot!(result); + } +} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__calendar__to_date_range_ending_on_a_period_end.snap b/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__calendar__to_date_range_ending_on_a_period_end.snap new file mode 100644 index 0000000000000..3d052f017b67d --- /dev/null +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__calendar__to_date_range_ending_on_a_period_end.snap @@ -0,0 +1,8 @@ +--- +source: cubesqlplanner/cubesqlplanner/src/tests/integration/calendar.rs +assertion_line: 434 +expression: result +--- +custom_calendar__date_val_month | calendar_orders__count_month_to_date | calendar_orders__count +--------------------------------+--------------------------------------+----------------------- +2024-03-03 00:00:00+00 | 5 | 5 diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__calendar__to_date_range_opening_mid_period.snap b/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__calendar__to_date_range_opening_mid_period.snap new file mode 100644 index 0000000000000..f1eef053d39a8 --- /dev/null +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__calendar__to_date_range_opening_mid_period.snap @@ -0,0 +1,9 @@ +--- +source: cubesqlplanner/cubesqlplanner/src/tests/integration/calendar.rs +assertion_line: 460 +expression: result +--- +custom_calendar__date_val_month | calendar_orders__count_month_to_date | calendar_orders__count +--------------------------------+--------------------------------------+----------------------- +2024-03-03 00:00:00+00 | 5 | 1 +2024-04-07 00:00:00+00 | 4 | 1 diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__calendar__to_date_retail_month_by_day.snap b/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__calendar__to_date_retail_month_by_day.snap new file mode 100644 index 0000000000000..aff59181c8b4c --- /dev/null +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__calendar__to_date_retail_month_by_day.snap @@ -0,0 +1,17 @@ +--- +source: cubesqlplanner/cubesqlplanner/src/tests/integration/calendar.rs +assertion_line: 382 +expression: result +--- +custom_calendar__date_val_day | calendar_orders__count_month_to_date +------------------------------+------------------------------------- +2024-02-29 00:00:00+00 | 3 +2024-03-01 00:00:00+00 | 3 +2024-03-02 00:00:00+00 | 3 +2024-03-03 00:00:00+00 | NULL +2024-03-04 00:00:00+00 | NULL +2024-03-05 00:00:00+00 | NULL +2024-03-06 00:00:00+00 | NULL +2024-03-07 00:00:00+00 | NULL +2024-03-08 00:00:00+00 | 1 +2024-03-09 00:00:00+00 | 1 diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__calendar__to_date_retail_month_by_retail_month.snap b/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__calendar__to_date_retail_month_by_retail_month.snap new file mode 100644 index 0000000000000..76863b408b13e --- /dev/null +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__calendar__to_date_retail_month_by_retail_month.snap @@ -0,0 +1,10 @@ +--- +source: cubesqlplanner/cubesqlplanner/src/tests/integration/calendar.rs +assertion_line: 409 +expression: result +--- +custom_calendar__date_val_month | calendar_orders__count_month_to_date | calendar_orders__count +--------------------------------+--------------------------------------+----------------------- +2024-02-04 00:00:00+00 | 3 | 3 +2024-03-03 00:00:00+00 | 5 | 5 +2024-04-07 00:00:00+00 | 4 | 4 diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__calendar__to_date_retail_week_by_day.snap b/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__calendar__to_date_retail_week_by_day.snap new file mode 100644 index 0000000000000..a0f1209f3fe24 --- /dev/null +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__calendar__to_date_retail_week_by_day.snap @@ -0,0 +1,18 @@ +--- +source: cubesqlplanner/cubesqlplanner/src/tests/integration/calendar.rs +assertion_line: 358 +expression: result +--- +custom_calendar__date_val_day | calendar_orders__count_week_to_date +------------------------------+------------------------------------ +2024-02-08 00:00:00+00 | 1 +2024-02-09 00:00:00+00 | 1 +2024-02-10 00:00:00+00 | 2 +2024-02-11 00:00:00+00 | NULL +2024-02-12 00:00:00+00 | NULL +2024-02-13 00:00:00+00 | NULL +2024-02-14 00:00:00+00 | NULL +2024-02-15 00:00:00+00 | NULL +2024-02-16 00:00:00+00 | NULL +2024-02-17 00:00:00+00 | 1 +2024-02-18 00:00:00+00 | NULL diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__calendar__to_date_week_and_month_together.snap b/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__calendar__to_date_week_and_month_together.snap new file mode 100644 index 0000000000000..d606cc952b28f --- /dev/null +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__calendar__to_date_week_and_month_together.snap @@ -0,0 +1,16 @@ +--- +source: cubesqlplanner/cubesqlplanner/src/tests/integration/calendar.rs +assertion_line: 461 +expression: result +--- +custom_calendar__date_val_day | calendar_orders__count_week_to_date | calendar_orders__count_month_to_date +------------------------------+-------------------------------------+------------------------------------- +2024-03-08 00:00:00+00 | 1 | 1 +2024-03-09 00:00:00+00 | 1 | 1 +2024-03-10 00:00:00+00 | NULL | 1 +2024-03-11 00:00:00+00 | NULL | 1 +2024-03-12 00:00:00+00 | NULL | 1 +2024-03-13 00:00:00+00 | NULL | 1 +2024-03-14 00:00:00+00 | NULL | 1 +2024-03-15 00:00:00+00 | 1 | 2 +2024-03-16 00:00:00+00 | 1 | 2 diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__views__view_raw_and_granular_time_dimension_converted_once_each.snap b/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__views__view_raw_and_granular_time_dimension_converted_once_each.snap new file mode 100644 index 0000000000000..6ad408b845347 --- /dev/null +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__views__view_raw_and_granular_time_dimension_converted_once_each.snap @@ -0,0 +1,14 @@ +--- +source: cubesqlplanner/cubesqlplanner/src/tests/integration/views.rs +expression: result +--- +orders_with_customers__created_at | orders_with_customers__created_at_day | orders_with_customers__count +----------------------------------+---------------------------------------+----------------------------- +2025-03-01 04:00:00 | 2025-03-01 00:00:00 | 1 +2025-03-02 05:00:00 | 2025-03-02 00:00:00 | 1 +2025-03-03 03:00:00 | 2025-03-03 00:00:00 | 1 +2025-03-04 08:00:00 | 2025-03-04 00:00:00 | 1 +2025-03-05 04:00:00 | 2025-03-05 00:00:00 | 1 +2025-03-06 02:00:00 | 2025-03-06 00:00:00 | 1 +2025-03-07 05:00:00 | 2025-03-07 00:00:00 | 1 +2025-03-08 03:00:00 | 2025-03-08 00:00:00 | 1 diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__views__view_raw_time_dimension_converted_once.snap b/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__views__view_raw_time_dimension_converted_once.snap new file mode 100644 index 0000000000000..1d8d23f6e3838 --- /dev/null +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/snapshots/cubesqlplanner__tests__integration__views__view_raw_time_dimension_converted_once.snap @@ -0,0 +1,14 @@ +--- +source: cubesqlplanner/cubesqlplanner/src/tests/integration/views.rs +expression: result +--- +orders_with_customers__created_at | orders_with_customers__count +----------------------------------+----------------------------- +2025-03-01 04:00:00 | 1 +2025-03-02 05:00:00 | 1 +2025-03-03 03:00:00 | 1 +2025-03-04 08:00:00 | 1 +2025-03-05 04:00:00 | 1 +2025-03-06 02:00:00 | 1 +2025-03-07 05:00:00 | 1 +2025-03-08 03:00:00 | 1 diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/time_dimensions.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/time_dimensions.rs index f6a51d3a1e2a4..26d0878a60671 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/time_dimensions.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/time_dimensions.rs @@ -388,3 +388,23 @@ async fn test_convert_tz_for_raw_time_dimensions() { insta::assert_snapshot!(result); } } + +// The conversion belongs to the cube that owns the column, and stays there when +// the dimension is asked for without a granularity. +#[test] +fn test_raw_time_dimension_converted_once() { + let ctx = create_context(); + + let sql = ctx + .build_sql(indoc! {" + measures: + - orders.count + dimensions: + - orders.created_at + timezone: \"America/Chicago\" + convert_tz_for_raw_time_dimension: true + "}) + .unwrap(); + + assert_eq!(sql.matches("AT TIME ZONE").count(), 1, "got: {sql}"); +} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/views.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/views.rs index 2f9e4d0e68a4b..cfabec9484f30 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/views.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/integration/views.rs @@ -201,3 +201,66 @@ async fn test_view_ungrouped() { insta::assert_snapshot!(result); } } + +fn tz_conversions_count(sql: &str) -> usize { + sql.matches("AT TIME ZONE").count() +} + +fn raw_time_dimension_query(members_yaml: &str) -> String { + format!( + indoc! {" + {}timezone: \"America/Chicago\" + convert_tz_for_raw_time_dimension: true + "}, + members_yaml + ) +} + +// A `type: time` dimension asked for without a granularity is converted into the +// query timezone once. A view re-exposing such a dimension is not a second +// conversion site: the value is read, and converted, on the owning cube. +// +// The count pins how many conversions the query applies; the result pins that +// they land on the column, with the offset going the right way. +#[tokio::test(flavor = "multi_thread")] +async fn test_view_raw_time_dimension_converted_once() { + let ctx = create_context(); + let query = raw_time_dimension_query(indoc! {" + measures: + - orders_with_customers.count + dimensions: + - orders_with_customers.created_at + order: + - id: orders_with_customers.created_at + "}); + + let sql = ctx.build_sql(&query).unwrap(); + assert_eq!(tz_conversions_count(&sql), 1, "got: {sql}"); + + if let Some(result) = ctx.try_execute_pg(&query, SEED).await { + insta::assert_snapshot!(result); + } +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_view_raw_and_granular_time_dimension_converted_once_each() { + let ctx = create_context(); + let query = raw_time_dimension_query(indoc! {" + measures: + - orders_with_customers.count + dimensions: + - orders_with_customers.created_at + time_dimensions: + - dimension: orders_with_customers.created_at + granularity: day + order: + - id: orders_with_customers.created_at + "}); + + let sql = ctx.build_sql(&query).unwrap(); + assert_eq!(tz_conversions_count(&sql), 2, "got: {sql}"); + + if let Some(result) = ctx.try_execute_pg(&query, SEED).await { + insta::assert_snapshot!(result); + } +} diff --git a/rust/cubesql/CHANGELOG.md b/rust/cubesql/CHANGELOG.md index 0a32e521ebdd0..18a9af836353c 100644 --- a/rust/cubesql/CHANGELOG.md +++ b/rust/cubesql/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/cubesql + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) ### Bug Fixes diff --git a/rust/cubesql/cubesql/src/compile/mod.rs b/rust/cubesql/cubesql/src/compile/mod.rs index a66370f23d007..bda2ae3768937 100644 --- a/rust/cubesql/cubesql/src/compile/mod.rs +++ b/rust/cubesql/cubesql/src/compile/mod.rs @@ -13922,6 +13922,64 @@ ORDER BY "source"."str0" ASC insta::assert_snapshot!(context.execute_query(query).await.unwrap()); } + /// `LIMIT 0` should reach the transport as `limit: 0` (and not as the default row + /// limit), and execute into an empty result + #[tokio::test] + async fn test_cube_scan_exec_limit_zero() { + init_testing_logger(); + + let context = TestContext::new(DatabaseProtocol::PostgreSQL).await; + + // language=PostgreSQL + let query = r#" + SELECT dim_str0 + FROM MultiTypeCube + GROUP BY 1 + LIMIT 0 + "#; + + let expected_cube_scan = V1LoadRequestQuery { + measures: Some(vec![]), + segments: Some(vec![]), + dimensions: Some(vec!["MultiTypeCube.dim_str0".to_string()]), + order: Some(vec![]), + limit: Some(0), + ..Default::default() + }; + + assert_eq!( + context + .convert_sql_to_cube_query(query) + .await + .unwrap() + .as_logical_plan() + .find_cube_scan() + .request, + expected_cube_scan, + ); + + // Mock is matched by the exact request, so execution would fail here if anything + // downstream replaced `limit: 0` with a default limit + context + .add_cube_load_mock( + expected_cube_scan, + simple_load_response(vec!["MultiTypeCube.dim_str0"], vec![vec![]]), + ) + .await; + + let result = context.execute_query(query).await.unwrap(); + assert_eq!( + result.trim(), + [ + "+----------+", + "| dim_str0 |", + "+----------+", + "+----------+", + ] + .join("\n") + ); + } + #[tokio::test] async fn test_wrapper_tableau_week_number() { if !Rewriter::sql_push_down_enabled() { diff --git a/rust/cubesql/cubesql/src/compile/rewrite/rules/members.rs b/rust/cubesql/cubesql/src/compile/rewrite/rules/members.rs index ebccb095aec62..3ad1983571cbc 100644 --- a/rust/cubesql/cubesql/src/compile/rewrite/rules/members.rs +++ b/rust/cubesql/cubesql/src/compile/rewrite/rules/members.rs @@ -2140,11 +2140,6 @@ impl MemberRules { fetch_value = *fetch; break; } - // TODO support this case - if fetch_value == Some(0) { - // Broken and unsupported case for now - return false; - } let mut inner_skip_value = None; for inner_skip in var_iter!(egraph[subst[inner_skip_var]], CubeScanOffset) { diff --git a/rust/cubesql/cubesql/src/compile/test/test_cube_scan.rs b/rust/cubesql/cubesql/src/compile/test/test_cube_scan.rs index c8686e4da883f..7052f35e2a570 100644 --- a/rust/cubesql/cubesql/src/compile/test/test_cube_scan.rs +++ b/rust/cubesql/cubesql/src/compile/test/test_cube_scan.rs @@ -100,6 +100,73 @@ async fn cubescan_limit_limit() { } } +/// LIMIT 0 should be pushed to CubeScan as limit=0, and not replaced with a default limit +#[tokio::test] +async fn cubescan_limit_zero() { + init_testing_logger(); + + let variants = vec![ + // language=PostgreSQL + r#" + SELECT + customer_gender + FROM + KibanaSampleDataEcommerce + GROUP BY + 1 + LIMIT 0 + "#, + // language=PostgreSQL + r#" + SELECT + customer_gender + FROM ( + SELECT + customer_gender + FROM + KibanaSampleDataEcommerce + GROUP BY + 1 + LIMIT 3 + ) scan + LIMIT 0 + "#, + // language=PostgreSQL + r#" + SELECT + customer_gender + FROM ( + SELECT + customer_gender + FROM + KibanaSampleDataEcommerce + GROUP BY + 1 + LIMIT 0 + ) scan + LIMIT 3 + "#, + ]; + + for variant in variants { + let query_plan = + convert_select_to_query_plan(variant.to_string(), DatabaseProtocol::PostgreSQL).await; + + let logical_plan = query_plan.as_logical_plan(); + assert_eq!( + logical_plan.find_cube_scan().request, + V1LoadRequestQuery { + measures: Some(vec![]), + dimensions: Some(vec!["KibanaSampleDataEcommerce.customer_gender".to_string()]), + segments: Some(vec![]), + order: Some(vec![]), + limit: Some(0), + ..Default::default() + } + ); + } +} + /// OFFSET over OFFSET should be pushed to single CubeScan #[tokio::test] async fn cubescan_offset_offset() { diff --git a/rust/cubesql/package.json b/rust/cubesql/package.json index e1b4b4f05289f..290a1668be595 100644 --- a/rust/cubesql/package.json +++ b/rust/cubesql/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-backend/cubesql", - "version": "1.7.31", + "version": "1.7.32", "description": "SQL API for Cube as proxy over MySQL protocol.", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" diff --git a/rust/cubestore/CHANGELOG.md b/rust/cubestore/CHANGELOG.md index 82b2af3fd13d1..c89ba2330c397 100644 --- a/rust/cubestore/CHANGELOG.md +++ b/rust/cubestore/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.32](https://github.com/cube-js/cube/compare/v1.7.31...v1.7.32) (2026-09-01) + +**Note:** Version bump only for package @cubejs-backend/cubestore + ## [1.7.31](https://github.com/cube-js/cube/compare/v1.7.30...v1.7.31) (2026-08-31) ### Performance Improvements diff --git a/rust/cubestore/Cargo.lock b/rust/cubestore/Cargo.lock index d0784092ea70d..15c9effe2fdb0 100644 --- a/rust/cubestore/Cargo.lock +++ b/rust/cubestore/Cargo.lock @@ -1445,7 +1445,7 @@ dependencies = [ [[package]] name = "cubestore" -version = "1.7.31" +version = "1.7.32" dependencies = [ "actix-rt", "anyhow", diff --git a/rust/cubestore/cubestore/Cargo.toml b/rust/cubestore/cubestore/Cargo.toml index 6fcc2e830e757..5b9a2fbfe5f9b 100644 --- a/rust/cubestore/cubestore/Cargo.toml +++ b/rust/cubestore/cubestore/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cubestore" -version = "1.7.31" +version = "1.7.32" authors = ["Cube Dev, Inc."] edition = "2021" license = "Apache-2.0" diff --git a/rust/cubestore/package.json b/rust/cubestore/package.json index b77034ae0cc9a..a8bdaebfb1a69 100644 --- a/rust/cubestore/package.json +++ b/rust/cubestore/package.json @@ -1,6 +1,6 @@ { "name": "@cubejs-backend/cubestore", - "version": "1.7.31", + "version": "1.7.32", "description": "Cube.js pre-aggregation storage layer.", "main": "dist/src/index.js", "typings": "dist/src/index.d.ts", @@ -33,7 +33,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@cubejs-backend/linter": "1.7.31", + "@cubejs-backend/linter": "1.7.32", "@types/jest": "^29", "@types/node": "^18", "jest": "^29", @@ -43,7 +43,7 @@ "access": "public" }, "dependencies": { - "@cubejs-backend/shared": "1.7.31", + "@cubejs-backend/shared": "1.7.32", "@octokit/core": "^3.2.5", "source-map-support": "^0.5.19" },