Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions mssql-odbc/src/api/disconnect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ mod tests {
SQL_ATTR_ODBC_VERSION, SQL_HANDLE_DBC, SQL_HANDLE_ENV, SQL_NULL_HANDLE, SQL_OV_ODBC3_80,
};
use crate::api::set_env_attr::sql_set_env_attr;
use crate::test_support::TestHandles;

#[test]
fn disconnect_when_not_connected() {
Expand Down Expand Up @@ -130,4 +131,17 @@ mod tests {
assert_eq!(ret, SQL_INVALID_HANDLE);
// TODO: verify SQLSTATE HY009 via SQLGetDiagRec
}

#[test]
fn connected_disconnect_succeeds() {
let h = TestHandles::with_env_dbc();
h.mark_dbc_connected();

assert_eq!(unsafe { sql_disconnect(h.dbc) }, SQL_SUCCESS);

let dbc = unsafe { handle_from_raw::<DbcHandle>(h.dbc) };
let state = dbc.inner.lock().unwrap();
assert_eq!(state.connection_state, ConnectionState::Disconnected);
assert!(state.active_stmt.is_none());
}
}
43 changes: 43 additions & 0 deletions mssql-odbc/src/api/exec_direct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ fn clear_exec_started(stmt: &StmtHandle) {
mod tests {
use super::*;
use crate::api::odbc_types::{SQL_NTS, SQL_NULL_HANDLE};
use crate::handles::dbc::DbcHandle;
use crate::test_support::TestHandles;

#[test]
Expand Down Expand Up @@ -293,4 +294,46 @@ mod tests {
// DBC is not connected
assert_eq!(ret, SQL_ERROR);
}

#[test]
fn connected_without_client_returns_error() {
let h = TestHandles::with_env_dbc_stmt();
h.mark_dbc_connected();

let sql: Vec<u16> = "SELECT 1".encode_utf16().chain(Some(0)).collect();
assert_eq!(
unsafe { sql_exec_direct_w(h.stmt, sql.as_ptr(), SQL_NTS) },
SQL_ERROR
);

let stmt = unsafe { handle_from_raw::<StmtHandle>(h.stmt) };
let stmt_state = stmt.inner.lock().unwrap();
assert_eq!(
stmt_state.diag_records[0].sql_state,
ERR_NO_ACTIVE_TDS_CLIENT.state
);
}

#[test]
fn connected_but_busy_with_other_statement_returns_error() {
let mut h = TestHandles::with_env_dbc_stmt();
h.mark_dbc_connected();
let other_stmt = h.alloc_extra_stmt();

let dbc = unsafe { handle_from_raw::<DbcHandle>(h.dbc) };
dbc.inner.lock().unwrap().active_stmt = Some(other_stmt);

let sql: Vec<u16> = "SELECT 1".encode_utf16().chain(Some(0)).collect();
assert_eq!(
unsafe { sql_exec_direct_w(h.stmt, sql.as_ptr(), SQL_NTS) },
SQL_ERROR
);

let stmt = unsafe { handle_from_raw::<StmtHandle>(h.stmt) };
let stmt_state = stmt.inner.lock().unwrap();
assert_eq!(
stmt_state.diag_records[0].sql_state,
ERR_CONNECTION_BUSY.state
);
}
}
184 changes: 184 additions & 0 deletions mssql-odbc/src/api/exports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -519,3 +519,187 @@ pub unsafe extern "C" fn SQLCancel(_statement_handle: SqlHandle) -> SqlReturn {
crate::init_tracing();
SQL_SUCCESS
}

#[cfg(test)]
mod tests {
use std::ptr;

use super::*;
use crate::api::odbc_types::{SQL_DROP, SQL_HANDLE_ENV, SQL_INVALID_HANDLE, SQL_NULL_HANDLE};

/// Every delegating export forwards a null handle to its impl, which
/// uniformly reports `SQL_INVALID_HANDLE`.
#[test]
fn delegating_exports_reject_null_handle() {
let sql: Vec<u16> = "SELECT 1".encode_utf16().chain(Some(0)).collect();
unsafe {
assert_eq!(
SQLFreeHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE),
SQL_INVALID_HANDLE
);
assert_eq!(
SQLSetEnvAttr(SQL_NULL_HANDLE, 0, ptr::null_mut(), 0),
SQL_INVALID_HANDLE
);
assert_eq!(
SQLGetDiagRecW(
SQL_HANDLE_ENV,
SQL_NULL_HANDLE,
1,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
0,
ptr::null_mut(),
),
SQL_INVALID_HANDLE
);
assert_eq!(
SQLGetDiagFieldW(
SQL_HANDLE_ENV,
SQL_NULL_HANDLE,
1,
0,
ptr::null_mut(),
0,
ptr::null_mut(),
),
SQL_INVALID_HANDLE
);
assert_eq!(
SQLDriverConnectW(
SQL_NULL_HANDLE,
ptr::null_mut(),
ptr::null(),
0,
ptr::null_mut(),
0,
ptr::null_mut(),
0,
),
SQL_INVALID_HANDLE
);
assert_eq!(SQLDisconnect(SQL_NULL_HANDLE), SQL_INVALID_HANDLE);
assert_eq!(
SQLSetConnectAttrW(SQL_NULL_HANDLE, 0, ptr::null_mut(), 0),
SQL_INVALID_HANDLE
);
assert_eq!(SQLCloseCursor(SQL_NULL_HANDLE), SQL_INVALID_HANDLE);
assert_eq!(SQLFreeStmt(SQL_NULL_HANDLE, SQL_CLOSE), SQL_INVALID_HANDLE);
assert_eq!(
SQLPrepareW(SQL_NULL_HANDLE, ptr::null(), 0),
SQL_INVALID_HANDLE
);
assert_eq!(
SQLExecDirectW(SQL_NULL_HANDLE, sql.as_ptr(), 0),
SQL_INVALID_HANDLE
);
assert_eq!(SQLFetch(SQL_NULL_HANDLE), SQL_INVALID_HANDLE);
assert_eq!(
SQLNumResultCols(SQL_NULL_HANDLE, ptr::null_mut()),
SQL_INVALID_HANDLE
);
assert_eq!(
SQLDescribeColW(
SQL_NULL_HANDLE,
1,
ptr::null_mut(),
0,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
),
SQL_INVALID_HANDLE
);
assert_eq!(
SQLGetData(SQL_NULL_HANDLE, 1, 0, ptr::null_mut(), 0, ptr::null_mut()),
SQL_INVALID_HANDLE
);
assert_eq!(SQLMoreResults(SQL_NULL_HANDLE), SQL_INVALID_HANDLE);
}
}

/// `SQLAllocHandle` validates its output pointer before touching the parent
/// handle; a null output pointer is `SQL_INVALID_HANDLE`.
#[test]
fn alloc_handle_rejects_null_output() {
let ret = unsafe { SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, ptr::null_mut()) };
assert_eq!(ret, SQL_INVALID_HANDLE);
}

/// Round-trips an ENV handle through the exported alloc/free wrappers.
#[test]
fn alloc_and_free_env_handle() {
let mut env: SqlHandle = SQL_NULL_HANDLE;
assert_eq!(
unsafe { SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &mut env) },
SQL_SUCCESS
);
assert!(!env.is_null());
assert_eq!(unsafe { SQLFreeHandle(SQL_HANDLE_ENV, env) }, SQL_SUCCESS);
}

/// Not-yet-implemented stubs succeed unconditionally regardless of handle.
#[test]
fn stub_exports_return_success() {
unsafe {
let mut row_count: i64 = -1;
assert_eq!(SQLRowCount(SQL_NULL_HANDLE, &mut row_count), SQL_SUCCESS);
assert_eq!(row_count, 0);

assert_eq!(
SQLGetConnectAttrW(SQL_NULL_HANDLE, 0, ptr::null_mut(), 0, ptr::null_mut()),
SQL_SUCCESS
);
assert_eq!(
SQLSetStmtAttrW(SQL_NULL_HANDLE, 0, ptr::null_mut(), 0),
SQL_SUCCESS
);
assert_eq!(
SQLGetStmtAttrW(SQL_NULL_HANDLE, 0, ptr::null_mut(), 0, ptr::null_mut()),
SQL_SUCCESS
);
assert_eq!(
SQLGetDescFieldW(SQL_NULL_HANDLE, 0, 0, ptr::null_mut(), 0, ptr::null_mut()),
SQL_SUCCESS
);
assert_eq!(
SQLBindParameter(
SQL_NULL_HANDLE,
1,
0,
0,
0,
0,
0,
ptr::null_mut(),
0,
ptr::null_mut(),
),
SQL_SUCCESS
);
assert_eq!(SQLCancel(SQL_NULL_HANDLE), SQL_SUCCESS);
}
}

/// `SQLRowCount` skips the write when the output pointer is null.
#[test]
fn row_count_tolerates_null_out_pointer() {
assert_eq!(
unsafe { SQLRowCount(SQL_NULL_HANDLE, ptr::null_mut()) },
SQL_SUCCESS
);
}

/// `SQLFreeStmt` only implements `SQL_CLOSE`; other options hit the default
/// arm and succeed without delegating.
#[test]
fn free_stmt_non_close_option_succeeds() {
assert_eq!(
unsafe { SQLFreeStmt(SQL_NULL_HANDLE, SQL_DROP) },
SQL_SUCCESS
);
}
}
60 changes: 60 additions & 0 deletions mssql-odbc/src/api/more_results.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,63 @@ fn sql_more_results_safe(statement_handle: SqlHandle, stmt: &StmtHandle) -> SqlR
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::api::odbc_types::SQL_NULL_HANDLE;
use crate::handles::dbc::DbcHandle;
use crate::test_support::TestHandles;

#[test]
fn null_handle_returns_invalid_handle() {
assert_eq!(
unsafe { sql_more_results(SQL_NULL_HANDLE) },
SQL_INVALID_HANDLE
);
}

#[test]
fn no_cursor_open_returns_no_data() {
let h = TestHandles::with_env_dbc_stmt();
assert_eq!(unsafe { sql_more_results(h.stmt) }, SQL_NO_DATA);
}

#[test]
fn busy_with_other_statement_returns_error() {
let mut h = TestHandles::with_env_dbc_stmt();
let other_stmt = h.alloc_extra_stmt();

let stmt = unsafe { handle_from_raw::<StmtHandle>(h.stmt) };
stmt.inner.lock().unwrap().set_state(STMT_STATE_CURSOR_OPEN);

let dbc = unsafe { handle_from_raw::<DbcHandle>(h.dbc) };
dbc.inner.lock().unwrap().active_stmt = Some(other_stmt);

assert_eq!(unsafe { sql_more_results(h.stmt) }, SQL_ERROR);

let stmt_state = stmt.inner.lock().unwrap();
assert_eq!(stmt_state.diag_records.len(), 1);
assert_eq!(
stmt_state.diag_records[0].sql_state,
ERR_CONNECTION_BUSY.state
);
}

#[test]
fn cursor_open_without_client_returns_error() {
let h = TestHandles::with_env_dbc_stmt();

let stmt = unsafe { handle_from_raw::<StmtHandle>(h.stmt) };
stmt.inner.lock().unwrap().set_state(STMT_STATE_CURSOR_OPEN);

assert_eq!(unsafe { sql_more_results(h.stmt) }, SQL_ERROR);

let stmt_state = stmt.inner.lock().unwrap();
assert_eq!(stmt_state.diag_records.len(), 1);
assert_eq!(
stmt_state.diag_records[0].sql_state,
ERR_NO_ACTIVE_TDS_CLIENT.state
);
}
}
Loading
Loading