From 506e4a5b826edcb9c7f534f149578028a12d2c89 Mon Sep 17 00:00:00 2001 From: Dmitry Patsura Date: Fri, 5 Dec 2025 10:51:44 +0100 Subject: [PATCH 1/5] feat: Allo to specify default dimension order in schema --- .../src/compiler/CubeEvaluator.ts | 1 + .../src/compiler/CubeSymbols.ts | 1 + .../src/compiler/CubeToMetaTransformer.ts | 2 + .../src/compiler/CubeValidator.ts | 1 + .../test/unit/cube-validator.test.ts | 63 +++++++++++++++++++ 5 files changed, 68 insertions(+) diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts index 24c298f91947a..92d63aab79412 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts @@ -36,6 +36,7 @@ export type DimensionDefinition = { fieldType?: string; multiStage?: boolean; shiftInterval?: string; + order?: 'asc' | 'desc'; }; export type TimeShiftDefinition = { diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts b/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts index fc13bad01feda..eb8d2e945d518 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts @@ -35,6 +35,7 @@ export type CubeSymbolDefinition = { granularities?: Record; timeShift?: TimeshiftDefinition[]; format?: string; + order?: 'asc' | 'desc'; }; export type HierarchyDefinition = { diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeToMetaTransformer.ts b/packages/cubejs-schema-compiler/src/compiler/CubeToMetaTransformer.ts index b1a892c5207d7..18edc54609e37 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeToMetaTransformer.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeToMetaTransformer.ts @@ -97,6 +97,7 @@ export type DimensionConfig = { primaryKey: boolean; aliasMember?: string; granularities?: GranularityDefinition[]; + order?: 'asc' | 'desc'; }; export type SegmentConfig = { @@ -274,6 +275,7 @@ export class CubeToMetaTransformer implements CompilerInterface { origin: gDef.origin, })) : undefined, + order: extendedDimDef.order, }; }), segments: Object.entries(extendedCube.segments || {}).map((nameToSegment: [string, any]) => { diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeValidator.ts b/packages/cubejs-schema-compiler/src/compiler/CubeValidator.ts index e73e791701453..d3a3186422b61 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeValidator.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeValidator.ts @@ -278,6 +278,7 @@ const BaseDimensionWithoutSubQuery = { otherwise: formatSchema }), meta: Joi.any(), + order: Joi.string().valid('asc', 'desc'), values: Joi.when('type', { is: 'switch', then: Joi.array().items(Joi.string()), diff --git a/packages/cubejs-schema-compiler/test/unit/cube-validator.test.ts b/packages/cubejs-schema-compiler/test/unit/cube-validator.test.ts index b80550da958d4..f403444b4c4cc 100644 --- a/packages/cubejs-schema-compiler/test/unit/cube-validator.test.ts +++ b/packages/cubejs-schema-compiler/test/unit/cube-validator.test.ts @@ -1675,5 +1675,68 @@ describe('Cube Validation', () => { const validationResult = cubeValidator.validate(cube, new ConsoleErrorReporter()); expect(validationResult.error).toBeTruthy(); }); + + it('dimension with valid order asc - correct', async () => { + const cubeValidator = new CubeValidator(new CubeSymbols()); + const cube = { + name: 'name', + sql: () => 'SELECT * FROM public.Orders', + dimensions: { + status: { + sql: () => 'status', + type: 'string', + order: 'asc' + }, + }, + fileName: 'fileName', + }; + + const validationResult = cubeValidator.validate(cube, new ConsoleErrorReporter()); + expect(validationResult.error).toBeFalsy(); + }); + + it('dimension with valid order desc - correct', async () => { + const cubeValidator = new CubeValidator(new CubeSymbols()); + const cube = { + name: 'name', + sql: () => 'SELECT * FROM public.Orders', + dimensions: { + createdAt: { + sql: () => 'created_at', + type: 'time', + order: 'desc' + }, + }, + fileName: 'fileName', + }; + + const validationResult = cubeValidator.validate(cube, new ConsoleErrorReporter()); + expect(validationResult.error).toBeFalsy(); + }); + + it('dimension with invalid order value - error', async () => { + const cubeValidator = new CubeValidator(new CubeSymbols()); + const cube = { + name: 'name', + sql: () => 'SELECT * FROM public.Orders', + dimensions: { + status: { + sql: () => 'status', + type: 'string', + order: 'invalid' // should only accept 'asc' or 'desc' + }, + }, + fileName: 'fileName', + }; + + const validationResult = cubeValidator.validate(cube, { + error: (message: any, _e: any) => { + console.log(message); + expect(message).toContain('order'); + } + } as any); + + expect(validationResult.error).toBeTruthy(); + }); }); }); From ba1b434ed77e801a15a3bdc061777a9ad054e10b Mon Sep 17 00:00:00 2001 From: Dmitry Patsura Date: Fri, 5 Dec 2025 11:59:14 +0100 Subject: [PATCH 2/5] chore: add docs --- .../data-modeling/reference/dimensions.mdx | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/docs/pages/product/data-modeling/reference/dimensions.mdx b/docs/pages/product/data-modeling/reference/dimensions.mdx index e4d088b76dfe3..7a0852e966e5b 100644 --- a/docs/pages/product/data-modeling/reference/dimensions.mdx +++ b/docs/pages/product/data-modeling/reference/dimensions.mdx @@ -330,6 +330,58 @@ cubes: +### `order` + +The `order` parameter specifies the default sort order for a dimension. Valid +values are `asc` (ascending) and `desc` (descending). This parameter is optional. + +When set, the dimension's default sort order is exposed via +[APIs and integrations][ref-apis]. Consuming applications, such as BI tools +and custom frontends, can use this metadata to apply consistent default sorting +when displaying dimension values, ensuring a uniform user experience across +different tools connected to the semantic layer. + + + +```javascript +cube(`orders`, { + // ... + + dimensions: { + status: { + sql: `status`, + type: `string`, + order: `asc` + }, + + created_at: { + sql: `created_at`, + type: `time`, + order: `desc` + } + } +}) +``` + +```yaml +cubes: + - name: orders + # ... + + dimensions: + - name: status + sql: status + type: string + order: asc + + - name: created_at + sql: created_at + type: time + order: desc +``` + + + ### `primary_key` Specify if a dimension is a primary key for a cube. The default value is From bf12493e83bb955e8e238087d9ff93faff3ab724 Mon Sep 17 00:00:00 2001 From: Dmitry Patsura Date: Fri, 5 Dec 2025 12:46:35 +0100 Subject: [PATCH 3/5] chore: regenerate --- packages/cubejs-api-gateway/openspec.yml | 7 +++++ rust/cubesql/cubeclient/src/models/mod.rs | 2 ++ .../src/models/v1_cube_meta_dimension.rs | 3 +++ .../models/v1_cube_meta_dimension_order.rs | 26 +++++++++++++++++++ 4 files changed, 38 insertions(+) create mode 100644 rust/cubesql/cubeclient/src/models/v1_cube_meta_dimension_order.rs diff --git a/packages/cubejs-api-gateway/openspec.yml b/packages/cubejs-api-gateway/openspec.yml index 030b1a516dbac..136f15dfb731f 100644 --- a/packages/cubejs-api-gateway/openspec.yml +++ b/packages/cubejs-api-gateway/openspec.yml @@ -145,6 +145,13 @@ components: type: "object" format: $ref: "#/components/schemas/V1CubeMetaFormat" + order: + $ref: "#/components/schemas/V1CubeMetaDimensionOrder" + V1CubeMetaDimensionOrder: + type: "string" + enum: + - "asc" + - "desc" V1CubeMetaMeasure: type: "object" required: diff --git a/rust/cubesql/cubeclient/src/models/mod.rs b/rust/cubesql/cubeclient/src/models/mod.rs index e900b80e3732e..2846dfb7a95d3 100644 --- a/rust/cubesql/cubeclient/src/models/mod.rs +++ b/rust/cubesql/cubeclient/src/models/mod.rs @@ -11,7 +11,9 @@ pub use self::v1_cube_meta_custom_time_format::Type as V1CubeMetaCustomTimeForma pub mod v1_cube_meta_dimension; pub use self::v1_cube_meta_dimension::V1CubeMetaDimension; pub mod v1_cube_meta_dimension_granularity; +pub mod v1_cube_meta_dimension_order; pub use self::v1_cube_meta_dimension_granularity::V1CubeMetaDimensionGranularity; +pub use self::v1_cube_meta_dimension_order::V1CubeMetaDimensionOrder; pub mod v1_cube_meta_folder; pub use self::v1_cube_meta_folder::V1CubeMetaFolder; pub mod v1_cube_meta_format; diff --git a/rust/cubesql/cubeclient/src/models/v1_cube_meta_dimension.rs b/rust/cubesql/cubeclient/src/models/v1_cube_meta_dimension.rs index d5ffda4204fc3..4fde577c8f8c5 100644 --- a/rust/cubesql/cubeclient/src/models/v1_cube_meta_dimension.rs +++ b/rust/cubesql/cubeclient/src/models/v1_cube_meta_dimension.rs @@ -32,6 +32,8 @@ pub struct V1CubeMetaDimension { pub meta: Option, #[serde(rename = "format", skip_serializing_if = "Option::is_none")] pub format: Option>, + #[serde(rename = "order", skip_serializing_if = "Option::is_none")] + pub order: Option, } impl V1CubeMetaDimension { @@ -46,6 +48,7 @@ impl V1CubeMetaDimension { granularities: None, meta: None, format: None, + order: None, } } } diff --git a/rust/cubesql/cubeclient/src/models/v1_cube_meta_dimension_order.rs b/rust/cubesql/cubeclient/src/models/v1_cube_meta_dimension_order.rs new file mode 100644 index 0000000000000..5cb5e3cd815ed --- /dev/null +++ b/rust/cubesql/cubeclient/src/models/v1_cube_meta_dimension_order.rs @@ -0,0 +1,26 @@ +/* + * Cube.js + * + * Cube.js Swagger Schema + * + * The version of the OpenAPI document: 1.0.0 + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Serialize}; + +/// Default sort order for a dimension +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] +pub enum V1CubeMetaDimensionOrder { + #[serde(rename = "asc")] + Asc, + #[serde(rename = "desc")] + Desc, +} + +impl Default for V1CubeMetaDimensionOrder { + fn default() -> V1CubeMetaDimensionOrder { + Self::Asc + } +} From dcd94b67b3504829d55a9751312a6727a071cf12 Mon Sep 17 00:00:00 2001 From: Dmitry Patsura Date: Fri, 5 Dec 2025 13:05:06 +0100 Subject: [PATCH 4/5] chore: update snapshot --- .../test/unit/__snapshots__/views.test.ts.snap | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/cubejs-schema-compiler/test/unit/__snapshots__/views.test.ts.snap b/packages/cubejs-schema-compiler/test/unit/__snapshots__/views.test.ts.snap index 78f1e4155503a..381b44f36a20a 100644 --- a/packages/cubejs-schema-compiler/test/unit/__snapshots__/views.test.ts.snap +++ b/packages/cubejs-schema-compiler/test/unit/__snapshots__/views.test.ts.snap @@ -15,6 +15,7 @@ Object { "key": "Meta.key for CubeA.id", }, "name": "simple_view.id", + "order": undefined, "primaryKey": false, "public": true, "shortTitle": "Title for CubeA.id", @@ -32,6 +33,7 @@ Object { "key": "Meta.key for CubeB.other_id", }, "name": "simple_view.other_id", + "order": undefined, "primaryKey": false, "public": true, "shortTitle": "Title for CubeB.other_id", @@ -114,6 +116,7 @@ Object { "key": "Meta.key for CubeB.other_id", }, "name": "simple_view.CubeB_other_id", + "order": undefined, "primaryKey": false, "public": true, "shortTitle": "Title for CubeB.other_id", @@ -196,6 +199,7 @@ Object { "key": "Meta.key for CubeA.id", }, "name": "simple_view.CubeA_id", + "order": undefined, "primaryKey": false, "public": true, "shortTitle": "Title for CubeA.id", @@ -213,6 +217,7 @@ Object { "key": "Meta.key for CubeB.id", }, "name": "simple_view.CubeB_id", + "order": undefined, "primaryKey": false, "public": true, "shortTitle": "Title for CubeB.id", @@ -230,6 +235,7 @@ Object { "key": "Meta.key for CubeB.other_id", }, "name": "simple_view.CubeB_other_id", + "order": undefined, "primaryKey": false, "public": true, "shortTitle": "Title for CubeB.other_id", @@ -312,6 +318,7 @@ Object { "key": "Meta.key for CubeB.other_id", }, "name": "simple_view.CubeB_other_id", + "order": undefined, "primaryKey": false, "public": true, "shortTitle": "Title for CubeB.other_id", From 6bb6d2000b9ea30940e22dffc402ccc2a4e8617b Mon Sep 17 00:00:00 2001 From: Dmitry Patsura Date: Mon, 8 Dec 2025 12:40:23 +0100 Subject: [PATCH 5/5] chore: alias --- rust/cubesql/cubesql/src/transport/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/cubesql/cubesql/src/transport/mod.rs b/rust/cubesql/cubesql/src/transport/mod.rs index ceb417aea00a0..8ed401947603e 100644 --- a/rust/cubesql/cubesql/src/transport/mod.rs +++ b/rust/cubesql/cubesql/src/transport/mod.rs @@ -21,6 +21,7 @@ pub type CubeMetaCustomTimeFormat = cubeclient::models::V1CubeMetaCustomTimeForm pub type CubeMetaCustomTimeFormatType = cubeclient::models::V1CubeMetaCustomTimeFormatType; pub type CubeMetaLinkFormat = cubeclient::models::V1CubeMetaLinkFormat; pub type CubeMetaLinkFormatType = cubeclient::models::V1CubeMetaLinkFormatType; +pub type CubeMetaDimensionOrder = cubeclient::models::V1CubeMetaDimensionOrder; pub type CubeMetaFormat = cubeclient::models::V1CubeMetaFormat; // Request/Response