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
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/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/__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",
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();
+ });
});
});
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
+ }
+}
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