@@ -9,7 +9,8 @@ use bencher_json::{
99} ;
1010use diesel:: {
1111 BelongingToDsl as _, Connection as _, ExpressionMethods as _, JoinOnDsl as _,
12- NullableExpressionMethods as _, QueryDsl as _, RunQueryDsl as _, SelectableHelper as _,
12+ NullableExpressionMethods as _, OptionalExtension as _, QueryDsl as _, RunQueryDsl as _,
13+ SelectableHelper as _,
1314} ;
1415use dropshot:: HttpError ;
1516use model:: UpdateModel ;
@@ -25,7 +26,10 @@ use super::{
2526use crate :: {
2627 auth_conn,
2728 context:: { ApiContext , DbConnection } ,
28- error:: { BencherResource , assert_parentage, assert_siblings, resource_not_found_err} ,
29+ error:: {
30+ BencherResource , assert_parentage, assert_siblings, resource_conflict_error,
31+ resource_not_found_err,
32+ } ,
2933 macros:: {
3034 fn_get:: { fn_get, fn_get_id, fn_get_uuid} ,
3135 sql:: last_insert_rowid,
@@ -62,6 +66,20 @@ impl QueryThreshold {
6266 fn_get_id ! ( threshold, ThresholdId , ThresholdUuid ) ;
6367 fn_get_uuid ! ( threshold, ThresholdId , ThresholdUuid ) ;
6468
69+ pub fn find_by_dimensions (
70+ conn : & mut DbConnection ,
71+ branch_id : BranchId ,
72+ testbed_id : TestbedId ,
73+ measure_id : MeasureId ,
74+ ) -> diesel:: QueryResult < Option < Self > > {
75+ schema:: threshold:: table
76+ . filter ( schema:: threshold:: branch_id. eq ( branch_id) )
77+ . filter ( schema:: threshold:: testbed_id. eq ( testbed_id) )
78+ . filter ( schema:: threshold:: measure_id. eq ( measure_id) )
79+ . first :: < Self > ( conn)
80+ . optional ( )
81+ }
82+
6583 pub fn get_with_uuid (
6684 conn : & mut DbConnection ,
6785 query_project : & QueryProject ,
@@ -352,6 +370,30 @@ impl InsertThreshold {
352370 measure_id : MeasureId ,
353371 model : Model ,
354372 ) -> Result < ThresholdId , HttpError > {
373+ // Check for an existing threshold with the same unique key before writing.
374+ if let Some ( existing) = QueryThreshold :: find_by_dimensions (
375+ auth_conn ! ( context) ,
376+ branch_id,
377+ testbed_id,
378+ measure_id,
379+ )
380+ . map_err ( |e| {
381+ crate :: error:: issue_error (
382+ "Failed to query threshold dimensions" ,
383+ "Failed to query threshold dimensions:" ,
384+ e,
385+ )
386+ } ) ? {
387+ return Err ( resource_conflict_error (
388+ BencherResource :: Threshold ,
389+ ( branch_id, testbed_id, measure_id) ,
390+ format ! (
391+ "A threshold ({}) already exists for this branch, testbed, and measure combination" ,
392+ existing. uuid
393+ ) ,
394+ ) ) ;
395+ }
396+
355397 #[ cfg( feature = "plus" ) ]
356398 Self :: rate_limit ( context, project_id) . await ?;
357399 let conn = write_conn ! ( context) ;
@@ -1917,4 +1959,145 @@ mod tests {
19171959 assert_eq ! ( results. len( ) , 20 ) ;
19181960 assert ! ( results. iter( ) . all( |( _, m) | m. is_some( ) ) ) ;
19191961 }
1962+
1963+ /// Test that `find_by_dimensions` returns `None` when no threshold exists.
1964+ #[ test]
1965+ fn find_by_dimensions_returns_none_when_no_threshold ( ) {
1966+ let mut conn = setup_test_db ( ) ;
1967+ let base = create_base_entities ( & mut conn) ;
1968+
1969+ let branch = create_branch_with_head (
1970+ & mut conn,
1971+ base. project_id ,
1972+ "00000000-0000-0000-0000-000000000010" ,
1973+ "main" ,
1974+ "main" ,
1975+ "00000000-0000-0000-0000-000000000011" ,
1976+ ) ;
1977+
1978+ let testbed = create_testbed (
1979+ & mut conn,
1980+ base. project_id ,
1981+ "00000000-0000-0000-0000-000000000020" ,
1982+ "localhost" ,
1983+ "localhost" ,
1984+ ) ;
1985+
1986+ let measure = create_measure (
1987+ & mut conn,
1988+ base. project_id ,
1989+ "00000000-0000-0000-0000-000000000030" ,
1990+ "latency" ,
1991+ "latency" ,
1992+ ) ;
1993+
1994+ let result =
1995+ QueryThreshold :: find_by_dimensions ( & mut conn, branch. branch_id , testbed, measure)
1996+ . expect ( "Query should succeed" ) ;
1997+ assert ! ( result. is_none( ) ) ;
1998+ }
1999+
2000+ /// Test that `find_by_dimensions` returns the existing threshold.
2001+ #[ test]
2002+ fn find_by_dimensions_returns_existing_threshold ( ) {
2003+ let mut conn = setup_test_db ( ) ;
2004+ let base = create_base_entities ( & mut conn) ;
2005+
2006+ let branch = create_branch_with_head (
2007+ & mut conn,
2008+ base. project_id ,
2009+ "00000000-0000-0000-0000-000000000010" ,
2010+ "main" ,
2011+ "main" ,
2012+ "00000000-0000-0000-0000-000000000011" ,
2013+ ) ;
2014+
2015+ let testbed = create_testbed (
2016+ & mut conn,
2017+ base. project_id ,
2018+ "00000000-0000-0000-0000-000000000020" ,
2019+ "localhost" ,
2020+ "localhost" ,
2021+ ) ;
2022+
2023+ let measure = create_measure (
2024+ & mut conn,
2025+ base. project_id ,
2026+ "00000000-0000-0000-0000-000000000030" ,
2027+ "latency" ,
2028+ "latency" ,
2029+ ) ;
2030+
2031+ let threshold_id = create_threshold (
2032+ & mut conn,
2033+ base. project_id ,
2034+ branch. branch_id ,
2035+ testbed,
2036+ measure,
2037+ "00000000-0000-0000-0000-000000000040" ,
2038+ ) ;
2039+
2040+ let result =
2041+ QueryThreshold :: find_by_dimensions ( & mut conn, branch. branch_id , testbed, measure)
2042+ . expect ( "Query should succeed" ) ;
2043+ assert ! ( result. is_some( ) ) ;
2044+ assert_eq ! ( result. unwrap( ) . id, threshold_id) ;
2045+ }
2046+
2047+ /// Test that inserting a duplicate threshold violates the UNIQUE constraint.
2048+ #[ test]
2049+ fn duplicate_threshold_insert_fails_with_unique_constraint ( ) {
2050+ let mut conn = setup_test_db ( ) ;
2051+ let base = create_base_entities ( & mut conn) ;
2052+
2053+ let branch = create_branch_with_head (
2054+ & mut conn,
2055+ base. project_id ,
2056+ "00000000-0000-0000-0000-000000000010" ,
2057+ "main" ,
2058+ "main" ,
2059+ "00000000-0000-0000-0000-000000000011" ,
2060+ ) ;
2061+
2062+ let testbed = create_testbed (
2063+ & mut conn,
2064+ base. project_id ,
2065+ "00000000-0000-0000-0000-000000000020" ,
2066+ "localhost" ,
2067+ "localhost" ,
2068+ ) ;
2069+
2070+ let measure = create_measure (
2071+ & mut conn,
2072+ base. project_id ,
2073+ "00000000-0000-0000-0000-000000000030" ,
2074+ "latency" ,
2075+ "latency" ,
2076+ ) ;
2077+
2078+ // First insert succeeds
2079+ create_threshold (
2080+ & mut conn,
2081+ base. project_id ,
2082+ branch. branch_id ,
2083+ testbed,
2084+ measure,
2085+ "00000000-0000-0000-0000-000000000040" ,
2086+ ) ;
2087+
2088+ // Second insert with same (branch, testbed, measure) should fail
2089+ let result = diesel:: insert_into ( schema:: threshold:: table)
2090+ . values ( (
2091+ schema:: threshold:: uuid. eq ( "00000000-0000-0000-0000-000000000041" ) ,
2092+ schema:: threshold:: project_id. eq ( base. project_id ) ,
2093+ schema:: threshold:: branch_id. eq ( branch. branch_id ) ,
2094+ schema:: threshold:: testbed_id. eq ( testbed) ,
2095+ schema:: threshold:: measure_id. eq ( measure) ,
2096+ schema:: threshold:: created. eq ( bencher_json:: DateTime :: TEST ) ,
2097+ schema:: threshold:: modified. eq ( bencher_json:: DateTime :: TEST ) ,
2098+ ) )
2099+ . execute ( & mut conn) ;
2100+
2101+ assert ! ( result. is_err( ) ) ;
2102+ }
19202103}
0 commit comments