Skip to content

Commit bcbb425

Browse files
committed
graphql, tests: _logs queries only return fields selected in query
1 parent 881e55a commit bcbb425

File tree

2 files changed

+111
-30
lines changed

2 files changed

+111
-30
lines changed

graphql/src/execution/execution.rs

Lines changed: 62 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -345,43 +345,75 @@ pub(crate) async fn execute_root_selection_set_uncached(
345345
)]
346346
})?;
347347

348-
// Convert log entries to GraphQL values
348+
// Get _Log_ type from schema for field selection
349+
let log_type_def = ctx.query.schema.get_named_type("_Log_").ok_or_else(|| {
350+
vec![QueryExecutionError::AbstractTypeError(
351+
"_Log_ type not found in schema".to_string(),
352+
)]
353+
})?;
354+
let log_object_type = match log_type_def {
355+
s::TypeDefinition::Object(obj) => sast::ObjectType::from(Arc::new(obj.clone())),
356+
_ => {
357+
return Err(vec![QueryExecutionError::AbstractTypeError(
358+
"_Log_ is not an object type".to_string(),
359+
)])
360+
}
361+
};
362+
363+
// Convert log entries to GraphQL values, respecting field selection
349364
let log_values: Vec<r::Value> = log_entries
350365
.into_iter()
351366
.map(|entry| {
352-
// Convert arguments Vec<(String, String)> to GraphQL objects
353-
let arguments: Vec<r::Value> = entry
354-
.arguments
355-
.into_iter()
356-
.map(|(key, value)| {
357-
object! {
358-
key: key,
359-
value: value,
360-
__typename: "_LogArgument_"
361-
}
362-
})
363-
.collect();
364-
365-
// Convert log level to string
367+
// Convert log level to string (needed by multiple fields)
366368
let level_str = entry.level.as_str().to_uppercase();
369+
let mut results = Vec::new();
367370

368-
object! {
369-
id: entry.id,
370-
subgraphId: entry.subgraph_id.to_string(),
371-
timestamp: entry.timestamp,
372-
level: level_str,
373-
text: entry.text,
374-
arguments: arguments,
375-
meta: object! {
376-
module: entry.meta.module,
377-
line: r::Value::Int(entry.meta.line),
378-
column: r::Value::Int(entry.meta.column),
379-
__typename: "_LogMeta_"
380-
},
381-
__typename: "_Log_"
371+
// Iterate over requested fields (same pattern as entity queries)
372+
for log_field in field
373+
.selection_set
374+
.fields_for(&log_object_type)
375+
.map_err(|e| vec![e])?
376+
{
377+
let response_key = log_field.response_key();
378+
379+
// Map field name to LogEntry value (replaces prefetched_object lookup)
380+
let value = match log_field.name.as_str() {
381+
"id" => entry.id.clone().into_value(),
382+
"subgraphId" => entry.subgraph_id.to_string().into_value(),
383+
"timestamp" => entry.timestamp.clone().into_value(),
384+
"level" => level_str.clone().into_value(),
385+
"text" => entry.text.clone().into_value(),
386+
"arguments" => {
387+
// Convert arguments Vec<(String, String)> to GraphQL objects
388+
let args: Vec<r::Value> = entry
389+
.arguments
390+
.iter()
391+
.map(|(key, value)| {
392+
object! {
393+
key: key.clone(),
394+
value: value.clone(),
395+
__typename: "_LogArgument_"
396+
}
397+
})
398+
.collect();
399+
r::Value::List(args)
400+
}
401+
"meta" => object! {
402+
module: entry.meta.module.clone(),
403+
line: r::Value::Int(entry.meta.line),
404+
column: r::Value::Int(entry.meta.column),
405+
__typename: "_LogMeta_"
406+
},
407+
"__typename" => "_Log_".into_value(),
408+
_ => continue, // Unknown field, skip it
409+
};
410+
411+
results.push((Word::from(response_key), value));
382412
}
413+
414+
Ok(r::Value::Object(Object::from_iter(results)))
383415
})
384-
.collect();
416+
.collect::<Result<Vec<_>, Vec<QueryExecutionError>>>()?;
385417

386418
let response_key = Word::from(field.response_key());
387419
let logs_object = Object::from_iter(vec![(response_key, r::Value::List(log_values))]);

tests/tests/integration_tests.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1529,6 +1529,55 @@ async fn test_logs_query(ctx: TestContext) -> anyhow::Result<()> {
15291529
error_msg
15301530
);
15311531

1532+
// Test 7: Field selection - verify only requested fields are returned
1533+
let query = r#"{
1534+
_logs(first: 1) {
1535+
id
1536+
timestamp
1537+
}
1538+
}"#
1539+
.to_string();
1540+
let resp = subgraph.query(&query).await?;
1541+
1542+
let logs = resp["data"]["_logs"]
1543+
.as_array()
1544+
.context("Expected _logs to be an array")?;
1545+
1546+
if !logs.is_empty() {
1547+
let log = &logs[0];
1548+
1549+
// Verify requested fields are present
1550+
assert!(log.get("id").is_some(), "Expected id field to be present");
1551+
assert!(
1552+
log.get("timestamp").is_some(),
1553+
"Expected timestamp field to be present"
1554+
);
1555+
1556+
// Verify non-requested fields are NOT present
1557+
assert!(
1558+
log.get("text").is_none(),
1559+
"Expected text field to NOT be present (field selection bug)"
1560+
);
1561+
assert!(
1562+
log.get("level").is_none(),
1563+
"Expected level field to NOT be present (field selection bug)"
1564+
);
1565+
assert!(
1566+
log.get("subgraphId").is_none(),
1567+
"Expected subgraphId field to NOT be present (field selection bug)"
1568+
);
1569+
assert!(
1570+
log.get("arguments").is_none(),
1571+
"Expected arguments field to NOT be present (field selection bug)"
1572+
);
1573+
assert!(
1574+
log.get("meta").is_none(),
1575+
"Expected meta field to NOT be present (field selection bug)"
1576+
);
1577+
1578+
println!("✓ Field selection works correctly - only requested fields returned");
1579+
}
1580+
15321581
Ok(())
15331582
}
15341583

0 commit comments

Comments
 (0)