Skip to content

Commit e82fbeb

Browse files
UsmanYasinusman.yasin
andauthored
fix(cube-orchestrator): Deterministic column sequence in output (#10086)
* fix: Deterministic column sequence in output * fix: tests fixed * fix: Removed unused import * chore(ci): trigger workflow rerun --------- Co-authored-by: usman.yasin <[email protected]>
1 parent 56b5c9b commit e82fbeb

File tree

5 files changed

+32
-22
lines changed

5 files changed

+32
-22
lines changed

packages/cubejs-backend-native/Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust/cubeorchestrator/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ serde = { version = "1.0.217", features = ["derive"] }
1010
serde_json = "1.0.133"
1111
anyhow = "1.0"
1212
itertools = "0.13.0"
13+
indexmap = { version = "2.0", features = ["serde"] }
1314

1415
[dependencies.neon]
1516
version = "=1"

rust/cubeorchestrator/src/query_message_parser.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use crate::{
33
transport::JsRawData,
44
};
55
use cubeshared::codegen::{root_as_http_message, HttpCommand};
6+
use indexmap::IndexMap;
67
use neon::prelude::Finalize;
7-
use std::collections::HashMap;
88

99
#[derive(Debug)]
1010
pub enum ParseError {
@@ -35,7 +35,7 @@ impl std::error::Error for ParseError {}
3535
pub struct QueryResult {
3636
pub columns: Vec<String>,
3737
pub rows: Vec<Vec<DBResponseValue>>,
38-
pub columns_pos: HashMap<String, usize>,
38+
pub columns_pos: IndexMap<String, usize>,
3939
}
4040

4141
impl Finalize for QueryResult {}
@@ -45,7 +45,7 @@ impl QueryResult {
4545
let mut result = QueryResult {
4646
columns: vec![],
4747
rows: vec![],
48-
columns_pos: HashMap::new(),
48+
columns_pos: IndexMap::new(),
4949
};
5050

5151
let http_message =
@@ -69,7 +69,7 @@ impl QueryResult {
6969
return Err(ParseError::ColumnNameNotDefined);
7070
}
7171

72-
let (columns, columns_pos): (Vec<_>, HashMap<_, _>) = result_set_columns
72+
let (columns, columns_pos): (Vec<_>, IndexMap<_, _>) = result_set_columns
7373
.iter()
7474
.enumerate()
7575
.map(|(index, column_name)| {
@@ -111,13 +111,13 @@ impl QueryResult {
111111
return Ok(QueryResult {
112112
columns: vec![],
113113
rows: vec![],
114-
columns_pos: HashMap::new(),
114+
columns_pos: IndexMap::new(),
115115
});
116116
}
117117

118118
let first_row = &js_raw_data[0];
119119
let columns: Vec<String> = first_row.keys().cloned().collect();
120-
let columns_pos: HashMap<String, usize> = columns
120+
let columns_pos: IndexMap<String, usize> = columns
121121
.iter()
122122
.enumerate()
123123
.map(|(index, column)| (column.clone(), index))

rust/cubeorchestrator/src/query_result_transform.rs

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::{
77
};
88
use anyhow::{bail, Context, Result};
99
use chrono::{DateTime, NaiveDateTime, TimeZone, Utc};
10+
use indexmap::IndexMap;
1011
use itertools::multizip;
1112
use serde::{Deserialize, Serialize};
1213
use serde_json::Value;
@@ -171,7 +172,12 @@ pub fn get_members(
171172
alias_to_member_name_map: &HashMap<String, String>,
172173
annotation: &HashMap<String, ConfigItem>,
173174
) -> Result<(MembersMap, Vec<String>)> {
174-
let mut members_map: MembersMap = HashMap::new();
175+
let mut members_map: MembersMap = IndexMap::new();
176+
// IndexMap maintains insertion order, ensuring deterministic column ordering.
177+
// The order comes from db_data.columns which now preserves the database result order
178+
// (since JsRawData uses IndexMap instead of HashMap).
179+
// Not sure if it solves the original comment below.
180+
// Original Comment:
175181
// Hashmaps don't guarantee the order of the elements while iterating
176182
// this fires in get_compact_row because members map doesn't hold the members for
177183
// date range queries, which are added later and thus columns in final recordset are not
@@ -267,13 +273,13 @@ pub fn get_members(
267273

268274
/// Convert DB response object to the compact output format.
269275
pub fn get_compact_row(
270-
members_to_alias_map: &HashMap<String, String>,
276+
members_to_alias_map: &IndexMap<String, String>,
271277
annotation: &HashMap<String, ConfigItem>,
272278
query_type: &QueryType,
273279
members: &[String],
274280
time_dimensions: Option<&Vec<QueryTimeDimension>>,
275281
db_row: &[DBResponseValue],
276-
columns_pos: &HashMap<String, usize>,
282+
columns_pos: &IndexMap<String, usize>,
277283
) -> Result<Vec<DBResponsePrimitive>> {
278284
let mut row: Vec<DBResponsePrimitive> = Vec::with_capacity(members.len());
279285

@@ -322,9 +328,9 @@ pub fn get_vanilla_row(
322328
query_type: &QueryType,
323329
query: &NormalizedQuery,
324330
db_row: &[DBResponseValue],
325-
columns_pos: &HashMap<String, usize>,
326-
) -> Result<HashMap<String, DBResponsePrimitive>> {
327-
let mut row = HashMap::new();
331+
columns_pos: &IndexMap<String, usize>,
332+
) -> Result<IndexMap<String, DBResponsePrimitive>> {
333+
let mut row = IndexMap::new();
328334

329335
// FIXME: For now custom granularities are not supported, only common ones.
330336
// There is no granularity type/class implementation in rust yet.
@@ -527,7 +533,7 @@ pub enum TransformedData {
527533
members: Vec<String>,
528534
dataset: Vec<Vec<DBResponsePrimitive>>,
529535
},
530-
Vanilla(Vec<HashMap<String, DBResponsePrimitive>>),
536+
Vanilla(Vec<IndexMap<String, DBResponsePrimitive>>),
531537
}
532538

533539
impl TransformedData {
@@ -2180,7 +2186,7 @@ mod tests {
21802186
&QueryResult {
21812187
columns: vec![],
21822188
rows: vec![],
2183-
columns_pos: HashMap::new(),
2189+
columns_pos: IndexMap::new(),
21842190
},
21852191
alias_to_member_name_map,
21862192
annotation,
@@ -2209,7 +2215,7 @@ mod tests {
22092215
alias_to_member_name_map,
22102216
annotation,
22112217
)?;
2212-
let members_map_expected: MembersMap = HashMap::from([
2218+
let members_map_expected: MembersMap = IndexMap::from([
22132219
(
22142220
"ECommerceRecordsUs2021.postalCode".to_string(),
22152221
"e_commerce_records_us2021__postal_code".to_string(),
@@ -2241,7 +2247,7 @@ mod tests {
22412247
&QueryResult {
22422248
columns: vec![],
22432249
rows: vec![],
2244-
columns_pos: HashMap::new(),
2250+
columns_pos: IndexMap::new(),
22452251
},
22462252
alias_to_member_name_map,
22472253
annotation,
@@ -2270,7 +2276,7 @@ mod tests {
22702276
alias_to_member_name_map,
22712277
annotation,
22722278
)?;
2273-
let members_map_expected: MembersMap = HashMap::from([
2279+
let members_map_expected: MembersMap = IndexMap::from([
22742280
(
22752281
"ECommerceRecordsUs2021.orderDate.day".to_string(),
22762282
"e_commerce_records_us2021__order_date_day".to_string(),
@@ -2313,7 +2319,7 @@ mod tests {
23132319
&QueryResult {
23142320
columns: vec![],
23152321
rows: vec![],
2316-
columns_pos: HashMap::new(),
2322+
columns_pos: IndexMap::new(),
23172323
},
23182324
alias_to_member_name_map,
23192325
annotation,
@@ -2345,7 +2351,7 @@ mod tests {
23452351
alias_to_member_name_map,
23462352
annotation,
23472353
)?;
2348-
let members_map_expected: HashMap<String, String> = HashMap::from([
2354+
let members_map_expected: MembersMap = IndexMap::from([
23492355
(
23502356
"ECommerceRecordsUs2021.orderDate.month".to_string(),
23512357
"e_commerce_records_us2021__order_date_month".to_string(),
@@ -2640,7 +2646,7 @@ mod tests {
26402646
&raw_data.rows[0],
26412647
&raw_data.columns_pos,
26422648
)?;
2643-
let expected = HashMap::from([
2649+
let expected = IndexMap::from([
26442650
(
26452651
"ECommerceRecordsUs2021.city".to_string(),
26462652
DBResponsePrimitive::String("Missouri City".to_string()),

rust/cubeorchestrator/src/transport.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::query_result_transform::DBResponsePrimitive;
2+
use indexmap::IndexMap;
23
use serde::{Deserialize, Serialize};
34
use serde_json::Value;
45
use std::{collections::HashMap, fmt::Display};
@@ -153,7 +154,7 @@ pub struct QueryTimeDimension {
153154

154155
pub type AliasToMemberMap = HashMap<String, String>;
155156

156-
pub type MembersMap = HashMap<String, String>;
157+
pub type MembersMap = IndexMap<String, String>;
157158

158159
#[derive(Debug, Clone, Serialize, Deserialize)]
159160
pub struct GranularityMeta {
@@ -317,4 +318,4 @@ pub struct TransformDataRequest {
317318
pub res_type: Option<ResultType>,
318319
}
319320

320-
pub type JsRawData = Vec<HashMap<String, DBResponsePrimitive>>;
321+
pub type JsRawData = Vec<IndexMap<String, DBResponsePrimitive>>;

0 commit comments

Comments
 (0)