@@ -3,7 +3,7 @@ use crate::webserver::database::blob_to_data_url;
33use bigdecimal:: { BigDecimal , ToPrimitive } ;
44use chrono:: { DateTime , FixedOffset , NaiveDateTime } ;
55use serde_json:: { self , Map , Value } ;
6- use sqlx:: any:: { AnyRow , AnyTypeInfo , AnyTypeInfoKind } ;
6+ use sqlx:: any:: { AnyColumn , AnyRow , AnyTypeInfo , AnyTypeInfoKind } ;
77use sqlx:: Decode ;
88use sqlx:: { Column , Row , TypeInfo , ValueRef } ;
99
@@ -13,13 +13,27 @@ pub fn row_to_json(row: &AnyRow) -> Value {
1313 let columns = row. columns ( ) ;
1414 let mut map = Map :: new ( ) ;
1515 for col in columns {
16- let key = col . name ( ) . to_string ( ) ;
16+ let key = canonical_col_name ( col ) ;
1717 let value: Value = sql_to_json ( row, col) ;
1818 map = add_value_to_map ( map, ( key, value) ) ;
1919 }
2020 Object ( map)
2121}
2222
23+ fn canonical_col_name ( col : & AnyColumn ) -> String {
24+ // Some databases fold all unquoted identifiers to uppercase but SQLPage uses lowercase property names
25+ if matches ! ( col. type_info( ) . 0 , AnyTypeInfoKind :: Odbc ( _) )
26+ && col
27+ . name ( )
28+ . chars ( )
29+ . all ( |c| c. is_ascii_uppercase ( ) || c == '_' )
30+ {
31+ col. name ( ) . to_ascii_lowercase ( )
32+ } else {
33+ col. name ( ) . to_owned ( )
34+ }
35+ }
36+
2337pub fn sql_to_json ( row : & AnyRow , col : & sqlx:: any:: AnyColumn ) -> Value {
2438 let raw_value_result = row. try_get_raw ( col. ordinal ( ) ) ;
2539 match raw_value_result {
@@ -160,23 +174,23 @@ mod tests {
160174 let db_url = test_database_url ( ) ;
161175 let mut c = sqlx:: AnyConnection :: connect ( & db_url) . await ?;
162176 let row = sqlx:: query (
163- r# "SELECT
164- 123.456 as "ONE_VALUE" ,
165- 1 as "TWO_VALUES" ,
166- 2 as "TWO_VALUES" ,
167- 'x' as "THREE_VALUES" ,
168- 'y' as "THREE_VALUES" ,
169- 'z' as "THREE_VALUES"
170- "# ,
177+ "SELECT
178+ 123.456 as one_value ,
179+ 1 as two_values ,
180+ 2 as two_values ,
181+ 'x' as three_values ,
182+ 'y' as three_values ,
183+ 'z' as three_values
184+ " ,
171185 )
172186 . fetch_one ( & mut c)
173187 . await ?;
174188 expect_json_object_equal (
175189 & row_to_json ( & row) ,
176190 & serde_json:: json!( {
177- "ONE_VALUE " : 123.456 ,
178- "TWO_VALUES " : [ 1 , 2 ] ,
179- "THREE_VALUES " : [ "x" , "y" , "z" ] ,
191+ "one_value " : 123.456 ,
192+ "two_values " : [ 1 , 2 ] ,
193+ "three_values " : [ "x" , "y" , "z" ] ,
180194 } ) ,
181195 ) ;
182196 Ok ( ( ) )
@@ -506,6 +520,100 @@ mod tests {
506520 ) ;
507521 }
508522
523+ #[ actix_web:: test]
524+ async fn test_canonical_col_name_variations ( ) -> anyhow:: Result < ( ) > {
525+ let db_url = test_database_url ( ) ;
526+ let mut c = sqlx:: AnyConnection :: connect ( & db_url) . await ?;
527+
528+ // Test various column name formats to ensure canonical_col_name works correctly
529+ let row = sqlx:: query (
530+ r#"SELECT
531+ 42 as "UPPERCASE_COL",
532+ 42 as "lowercase_col",
533+ 42 as "Mixed_Case_Col",
534+ 42 as "COL_WITH_123_NUMBERS",
535+ 42 as "col-with-dashes",
536+ 42 as "col with spaces",
537+ 42 as "_UNDERSCORE_PREFIX",
538+ 42 as "123_NUMBER_PREFIX"
539+ "# ,
540+ )
541+ . fetch_one ( & mut c)
542+ . await ?;
543+
544+ let json_result = row_to_json ( & row) ;
545+
546+ // For ODBC databases, uppercase columns should be converted to lowercase
547+ // For other databases, names should remain as-is
548+ let expected_json = if c. kind ( ) == sqlx:: any:: AnyKind :: Odbc {
549+ // ODBC database - uppercase should be converted to lowercase
550+ serde_json:: json!( {
551+ "uppercase_col" : 42 ,
552+ "lowercase_col" : 42 ,
553+ "Mixed_Case_Col" : 42 ,
554+ "COL_WITH_123_NUMBERS" : 42 ,
555+ "col-with-dashes" : 42 ,
556+ "col with spaces" : 42 ,
557+ "_underscore_prefix" : 42 ,
558+ "123_NUMBER_PREFIX" : 42
559+ } )
560+ } else {
561+ // Non-ODBC database - names remain as-is
562+ serde_json:: json!( {
563+ "UPPERCASE_COL" : 42 ,
564+ "lowercase_col" : 42 ,
565+ "Mixed_Case_Col" : 42 ,
566+ "COL_WITH_123_NUMBERS" : 42 ,
567+ "col-with-dashes" : 42 ,
568+ "col with spaces" : 42 ,
569+ "_UNDERSCORE_PREFIX" : 42 ,
570+ "123_NUMBER_PREFIX" : 42
571+ } )
572+ } ;
573+
574+ expect_json_object_equal ( & json_result, & expected_json) ;
575+
576+ Ok ( ( ) )
577+ }
578+
579+ #[ actix_web:: test]
580+ async fn test_row_to_json_edge_cases ( ) -> anyhow:: Result < ( ) > {
581+ let db_url = test_database_url ( ) ;
582+ let mut c = sqlx:: AnyConnection :: connect ( & db_url) . await ?;
583+
584+ // Test edge cases for row_to_json
585+ let row = sqlx:: query (
586+ "SELECT
587+ NULL as null_col,
588+ '' as empty_string,
589+ 0 as zero_value,
590+ -42 as negative_int,
591+ 1.23456 as my_float,
592+ 'special_chars_!@#$%^&*()' as special_chars,
593+ 'line1
594+ line2' as multiline_string
595+ " ,
596+ )
597+ . fetch_one ( & mut c)
598+ . await ?;
599+
600+ let json_result = row_to_json ( & row) ;
601+
602+ let expected_json = serde_json:: json!( {
603+ "null_col" : null,
604+ "empty_string" : "" ,
605+ "zero_value" : 0 ,
606+ "negative_int" : -42 ,
607+ "my_float" : 1.23456 ,
608+ "special_chars" : "special_chars_!@#$%^&*()" ,
609+ "multiline_string" : "line1\n line2"
610+ } ) ;
611+
612+ expect_json_object_equal ( & json_result, & expected_json) ;
613+
614+ Ok ( ( ) )
615+ }
616+
509617 /// Compare JSON values, treating integers and floats that are numerically equal as equal
510618 fn json_values_equal ( a : & Value , b : & Value ) -> bool {
511619 use Value :: * ;
0 commit comments