Skip to content

Commit 0bfd4cc

Browse files
committed
test: Add comprehensive error handling tests for ODBC connections and queries
This commit introduces a suite of tests to validate error handling in various scenarios, including connection errors, SQL syntax errors, parameter binding issues, and transaction errors. The tests ensure that the ODBC implementation gracefully handles invalid inputs and maintains stability across different operations.
1 parent 9d4e17a commit 0bfd4cc

File tree

1 file changed

+296
-1
lines changed

1 file changed

+296
-1
lines changed

tests/odbc/odbc.rs

Lines changed: 296 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use futures::TryStreamExt;
2-
use sqlx_oldapi::odbc::Odbc;
2+
use sqlx_oldapi::odbc::{Odbc, OdbcConnectOptions};
33
use sqlx_oldapi::Column;
44
use sqlx_oldapi::Connection;
55
use sqlx_oldapi::Executor;
@@ -8,6 +8,7 @@ use sqlx_oldapi::Statement;
88
use sqlx_oldapi::Value;
99
use sqlx_oldapi::ValueRef;
1010
use sqlx_test::new;
11+
use std::str::FromStr;
1112

1213
#[tokio::test]
1314
async 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

Comments
 (0)