11use futures:: TryStreamExt ;
2- use sqlx_oldapi:: odbc:: Odbc ;
2+ use sqlx_oldapi:: odbc:: { Odbc , OdbcConnectOptions } ;
33use sqlx_oldapi:: Column ;
44use sqlx_oldapi:: Connection ;
55use sqlx_oldapi:: Executor ;
@@ -8,6 +8,7 @@ use sqlx_oldapi::Statement;
88use sqlx_oldapi:: Value ;
99use sqlx_oldapi:: ValueRef ;
1010use sqlx_test:: new;
11+ use std:: str:: FromStr ;
1112
1213#[ tokio:: test]
1314async fn it_connects_and_pings ( ) -> anyhow:: Result < ( ) > {
@@ -636,3 +637,297 @@ async fn it_handles_numeric_precision() -> anyhow::Result<()> {
636637
637638 Ok ( ( ) )
638639}
640+
641+ // Error case tests
642+
643+ #[ tokio:: test]
644+ async fn it_handles_connection_level_errors ( ) -> anyhow:: Result < ( ) > {
645+ // Test connection with obviously invalid connection strings
646+ let invalid_opts = OdbcConnectOptions :: from_str ( "DSN=DefinitelyNonExistentDataSource_12345" ) ?;
647+ let result = sqlx_oldapi:: odbc:: OdbcConnection :: connect_with ( & invalid_opts) . await ;
648+ // This should reliably fail across all ODBC drivers
649+ assert ! ( result. is_err( ) ) ;
650+
651+ // Test with malformed connection string
652+ let malformed_opts = OdbcConnectOptions :: from_str ( "INVALID_KEY_VALUE_PAIRS;;;" ) ?;
653+ let result = sqlx_oldapi:: odbc:: OdbcConnection :: connect_with ( & malformed_opts) . await ;
654+ // This should also reliably fail
655+ assert ! ( result. is_err( ) ) ;
656+
657+ Ok ( ( ) )
658+ }
659+
660+ #[ tokio:: test]
661+ async fn it_handles_sql_syntax_errors ( ) -> anyhow:: Result < ( ) > {
662+ let mut conn = new :: < Odbc > ( ) . await ?;
663+
664+ // Test invalid SQL syntax
665+ let result = conn. execute ( "INVALID SQL SYNTAX THAT SHOULD FAIL" ) . await ;
666+ assert ! ( result. is_err( ) ) ;
667+
668+ // Test malformed SELECT
669+ let result = conn. execute ( "SELECT FROM WHERE" ) . await ;
670+ assert ! ( result. is_err( ) ) ;
671+
672+ // Test unclosed quotes
673+ let result = conn. execute ( "SELECT 'unclosed string" ) . await ;
674+ assert ! ( result. is_err( ) ) ;
675+
676+ Ok ( ( ) )
677+ }
678+
679+ #[ tokio:: test]
680+ async fn it_handles_prepare_statement_errors ( ) -> anyhow:: Result < ( ) > {
681+ let mut conn = new :: < Odbc > ( ) . await ?;
682+
683+ // Many ODBC drivers are permissive at prepare time and only validate at execution
684+ // So we test that execution fails even if preparation succeeds
685+
686+ // Test executing prepared invalid SQL
687+ if let Ok ( stmt) = ( & mut conn) . prepare ( "INVALID PREPARE STATEMENT" ) . await {
688+ let result = stmt. query ( ) . fetch_one ( & mut conn) . await ;
689+ assert ! ( result. is_err( ) ) ;
690+ }
691+
692+ // Test executing prepared SQL with syntax errors
693+ if let Ok ( stmt) = ( & mut conn) . prepare ( "SELECT FROM WHERE 1=1" ) . await {
694+ let result = stmt. query ( ) . fetch_one ( & mut conn) . await ;
695+ assert ! ( result. is_err( ) ) ;
696+ }
697+
698+ // Test with completely malformed SQL that should fail even permissive drivers
699+ let result = ( & mut conn) . prepare ( "" ) . await ;
700+ // Empty SQL should generally fail, but if it doesn't, that's also valid ODBC behavior
701+ let _ = result;
702+
703+ Ok ( ( ) )
704+ }
705+
706+ #[ tokio:: test]
707+ async fn it_handles_parameter_binding_errors ( ) -> anyhow:: Result < ( ) > {
708+ let mut conn = new :: < Odbc > ( ) . await ?;
709+
710+ // Test with completely missing parameters - this should more reliably fail
711+ let stmt = ( & mut conn)
712+ . prepare ( "SELECT ? AS param1, ? AS param2" )
713+ . await ?;
714+
715+ // Test with no parameters when some are expected
716+ let result = stmt. query ( ) . fetch_one ( & mut conn) . await ;
717+ // This test may or may not fail depending on ODBC driver behavior
718+ // Some drivers are permissive and treat missing params as NULL
719+ // The important thing is that we don't panic
720+ let _ = result;
721+
722+ // Test that we can handle parameter binding gracefully
723+ // Even if the driver is permissive, the system should be robust
724+ let stmt2 = ( & mut conn) . prepare ( "SELECT ? AS single_param" ) . await ?;
725+
726+ // Bind correct number of parameters - this should work
727+ let result = stmt2. query ( ) . bind ( 42i32 ) . fetch_one ( & mut conn) . await ;
728+ // If this fails, it's likely due to other issues, not parameter count
729+ if result. is_err ( ) {
730+ // Log that even basic parameter binding failed - this indicates deeper issues
731+ println ! ( "Note: Basic parameter binding failed, may indicate driver issues" ) ;
732+ }
733+
734+ Ok ( ( ) )
735+ }
736+
737+ #[ tokio:: test]
738+ async fn it_handles_parameter_execution_errors ( ) -> anyhow:: Result < ( ) > {
739+ let mut conn = new :: < Odbc > ( ) . await ?;
740+
741+ // Test parameter binding with incompatible operations that should fail at execution
742+ let stmt = ( & mut conn) . prepare ( "SELECT ? / 0 AS div_by_zero" ) . await ?;
743+
744+ // This should execute but may produce a runtime error (division by zero)
745+ let result = stmt. query ( ) . bind ( 42i32 ) . fetch_one ( & mut conn) . await ;
746+ // Division by zero behavior is database-specific, so we just ensure no panic
747+ let _ = result;
748+
749+ // Test with a parameter in an invalid context that should fail
750+ if let Ok ( stmt) = ( & mut conn) . prepare ( "SELECT * FROM ?" ) . await {
751+ // Using parameter as table name should fail at execution
752+ let result = stmt
753+ . query ( )
754+ . bind ( "non_existent_table" )
755+ . fetch_one ( & mut conn)
756+ . await ;
757+ assert ! ( result. is_err( ) ) ;
758+ }
759+
760+ Ok ( ( ) )
761+ }
762+
763+ #[ tokio:: test]
764+ async fn it_handles_fetch_errors_from_invalid_queries ( ) -> anyhow:: Result < ( ) > {
765+ let mut conn = new :: < Odbc > ( ) . await ?;
766+
767+ // Test fetching from invalid table
768+ {
769+ let mut stream = conn. fetch ( "SELECT * FROM non_existent_table_12345" ) ;
770+ let result = stream. try_next ( ) . await ;
771+ assert ! ( result. is_err( ) ) ;
772+ }
773+
774+ // Test fetching with invalid column references
775+ {
776+ let mut stream =
777+ conn. fetch ( "SELECT non_existent_column FROM (SELECT 1 AS existing_column) t" ) ;
778+ let result = stream. try_next ( ) . await ;
779+ assert ! ( result. is_err( ) ) ;
780+ }
781+
782+ Ok ( ( ) )
783+ }
784+
785+ #[ tokio:: test]
786+ async fn it_handles_transaction_errors ( ) -> anyhow:: Result < ( ) > {
787+ let mut conn = new :: < Odbc > ( ) . await ?;
788+
789+ // Start a transaction
790+ let mut tx = conn. begin ( ) . await ?;
791+
792+ // Try to execute invalid SQL in transaction
793+ let result = tx. execute ( "INVALID TRANSACTION SQL" ) . await ;
794+ assert ! ( result. is_err( ) ) ;
795+
796+ // Transaction should still be rollbackable even after error
797+ let rollback_result = tx. rollback ( ) . await ;
798+ // Some databases may auto-rollback on errors, so we don't assert success here
799+ // Just ensure we don't panic
800+ let _ = rollback_result;
801+
802+ Ok ( ( ) )
803+ }
804+
805+ #[ tokio:: test]
806+ async fn it_handles_fetch_optional_errors ( ) -> anyhow:: Result < ( ) > {
807+ let mut conn = new :: < Odbc > ( ) . await ?;
808+
809+ // Test fetch_optional with invalid SQL
810+ let result = ( & mut conn)
811+ . fetch_optional ( "INVALID SQL FOR FETCH OPTIONAL" )
812+ . await ;
813+ assert ! ( result. is_err( ) ) ;
814+
815+ // Test fetch_optional with malformed query
816+ let result = ( & mut conn) . fetch_optional ( "SELECT FROM" ) . await ;
817+ assert ! ( result. is_err( ) ) ;
818+
819+ Ok ( ( ) )
820+ }
821+
822+ #[ tokio:: test]
823+ async fn it_handles_execute_many_errors ( ) -> anyhow:: Result < ( ) > {
824+ let mut conn = new :: < Odbc > ( ) . await ?;
825+
826+ // Test execute with invalid SQL that would affect multiple rows
827+ let result = conn. execute ( "UPDATE non_existent_table SET col = 1" ) . await ;
828+ assert ! ( result. is_err( ) ) ;
829+
830+ // Test execute with constraint violations (if supported by the database)
831+ // This is database-specific, so we'll test with a more generic invalid statement
832+ let result = conn
833+ . execute ( "INSERT INTO non_existent_table VALUES (1)" )
834+ . await ;
835+ assert ! ( result. is_err( ) ) ;
836+
837+ Ok ( ( ) )
838+ }
839+
840+ #[ tokio:: test]
841+ async fn it_handles_invalid_column_access ( ) -> anyhow:: Result < ( ) > {
842+ let mut conn = new :: < Odbc > ( ) . await ?;
843+
844+ let mut stream = conn. fetch ( "SELECT 'test' AS single_column" ) ;
845+ if let Some ( row) = stream. try_next ( ) . await ? {
846+ // Test accessing non-existent column by index
847+ let result = row. try_get_raw ( 999 ) ; // Invalid index
848+ assert ! ( result. is_err( ) ) ;
849+
850+ // Test accessing non-existent column by name
851+ let result = row. try_get_raw ( "non_existent_column" ) ;
852+ assert ! ( result. is_err( ) ) ;
853+ }
854+
855+ Ok ( ( ) )
856+ }
857+
858+ #[ tokio:: test]
859+ async fn it_handles_type_conversion_errors ( ) -> anyhow:: Result < ( ) > {
860+ let mut conn = new :: < Odbc > ( ) . await ?;
861+
862+ let mut stream = conn. fetch ( "SELECT 'not_a_number' AS text_value" ) ;
863+ if let Some ( row) = stream. try_next ( ) . await ? {
864+ // Try to decode text as number - this might succeed or fail depending on implementation
865+ // The error handling depends on whether the decode trait panics or returns a result
866+ let text_val = row. try_get_raw ( 0 ) ?. to_owned ( ) ;
867+
868+ // Test decoding text as different types
869+ // Some type conversions might work (string parsing) while others might fail
870+ // This tests the robustness of the type system
871+ let _: Result < i32 , _ > = std:: panic:: catch_unwind ( || text_val. decode :: < i32 > ( ) ) ;
872+
873+ // The test should not panic even with invalid conversions
874+ }
875+
876+ Ok ( ( ) )
877+ }
878+
879+ #[ tokio:: test]
880+ async fn it_handles_large_invalid_queries ( ) -> anyhow:: Result < ( ) > {
881+ let mut conn = new :: < Odbc > ( ) . await ?;
882+
883+ // Test with very long invalid SQL
884+ let large_invalid_sql = "SELECT " . to_string ( ) + & "invalid_column, " . repeat ( 1000 ) + "1" ;
885+ let result = conn. execute ( large_invalid_sql. as_str ( ) ) . await ;
886+ assert ! ( result. is_err( ) ) ;
887+
888+ // Test with deeply nested invalid SQL
889+ let nested_invalid_sql = "SELECT (" . repeat ( 100 ) + "1" + & ")" . repeat ( 100 ) + " FROM non_existent" ;
890+ let result = conn. execute ( nested_invalid_sql. as_str ( ) ) . await ;
891+ assert ! ( result. is_err( ) ) ;
892+
893+ Ok ( ( ) )
894+ }
895+
896+ #[ tokio:: test]
897+ async fn it_handles_concurrent_error_scenarios ( ) -> anyhow:: Result < ( ) > {
898+ let mut conn = new :: < Odbc > ( ) . await ?;
899+
900+ // Test multiple invalid operations in sequence
901+ let _ = conn. execute ( "INVALID SQL 1" ) . await ;
902+ let _ = conn. execute ( "INVALID SQL 2" ) . await ;
903+ let _ = conn. execute ( "INVALID SQL 3" ) . await ;
904+
905+ // Connection should still be usable after errors
906+ let valid_result = conn. execute ( "SELECT 1" ) . await ;
907+ // Some databases may close connection on errors, others may keep it open
908+ // We just ensure no panic occurs
909+ let _ = valid_result;
910+
911+ Ok ( ( ) )
912+ }
913+
914+ #[ tokio:: test]
915+ async fn it_handles_prepared_statement_with_wrong_parameters ( ) -> anyhow:: Result < ( ) > {
916+ let mut conn = new :: < Odbc > ( ) . await ?;
917+
918+ // Prepare a statement expecting specific parameter types
919+ let stmt = ( & mut conn) . prepare ( "SELECT ? + ? AS sum" ) . await ?;
920+
921+ // Test binding incompatible types (if the database is strict about types)
922+ // Some databases/drivers are permissive, others are strict
923+ let result = stmt
924+ . query ( )
925+ . bind ( "not_a_number" )
926+ . bind ( "also_not_a_number" )
927+ . fetch_one ( & mut conn)
928+ . await ;
929+ // This may or may not error depending on the database's type system
930+ let _ = result;
931+
932+ Ok ( ( ) )
933+ }
0 commit comments