Skip to content

Commit 344270d

Browse files
committed
graph, graphql, tests: Support specifying sort order in _logs queries
1 parent a97ee5c commit 344270d

6 files changed

Lines changed: 108 additions & 3 deletions

File tree

graph/src/components/log_store/elasticsearch.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ impl ElasticsearchLogStore {
8484
"from": query.skip,
8585
"size": query.first,
8686
"sort": [
87-
{ "timestamp": { "order": "desc" } }
87+
{ "timestamp": { "order": query.order_direction.as_str() } }
8888
]
8989
})
9090
}

graph/src/components/log_store/loki.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,16 @@ impl LokiLogStore {
5555
from: &str,
5656
to: &str,
5757
limit: u32,
58+
order_direction: super::OrderDirection,
5859
) -> Result<Vec<LogEntry>, LogStoreError> {
5960
let url = format!("{}/loki/api/v1/query_range", self.endpoint);
6061

62+
// Map order direction to Loki's direction parameter
63+
let direction = match order_direction {
64+
super::OrderDirection::Desc => "backward", // Most recent first
65+
super::OrderDirection::Asc => "forward", // Oldest first
66+
};
67+
6168
let mut request = self
6269
.client
6370
.get(&url)
@@ -66,7 +73,7 @@ impl LokiLogStore {
6673
("start", from),
6774
("end", to),
6875
("limit", &limit.to_string()),
69-
("direction", "backward"), // Most recent first
76+
("direction", direction),
7077
])
7178
.timeout(Duration::from_secs(10));
7279

@@ -158,7 +165,9 @@ impl LogStore for LokiLogStore {
158165
// Execute query with limit + skip to handle pagination
159166
let limit = query.first + query.skip;
160167

161-
let mut entries = self.execute_query(&logql_query, from, to, limit).await?;
168+
let mut entries = self
169+
.execute_query(&logql_query, from, to, limit, query.order_direction)
170+
.await?;
162171

163172
// Apply skip/first pagination
164173
if query.skip > 0 {
@@ -239,6 +248,7 @@ mod tests {
239248
search: None,
240249
first: 100,
241250
skip: 0,
251+
order_direction: crate::components::log_store::OrderDirection::Desc,
242252
};
243253

244254
let logql = store.build_logql_query(&query);
@@ -256,6 +266,7 @@ mod tests {
256266
search: None,
257267
first: 100,
258268
skip: 0,
269+
order_direction: crate::components::log_store::OrderDirection::Desc,
259270
};
260271

261272
let logql = store.build_logql_query(&query);
@@ -273,6 +284,7 @@ mod tests {
273284
search: Some("transaction failed".to_string()),
274285
first: 100,
275286
skip: 0,
287+
order_direction: crate::components::log_store::OrderDirection::Desc,
276288
};
277289

278290
let logql = store.build_logql_query(&query);

graph/src/schema/api.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,6 +1396,18 @@ fn logs_field() -> s::Field {
13961396
default_value: Some(s::Value::Int(0.into())),
13971397
directives: vec![],
13981398
},
1399+
// orderDirection: OrderDirection (default desc)
1400+
s::InputValue {
1401+
position: Pos::default(),
1402+
description: Some(
1403+
"Sort direction for results. Default: desc (newest first)."
1404+
.to_string()
1405+
),
1406+
name: String::from("orderDirection"),
1407+
value_type: s::Type::NamedType(String::from("OrderDirection")),
1408+
default_value: Some(s::Value::Enum(String::from("desc"))),
1409+
directives: vec![],
1410+
},
13991411
],
14001412
field_type: s::Type::NonNullType(Box::new(s::Type::ListType(Box::new(
14011413
s::Type::NonNullType(Box::new(s::Type::NamedType(String::from("_Log_")))),

graph/src/schema/logs.graphql

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ enum LogLevel {
1515
DEBUG
1616
}
1717

18+
"""
19+
Sort direction for query results.
20+
"""
21+
enum OrderDirection {
22+
"Ascending order (oldest first for timestamps)"
23+
asc
24+
"Descending order (newest first for timestamps)"
25+
desc
26+
}
27+
1828
"""
1929
A log entry emitted by a subgraph during indexing.
2030
Logs can be generated by the subgraph's AssemblyScript code using the `log.*` functions.

graphql/src/store/logs.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ pub fn build_log_query(
6363
let mut search = None;
6464
let mut first = 100;
6565
let mut skip = 0;
66+
let mut order_direction = graph::components::log_store::OrderDirection::Desc;
6667

6768
// Parse arguments
6869
for (name, value) in &field.arguments {
@@ -156,6 +157,17 @@ pub fn build_log_query(
156157
skip = skip_u32;
157158
}
158159
}
160+
"orderDirection" => {
161+
if let r::Value::Enum(order_str) = value {
162+
order_direction = order_str.parse().map_err(|e: String| {
163+
QueryExecutionError::InvalidArgumentError(
164+
field.position,
165+
"orderDirection".to_string(),
166+
q::Value::String(e),
167+
)
168+
})?;
169+
}
170+
}
159171
_ => {
160172
// Unknown argument, ignore
161173
}
@@ -170,5 +182,6 @@ pub fn build_log_query(
170182
search,
171183
first,
172184
skip,
185+
order_direction,
173186
})
174187
}

tests/tests/integration_tests.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1684,6 +1684,64 @@ async fn test_logs_query(ctx: TestContext) -> anyhow::Result<()> {
16841684
println!("✓ Field selection works correctly - only requested fields returned");
16851685
}
16861686

1687+
// Test 8: Order direction - ascending
1688+
let query = r#"{
1689+
_logs(first: 10, orderDirection: asc) {
1690+
id
1691+
timestamp
1692+
}
1693+
}"#
1694+
.to_string();
1695+
let resp = subgraph.query(&query).await?;
1696+
1697+
let logs = resp["data"]["_logs"]
1698+
.as_array()
1699+
.context("Expected _logs to be an array")?;
1700+
1701+
// Verify ascending order (each timestamp >= previous)
1702+
if logs.len() > 1 {
1703+
for i in 1..logs.len() {
1704+
let prev_ts = logs[i - 1]["timestamp"].as_str().unwrap();
1705+
let curr_ts = logs[i]["timestamp"].as_str().unwrap();
1706+
assert!(
1707+
curr_ts >= prev_ts,
1708+
"Expected ascending order, but {} came before {}",
1709+
prev_ts,
1710+
curr_ts
1711+
);
1712+
}
1713+
println!("✓ Ascending order works correctly");
1714+
}
1715+
1716+
// Test 9: Order direction - descending (explicit)
1717+
let query = r#"{
1718+
_logs(first: 10, orderDirection: desc) {
1719+
id
1720+
timestamp
1721+
}
1722+
}"#
1723+
.to_string();
1724+
let resp = subgraph.query(&query).await?;
1725+
1726+
let logs = resp["data"]["_logs"]
1727+
.as_array()
1728+
.context("Expected _logs to be an array")?;
1729+
1730+
// Verify descending order (each timestamp <= previous)
1731+
if logs.len() > 1 {
1732+
for i in 1..logs.len() {
1733+
let prev_ts = logs[i - 1]["timestamp"].as_str().unwrap();
1734+
let curr_ts = logs[i]["timestamp"].as_str().unwrap();
1735+
assert!(
1736+
curr_ts <= prev_ts,
1737+
"Expected descending order, but {} came before {}",
1738+
prev_ts,
1739+
curr_ts
1740+
);
1741+
}
1742+
println!("✓ Descending order works correctly");
1743+
}
1744+
16871745
Ok(())
16881746
}
16891747

0 commit comments

Comments
 (0)