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
10 changes: 10 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[env]
# Point the ODBC stack at project-local config files instead of ~/.odbc.ini / ~/.odbcinst.ini.
# ODBCINI: path to the user odbc.ini (DSN definitions)
# ODBCSYSINI: directory containing the system odbcinst.ini (driver registrations)
#
# relative = true resolves each value against the workspace root, producing an absolute path.
# test_data/odbcinst.ini is generated by scripts/build-and-setup.sh and is gitignored
# (it contains a machine-specific path to target/debug/libodbc_driver_rs.so).
ODBCINI = { value = "test_data/odbc.ini", relative = true }
ODBCSYSINI = { value = "test_data", relative = true }
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/target
/deps
test_odbc.sqlite
test_data/odbcinst.ini
lars_notes.md
.claude/
ODBC
/docs/superpowers/plans/
120 changes: 9 additions & 111 deletions scripts/build-and-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,136 +8,34 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
BUILD_TYPE="${1:-debug}"

echo "🔨 Building ODBC driver (${BUILD_TYPE} mode)..."
echo "Building ODBC driver (${BUILD_TYPE} mode)..."

cd "$PROJECT_ROOT"

# Build the driver
if [[ "$BUILD_TYPE" == "release" ]]; then
echo "🚀 Building release version..."
cargo build --release
DRIVER_PATH="$PROJECT_ROOT/target/release/libodbc_driver_rs.so"
else
echo "🐛 Building debug version..."
cargo build
DRIVER_PATH="$PROJECT_ROOT/target/debug/libodbc_driver_rs.so"
fi

# Verify the driver was built
if [[ ! -f "$DRIVER_PATH" ]]; then
echo "❌ Error: Driver library not found at $DRIVER_PATH"
echo " Build may have failed. Check the output above."
echo "Error: Driver library not found at $DRIVER_PATH"
exit 1
fi

echo "✅ Driver built successfully: $DRIVER_PATH"

# ODBC configuration
ODBCINST_INI="$HOME/.odbcinst.ini"
ODBC_INI="$HOME/.odbc.ini"
TEST_DB_PATH="$PROJECT_ROOT/test_odbc.sqlite"

echo ""
echo "🔧 Configuring ODBC settings..."

# Create or update .odbcinst.ini (driver configuration)
echo "📝 Updating driver configuration: $ODBCINST_INI"

# Create backup if file exists and no backup exists yet
if [[ -f "$ODBCINST_INI" ]] && [[ ! -f "${ODBCINST_INI}.backup" ]]; then
echo "📋 Creating one-time backup: ${ODBCINST_INI}.backup"
cp "$ODBCINST_INI" "${ODBCINST_INI}.backup"
fi

# Remove existing odbcrs_sqlite section and add new one
if [[ -f "$ODBCINST_INI" ]]; then
# Remove existing section if present
sed -i '/^\[odbcrs_sqlite\]/,/^\[/{ /^\[odbcrs_sqlite\]/d; /^\[/!d; }' "$ODBCINST_INI" 2>/dev/null || true
fi

# Add our driver configuration
cat >> "$ODBCINST_INI" << EOF

# Write test_data/odbcinst.ini with the absolute path to the built driver.
# This file is gitignored because the path is machine-specific.
# ODBCSYSINI in .cargo/config.toml points cargo test at test_data/ so that
# unixODBC finds this file instead of ~/.odbcinst.ini.
cat > "$PROJECT_ROOT/test_data/odbcinst.ini" << EOF
[odbcrs_sqlite]
Driver = $DRIVER_PATH
Description = Experimental Rust SQLite ODBC Driver
Threading = 2
UsageCount = 1
EOF

echo "✅ Driver registered in $ODBCINST_INI"

# Create or update .odbc.ini (data source configuration)
echo "📝 Updating data source configuration: $ODBC_INI"

# Create backup if file exists and no backup exists yet
if [[ -f "$ODBC_INI" ]] && [[ ! -f "${ODBC_INI}.backup" ]]; then
echo "📋 Creating one-time backup: ${ODBC_INI}.backup"
cp "$ODBC_INI" "${ODBC_INI}.backup"
fi

# Remove existing test_connection section and add new one
if [[ -f "$ODBC_INI" ]]; then
sed -i '/^\[test_connection\]/,/^\[/{ /^\[test_connection\]/d; /^\[/!d; }' "$ODBC_INI" 2>/dev/null || true
fi

# Add our data source configuration
cat >> "$ODBC_INI" << EOF

[test_connection]
Driver = odbcrs_sqlite
Database = $TEST_DB_PATH
Description = Test connection for ODBC Rust driver development
EOF

echo "✅ Data source configured in $ODBC_INI"

# Verify test database exists
if [[ ! -f "$TEST_DB_PATH" ]]; then
echo "⚠️ Test database not found. Creating it..."
./scripts/setup-test-db.sh
fi

# Verify ODBC installation
echo ""
echo "🔍 Verifying ODBC installation..."

# Check if odbcinst is available
if command -v odbcinst &> /dev/null; then
echo "📋 Installed drivers:"
odbcinst -q -d | grep -E "(odbcrs_sqlite|SQLite)" || echo " (No SQLite drivers found)"

echo ""
echo "📋 Configured data sources:"
odbcinst -q -s | grep -E "(test_connection)" || echo " (test_connection not found)"
else
echo "⚠️ odbcinst command not found. Install unixODBC development tools:"
echo " Ubuntu/Debian: sudo apt-get install unixodbc-dev"
echo " Arch Linux: sudo pacman -S unixodbc"
fi

# Check if isql is available for testing
if command -v isql &> /dev/null; then
echo ""
echo "🎯 Ready for testing! Try these commands:"
echo " isql -3 test_connection -v # Test connection"
echo " help; # List tables (should work)"
echo " SELECT * FROM users; # Query data (may not work yet)"
else
echo ""
echo "⚠️ isql command not found. Install unixODBC:"
echo " Ubuntu/Debian: sudo apt-get install unixodbc"
echo " Arch Linux: sudo pacman -S unixodbc"
fi

echo ""
echo "📁 Configuration summary:"
echo " Driver: $DRIVER_PATH"
echo " Database: $TEST_DB_PATH"
echo " Driver config: $ODBCINST_INI"
echo " Data source config: $ODBC_INI"

echo ""
echo "🚀 Build and setup complete!"
echo " Run 'cargo test' to verify unit tests still pass"
echo " Use './scripts/run-tests.sh' for integration testing (when available)"
echo "Driver registered in test_data/odbcinst.ini ($DRIVER_PATH)"
echo "Setup complete. Run 'cargo test' to verify."
2 changes: 0 additions & 2 deletions src/connection.rs

This file was deleted.

33 changes: 17 additions & 16 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,41 +1,42 @@
use odbc_sys::{Integer, WChar};

mod connection;
mod logging;
mod odbc;

// Cross-platform ODBC library linking configuration
// These attributes handle the complexity of linking against different ODBC implementations
// across Windows, Linux, and macOS with support for both static and dynamic linking.
/// Registers the SQLite implementation with the driver's handle factory.
/// Called once at driver startup (from SQLAllocHandle for environment handles).
pub(crate) fn init_driver() {
odbc::handles::register_factory(Box::new(
odbc::implementation::query::SqliteDbConnectionFactory,
));
}

// Windows: Use the built-in Windows ODBC library
#[cfg_attr(windows, link(name = "odbc32"))]
// Unix + Dynamic + unixODBC (default case for Linux)
// SQLGetPrivateProfileStringW is an ODBC installer/configuration function, not a Driver Manager
// function. It lives in different libraries depending on platform:
// Windows: odbccp32.dll (ODBC installer library, NOT odbc32.dll)
// Unix + unixODBC: libodbcinst (dynamic or static)
// Unix + iODBC: libiodbcinst (dynamic or static)
//
// This function is declared here because it is not in the standard odbc-sys crate.
// An upstream contribution was rejected: https://github.com/pacman82/odbc-sys/pull/44
#[cfg_attr(windows, link(name = "odbccp32"))]
#[cfg_attr(
all(not(windows), not(feature = "static"), not(feature = "iodbc")),
link(name = "odbcinst")
)]
// Unix + Static + unixODBC (for self-contained binaries)
#[cfg_attr(
all(not(windows), feature = "static", not(feature = "iodbc")),
link(name = "odbcinst", kind = "static")
)]
// Unix + Dynamic + iODBC (common on macOS)
#[cfg_attr(
all(not(windows), not(feature = "static"), feature = "iodbc"),
link(name = "iodbcinst")
)]
// Unix + Static + iODBC (self-contained binaries with iODBC)
#[cfg_attr(
all(not(windows), feature = "static", feature = "iodbc"),
link(name = "iodbcinst", kind = "static")
)]
// This function is here because it's needed for ODBC configuration file parsing
// but doesn't exist in the standard odbc-sys crate.
// It was added here directly temporarily, eventually this might move to a separate file or library.
// We tried contributing it upstream in odbc-sys but it got rejected for good reason:
// https://github.com/pacman82/odbc-sys/pull/44
unsafe extern "C" {
unsafe extern "system" {
/// Gets a list of names of values or data corresponding to a value of the system information.
///
/// # Returns
Expand Down
3 changes: 2 additions & 1 deletion src/odbc.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod api;
mod def;
mod implementation;
pub(crate) mod handles;
pub(crate) mod implementation;
mod utils;
18 changes: 9 additions & 9 deletions src/odbc/api/sqlallochandle.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
use crate::logging;
use crate::odbc::implementation::alloc_handles::{
ConnectionHandle, EnvironmentHandle, allocate_stmt_handle, impl_allocate_dbc_handle,
impl_allocate_environment_handle,
};
use crate::odbc::handles::{ConnectionHandle, EnvironmentHandle};
use crate::odbc::utils::{get_from_wrapper, wrap_and_set};
use odbc_sys::{HandleType, Pointer, SmallInt, SqlReturn};
use tracing::{debug, error, info};

/// SQLAllocHandle allocates an environment, connection, statement, or descriptor handle.
#[allow(non_snake_case)]
#[unsafe(no_mangle)]
pub extern "C" fn SQLAllocHandle(
pub extern "system" fn SQLAllocHandle(
handle_type: SmallInt,
input_handle: Pointer,
output_handle: *mut Pointer,
Expand Down Expand Up @@ -50,8 +47,10 @@ pub extern "C" fn SQLAllocHandle(
return SqlReturn::ERROR;
}

// Call the implementation and convert the output properly
let handle = impl_allocate_environment_handle();
// Register the SQLite factory on first use.
crate::init_driver();

let handle = EnvironmentHandle::default();
wrap_and_set(handle_type, handle, output_handle);

info!("Successfully allocated an environment handle");
Expand Down Expand Up @@ -79,7 +78,8 @@ pub extern "C" fn SQLAllocHandle(
}
};

let handle = impl_allocate_dbc_handle(env_handle);
let handle = ConnectionHandle { connection: None };
let _ = env_handle; // env_handle validated above; connection state lives in the handle
wrap_and_set(handle_type, handle, output_handle);

info!("Successfully allocated a Dbc handle");
Expand All @@ -97,7 +97,7 @@ pub extern "C" fn SQLAllocHandle(
}
};

let handle = match allocate_stmt_handle(connection_handle) {
let handle = match connection_handle.allocate_stmt_handle() {
Some(handle) => handle,
None => {
error!("Cannot allocate statement handle: no active connection");
Expand Down
2 changes: 1 addition & 1 deletion src/odbc/api/sqlbindcol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use tracing::info;

#[allow(non_snake_case)]
#[unsafe(no_mangle)]
pub extern "C" fn SQLBindCol(
pub extern "system" fn SQLBindCol(
_statement_handle: *mut c_void,
_column_number: u16,
_target_type: i16,
Expand Down
2 changes: 1 addition & 1 deletion src/odbc/api/sqlbindparameter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use tracing::info;

#[allow(non_snake_case)]
#[unsafe(no_mangle)]
pub extern "C" fn SQLBindParameter(
pub extern "system" fn SQLBindParameter(
_statement_handle: *mut c_void,
_parameter_number: u16,
_input_output_type: i16,
Expand Down
2 changes: 1 addition & 1 deletion src/odbc/api/sqlbrowseconnect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use tracing::info;

#[allow(non_snake_case)]
#[unsafe(no_mangle)]
pub extern "C" fn SQLBrowseConnectW(
pub extern "system" fn SQLBrowseConnectW(
_connection_handle: *mut c_void,
_in_connection_string: *const u16,
_in_string_length: i16,
Expand Down
2 changes: 1 addition & 1 deletion src/odbc/api/sqlbulkoperations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use tracing::info;

#[allow(non_snake_case)]
#[unsafe(no_mangle)]
pub extern "C" fn SQLBulkOperations(_statement_handle: *mut c_void, _operation: u16) -> SqlReturn {
pub extern "system" fn SQLBulkOperations(_statement_handle: *mut c_void, _operation: u16) -> SqlReturn {
info!("SQLBulkOperations");
SqlReturn::SUCCESS
}
2 changes: 1 addition & 1 deletion src/odbc/api/sqlcancel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use tracing::info;

#[allow(non_snake_case)]
#[unsafe(no_mangle)]
pub extern "C" fn SQLCancel(_statement_handle: *mut c_void) -> SqlReturn {
pub extern "system" fn SQLCancel(_statement_handle: *mut c_void) -> SqlReturn {
info!("SQLCancel");
SqlReturn::SUCCESS
}
2 changes: 1 addition & 1 deletion src/odbc/api/sqlcancelhandle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use tracing::info;

#[allow(non_snake_case)]
#[unsafe(no_mangle)]
pub extern "C" fn SQLCancelHandle(_handle_type: i16, _handle: *mut c_void) -> SqlReturn {
pub extern "system" fn SQLCancelHandle(_handle_type: i16, _handle: *mut c_void) -> SqlReturn {
info!("SQLCancelHandle");
SqlReturn::SUCCESS
}
2 changes: 1 addition & 1 deletion src/odbc/api/sqlclosecursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use tracing::info;

#[allow(non_snake_case)]
#[unsafe(no_mangle)]
pub extern "C" fn SQLCloseCursor(_statement_handle: *mut c_void) -> SqlReturn {
pub extern "system" fn SQLCloseCursor(_statement_handle: *mut c_void) -> SqlReturn {
info!("SQLCloseCursor");
SqlReturn::SUCCESS
}
Loading