@@ -344,6 +344,24 @@ fn dataset_matches_telemetry_type(dataset: &Value, expected: Option<&str>) -> bo
344344 } )
345345}
346346
347+ /// Whether `name` occurs in `query` as a whole token. Dataset names may contain
348+ /// `-` and `_`, so those count as part of a name rather than as boundaries.
349+ fn query_mentions_dataset_name ( query : & str , name : & str ) -> bool {
350+ let is_name_character =
351+ |character : char | character. is_ascii_alphanumeric ( ) || character == '_' || character == '-' ;
352+ !name. is_empty ( )
353+ && query. match_indices ( name) . any ( |( start, matched) | {
354+ !query[ ..start]
355+ . chars ( )
356+ . next_back ( )
357+ . is_some_and ( is_name_character)
358+ && !query[ start + matched. len ( ) ..]
359+ . chars ( )
360+ . next ( )
361+ . is_some_and ( is_name_character)
362+ } )
363+ }
364+
347365pub fn resolve_dataset_candidates ( payload : Value , arguments : & Value ) -> Value {
348366 let query = arguments
349367 . get ( "query" )
@@ -470,7 +488,7 @@ pub fn resolve_dataset_candidates(payload: Value, arguments: &Value) -> Value {
470488 if !query. is_empty ( ) && name == query {
471489 score += 1_000 ;
472490 reasons. push ( "exact dataset name" ) ;
473- } else if !query. is_empty ( ) && query . contains ( & name) {
491+ } else if !query. is_empty ( ) && query_mentions_dataset_name ( & query , & name) {
474492 score += 750 ;
475493 reasons. push ( "dataset name explicitly mentioned in the question" ) ;
476494 } else if !query. is_empty ( ) && name. contains ( & query) {
@@ -2229,6 +2247,35 @@ mod tests {
22292247 assert_eq ! ( sole_mismatch[ "status" ] , "not_found" ) ;
22302248 }
22312249
2250+ #[ actix_web:: test]
2251+ async fn shared_dataset_resolution_requires_whole_name_mentions ( ) {
2252+ let datasets = json ! ( [ { "name" : "api" } , { "name" : "web" } ] ) ;
2253+ let embedded = resolve_dataset_candidates (
2254+ datasets. clone ( ) ,
2255+ & json ! ( { "query" : "show rapid failures" } ) ,
2256+ ) ;
2257+ assert_ne ! ( embedded[ "status" ] , "resolved" ) ;
2258+
2259+ let mentioned =
2260+ resolve_dataset_candidates ( datasets. clone ( ) , & json ! ( { "query" : "show api errors" } ) ) ;
2261+ assert_eq ! ( mentioned[ "status" ] , "resolved" ) ;
2262+ assert_eq ! ( mentioned[ "selectedDatasets" ] , json!( [ "api" ] ) ) ;
2263+
2264+ let punctuated =
2265+ resolve_dataset_candidates ( datasets, & json ! ( { "query" : "errors in (api)?" } ) ) ;
2266+ assert_eq ! ( punctuated[ "selectedDatasets" ] , json!( [ "api" ] ) ) ;
2267+
2268+ let hyphenated = json ! ( [ { "name" : "otel-metrics" } , { "name" : "otel-traces" } ] ) ;
2269+ let longer_name = resolve_dataset_candidates (
2270+ hyphenated. clone ( ) ,
2271+ & json ! ( { "query" : "show otel-metrics-v2 errors" } ) ,
2272+ ) ;
2273+ assert_ne ! ( longer_name[ "status" ] , "resolved" ) ;
2274+ let underscored =
2275+ resolve_dataset_candidates ( hyphenated, & json ! ( { "query" : "show my_otel-metrics" } ) ) ;
2276+ assert_ne ! ( underscored[ "status" ] , "resolved" ) ;
2277+ }
2278+
22322279 #[ actix_web:: test]
22332280 async fn shared_dataset_resolution_prefers_exact_case_selection ( ) {
22342281 let datasets = json ! ( [ { "name" : "Logs" } , { "name" : "logs" } , { "name" : "Traces" } ] ) ;
0 commit comments