@@ -509,6 +509,171 @@ fn select_star_and_subquery_field_builds() {
509509 ) ;
510510}
511511
512+ #[ test]
513+ fn order_by_field_with_trailing_desc_uses_default_asc ( ) {
514+ // Suffix "DESC" is stripped; OrderOptions::default() => Sort::Asc applies.
515+ let sql = QueryBuilder :: select ( surrealex:: fields!( "id" ) )
516+ . from ( "users" )
517+ . order_by (
518+ "name DESC" ,
519+ surrealex:: types:: select:: OrderOptions :: default ( ) ,
520+ )
521+ . build ( ) ;
522+ assert_eq ! ( sql, "SELECT id FROM users ORDER BY name ASC" ) ;
523+ }
524+
525+ #[ test]
526+ fn order_by_field_with_trailing_desc_and_explicit_desc_no_duplication ( ) {
527+ // Suffix "DESC" is stripped; explicit Sort::Desc still produces a single DESC.
528+ let sql = QueryBuilder :: select ( surrealex:: fields!( "id" ) )
529+ . from ( "users" )
530+ . order_by ( "name DESC" , Sort :: Desc )
531+ . build ( ) ;
532+ assert_eq ! ( sql, "SELECT id FROM users ORDER BY name DESC" ) ;
533+ }
534+
535+ #[ test]
536+ fn order_by_field_with_trailing_desc_explicit_asc_wins ( ) {
537+ // Suffix "DESC" is stripped and discarded; explicit Sort::Asc wins.
538+ let sql = QueryBuilder :: select ( surrealex:: fields!( "id" ) )
539+ . from ( "users" )
540+ . order_by ( "name DESC" , Sort :: Asc )
541+ . build ( ) ;
542+ assert_eq ! ( sql, "SELECT id FROM users ORDER BY name ASC" ) ;
543+ }
544+
545+ #[ test]
546+ fn order_by_function_call_with_trailing_desc_uses_default_asc ( ) {
547+ // Suffix after closing paren is stripped; default Sort::Asc applies.
548+ let sql = QueryBuilder :: select ( surrealex:: fields!( "id" ) )
549+ . from ( "users" )
550+ . order_by ( "LOWER(name) DESC" , ( ) )
551+ . build ( ) ;
552+ assert_eq ! ( sql, "SELECT id FROM users ORDER BY LOWER(name) ASC" ) ;
553+ }
554+
555+ #[ test]
556+ fn order_by_collate_numeric_desc_combination_all_stripped ( ) {
557+ // All three trailing modifiers are stripped; default Sort::Asc applies.
558+ let sql = QueryBuilder :: select ( surrealex:: fields!( "id" ) )
559+ . from ( "scores" )
560+ . order_by ( "score COLLATE NUMERIC DESC" , ( ) )
561+ . build ( ) ;
562+ assert_eq ! ( sql, "SELECT id FROM scores ORDER BY score ASC" ) ;
563+ }
564+
565+ #[ test]
566+ fn order_by_trailing_asc_stripped_explicit_desc_wins ( ) {
567+ // Trailing "ASC" in field is stripped; explicit Sort::Desc applies.
568+ let sql = QueryBuilder :: select ( surrealex:: fields!( "id" ) )
569+ . from ( "t" )
570+ . order_by ( "name ASC" , Sort :: Desc )
571+ . build ( ) ;
572+ assert_eq ! ( sql, "SELECT id FROM t ORDER BY name DESC" ) ;
573+ }
574+
575+ #[ test]
576+ fn order_by_trailing_numeric_stripped_explicit_sort_applies ( ) {
577+ // Trailing "NUMERIC" is stripped; explicit Sort::Asc applies.
578+ let sql = QueryBuilder :: select ( surrealex:: fields!( "id" ) )
579+ . from ( "t" )
580+ . order_by ( "score NUMERIC" , Sort :: Asc )
581+ . build ( ) ;
582+ assert_eq ! ( sql, "SELECT id FROM t ORDER BY score ASC" ) ;
583+ }
584+
585+ #[ test]
586+ fn order_by_no_trailing_modifier_unchanged ( ) {
587+ // No suffix present — field passes through unmodified.
588+ let sql = QueryBuilder :: select ( surrealex:: fields!( "id" ) )
589+ . from ( "t" )
590+ . order_by ( "name" , Sort :: Desc )
591+ . build ( ) ;
592+ assert_eq ! ( sql, "SELECT id FROM t ORDER BY name DESC" ) ;
593+ }
594+
595+ #[ test]
596+ fn order_by_lowercase_trailing_desc_stripped ( ) {
597+ // Stripping is case-insensitive; lowercase "desc" is stripped.
598+ let sql = QueryBuilder :: select ( surrealex:: fields!( "id" ) )
599+ . from ( "t" )
600+ . order_by ( "name desc" , Sort :: Asc )
601+ . build ( ) ;
602+ assert_eq ! ( sql, "SELECT id FROM t ORDER BY name ASC" ) ;
603+ }
604+
605+ #[ test]
606+ fn order_by_mixed_case_trailing_modifier_stripped ( ) {
607+ // Mixed-case modifier is stripped.
608+ let sql = QueryBuilder :: select ( surrealex:: fields!( "id" ) )
609+ . from ( "t" )
610+ . order_by ( "name Desc" , Sort :: Desc )
611+ . build ( ) ;
612+ assert_eq ! ( sql, "SELECT id FROM t ORDER BY name DESC" ) ;
613+ }
614+
615+ #[ test]
616+ fn order_by_field_name_containing_desc_substring_not_stripped ( ) {
617+ // "described" does not end with the token " DESC", so nothing is stripped.
618+ let sql = QueryBuilder :: select ( surrealex:: fields!( "id" ) )
619+ . from ( "t" )
620+ . order_by ( "described" , Sort :: Asc )
621+ . build ( ) ;
622+ assert_eq ! ( sql, "SELECT id FROM t ORDER BY described ASC" ) ;
623+ }
624+
625+ #[ test]
626+ fn order_by_explicit_numeric_collate_options_still_applied_after_sanitize ( ) {
627+ // Even when a suffix is present, explicit numeric+collate flags from Sort::Desc are kept.
628+ let sql = QueryBuilder :: select ( surrealex:: fields!( "id" ) )
629+ . from ( "t" )
630+ . order_by ( "name DESC" , Sort :: Desc . collate ( ) . numeric ( ) )
631+ . build ( ) ;
632+ assert_eq ! ( sql, "SELECT id FROM t ORDER BY name COLLATE NUMERIC DESC" ) ;
633+ }
634+
635+ #[ test]
636+ fn sanitize_field_strips_single_desc ( ) {
637+ use surrealex:: types:: select:: OrderTerm ;
638+ let field = OrderTerm :: sanitize_field ( "name DESC" ) ;
639+ assert_eq ! ( field, "name" ) ;
640+ }
641+
642+ #[ test]
643+ fn sanitize_field_strips_collate_numeric_desc_chain ( ) {
644+ use surrealex:: types:: select:: OrderTerm ;
645+ let field = OrderTerm :: sanitize_field ( "score COLLATE NUMERIC DESC" ) ;
646+ assert_eq ! ( field, "score" ) ;
647+ }
648+
649+ #[ test]
650+ fn sanitize_field_strips_suffix_after_function_call ( ) {
651+ use surrealex:: types:: select:: OrderTerm ;
652+ let field = OrderTerm :: sanitize_field ( "LOWER(name) DESC" ) ;
653+ assert_eq ! ( field, "LOWER(name)" ) ;
654+ }
655+
656+ #[ test]
657+ fn sanitize_field_no_modifier_returns_unchanged ( ) {
658+ use surrealex:: types:: select:: OrderTerm ;
659+ let field = OrderTerm :: sanitize_field ( "name" ) ;
660+ assert_eq ! ( field, "name" ) ;
661+ }
662+
663+ #[ test]
664+ fn sanitize_field_case_insensitive_strip ( ) {
665+ use surrealex:: types:: select:: OrderTerm ;
666+ let field = OrderTerm :: sanitize_field ( "name desc" ) ;
667+ assert_eq ! ( field, "name" ) ;
668+ }
669+
670+ #[ test]
671+ fn sanitize_field_embedded_desc_substring_not_stripped ( ) {
672+ use surrealex:: types:: select:: OrderTerm ;
673+ let field = OrderTerm :: sanitize_field ( "described" ) ;
674+ assert_eq ! ( field, "described" ) ;
675+ }
676+
512677#[ test]
513678fn explain_simple_builds ( ) {
514679 let sql = QueryBuilder :: select ( surrealex:: fields!( "id" ) )
0 commit comments