Skip to content
Merged
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
48 changes: 41 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,28 +86,62 @@ jobs:
- name: Checkout
uses: actions/checkout@v4

- name: Install meson and ninja
run: python -m pip install meson ninja
- name: Install meson, ninja, and pkgconf
run: python -m pip install meson ninja pkgconf

- name: Setup MSVC
uses: TheMrMilchmann/setup-msvc-dev@v3
with:
arch: x64

- name: Remove Strawberry Perl from PATH
shell: powershell
run: |
# Strawberry Perl injects ccache and a broken pkg-config into PATH
$cleanPath = ($env:PATH -split ';' | Where-Object { $_ -notlike '*Strawberry*' }) -join ';'
echo "PATH=$cleanPath" | Out-File -FilePath $env:GITHUB_ENV -Append

- name: Install PostgreSQL
shell: powershell
run: |
choco install postgresql17 --params "/Password:postgres /Port:5432" -y
# Add PostgreSQL bin to PATH for this job
# Add PostgreSQL bin to PATH for DLL loading at runtime
echo "C:\Program Files\PostgreSQL\17\bin" | Out-File -FilePath $env:GITHUB_PATH -Append

- name: Fix libpq.pc prefix
shell: powershell
run: |
# The choco-installed PostgreSQL ships a libpq.pc with the build machine's
# prefix path baked in. Rewrite it to point to the actual install location.
$pgDir = "C:/Program Files/PostgreSQL/17"
$pcDir = "$pgDir/lib/pkgconfig"
if (Test-Path "$pcDir/libpq.pc") {
@"
prefix=$pgDir
includedir=`${prefix}/include
libdir=`${prefix}/lib

Name: libpq
Description: PostgreSQL libpq library
Version: 17
Libs: -L`${libdir} -lpq
Cflags: -I`${includedir}
"@ | Set-Content "$pcDir/libpq.pc"
}

- name: Configure
shell: powershell
run: |
$env:PKG_CONFIG_PATH = "C:\Program Files\PostgreSQL\17\lib\pkgconfig"
meson setup builddir -Ddriver_manager=none
env:
PKG_CONFIG_PATH: C:\Program Files\PostgreSQL\17\lib\pkgconfig
# Create native file to use pkgconf-pypi (avoids broken pkg-config)
@"
[binaries]
pkg-config = 'pkgconf-pypi'

[built-in options]
pkg_config_path = 'C:/Program Files/PostgreSQL/17/lib/pkgconfig'
"@ | Set-Content native.ini

meson setup builddir -Ddriver_manager=none --native-file=native.ini

- name: Build
run: meson compile -C builddir
Expand Down
74 changes: 58 additions & 16 deletions src/dsn_config.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
#include <string.h>
#include <stdlib.h>

#if defined(_WIN32) || defined(_WIN64)
#include <windows.h>
#endif

#ifdef HAVE_ODBCINST
#include <odbcinst.h>

Expand All @@ -23,22 +27,38 @@
#define INI_VALUE_BUFFER_SIZE 512

/*
* Read a single string value from the DSN section of odbc.ini.
* Read a single string value from the DSN section of the specified INI file.
* Returns the number of characters written to the buffer (excluding null
* terminator), or 0 if the key does not exist or has an empty value.
*/
static int read_ini_string(const char *dsn_name, const char *key,
char *buffer, int buffer_size)
static int read_ini_string_from(const char *dsn_name, const char *key,
char *buffer, int buffer_size,
const char *ini_file)
{
/* SQLGetPrivateProfileString returns the number of characters written.
* If the DSN section or key doesn't exist, it writes the default value
* (empty string here) and returns 0. */
* (empty string here) and returns 0.
*
* On Windows, SQLGetPrivateProfileString always reads from the registry
* regardless of the filename argument. For file-based reads (used in
* testing), we use GetPrivateProfileStringA directly. */
#if defined(_WIN32) || defined(_WIN64)
if (ini_file && strcmp(ini_file, ODBC_INI_FILE) != 0) {
/* File-based read — use Win32 API directly */
return (int)GetPrivateProfileStringA(
dsn_name, key, "", buffer, (DWORD)buffer_size, ini_file);
}
#endif
int chars_written = SQLGetPrivateProfileString(
dsn_name, key, "", buffer, buffer_size, ODBC_INI_FILE);
dsn_name, key, "", buffer, buffer_size, ini_file);
return chars_written;
}

bool dsn_config_read(const char *dsn_name, ConnectionInfo *out_info)
/*
* Internal implementation shared by dsn_config_read and dsn_config_read_file.
*/
static bool dsn_config_read_impl(const char *dsn_name, ConnectionInfo *out_info,
const char *ini_file)
{
if (!dsn_name || dsn_name[0] == '\0' || !out_info) {
return false;
Expand All @@ -49,44 +69,44 @@ bool dsn_config_read(const char *dsn_name, ConnectionInfo *out_info)

/* Read server name — try "Servername" first (psqlodbc convention),
* then fall back to "Server" (common alias) */
if (read_ini_string(dsn_name, DSN_KEY_SERVERNAME, buffer, sizeof(buffer)) > 0) {
if (read_ini_string_from(dsn_name, DSN_KEY_SERVERNAME, buffer, sizeof(buffer), ini_file) > 0) {
strncpy(out_info->server, buffer, sizeof(out_info->server) - 1);
out_info->server[sizeof(out_info->server) - 1] = '\0';
found_any_value = true;
} else if (read_ini_string(dsn_name, DSN_KEY_SERVER, buffer, sizeof(buffer)) > 0) {
} else if (read_ini_string_from(dsn_name, DSN_KEY_SERVER, buffer, sizeof(buffer), ini_file) > 0) {
strncpy(out_info->server, buffer, sizeof(out_info->server) - 1);
out_info->server[sizeof(out_info->server) - 1] = '\0';
found_any_value = true;
}

/* Read port */
if (read_ini_string(dsn_name, DSN_KEY_PORT, buffer, sizeof(buffer)) > 0) {
if (read_ini_string_from(dsn_name, DSN_KEY_PORT, buffer, sizeof(buffer), ini_file) > 0) {
strncpy(out_info->port, buffer, sizeof(out_info->port) - 1);
out_info->port[sizeof(out_info->port) - 1] = '\0';
found_any_value = true;
}

/* Read database name */
if (read_ini_string(dsn_name, DSN_KEY_DATABASE, buffer, sizeof(buffer)) > 0) {
if (read_ini_string_from(dsn_name, DSN_KEY_DATABASE, buffer, sizeof(buffer), ini_file) > 0) {
strncpy(out_info->database, buffer, sizeof(out_info->database) - 1);
out_info->database[sizeof(out_info->database) - 1] = '\0';
found_any_value = true;
}

/* Read username — try "Username" first, then "UID" as alias */
if (read_ini_string(dsn_name, DSN_KEY_USERNAME, buffer, sizeof(buffer)) > 0) {
if (read_ini_string_from(dsn_name, DSN_KEY_USERNAME, buffer, sizeof(buffer), ini_file) > 0) {
strncpy(out_info->username, buffer, sizeof(out_info->username) - 1);
out_info->username[sizeof(out_info->username) - 1] = '\0';
found_any_value = true;
} else if (read_ini_string(dsn_name, DSN_KEY_UID, buffer, sizeof(buffer)) > 0) {
} else if (read_ini_string_from(dsn_name, DSN_KEY_UID, buffer, sizeof(buffer), ini_file) > 0) {
strncpy(out_info->username, buffer, sizeof(out_info->username) - 1);
out_info->username[sizeof(out_info->username) - 1] = '\0';
found_any_value = true;
}

/* Read password — heap-allocated for secure clearing.
* Only overwrite if the INI value is non-empty. */
if (read_ini_string(dsn_name, DSN_KEY_PASSWORD, buffer, sizeof(buffer)) > 0) {
if (read_ini_string_from(dsn_name, DSN_KEY_PASSWORD, buffer, sizeof(buffer), ini_file) > 0) {
size_t password_length = strlen(buffer);
char *new_password = malloc(password_length + 1);
if (new_password) {
Expand All @@ -101,21 +121,21 @@ bool dsn_config_read(const char *dsn_name, ConnectionInfo *out_info)
}

/* Read SSL mode */
if (read_ini_string(dsn_name, DSN_KEY_SSLMODE, buffer, sizeof(buffer)) > 0) {
if (read_ini_string_from(dsn_name, DSN_KEY_SSLMODE, buffer, sizeof(buffer), ini_file) > 0) {
strncpy(out_info->sslmode, buffer, sizeof(out_info->sslmode) - 1);
out_info->sslmode[sizeof(out_info->sslmode) - 1] = '\0';
found_any_value = true;
}

/* Read application name */
if (read_ini_string(dsn_name, DSN_KEY_APP_NAME, buffer, sizeof(buffer)) > 0) {
if (read_ini_string_from(dsn_name, DSN_KEY_APP_NAME, buffer, sizeof(buffer), ini_file) > 0) {
strncpy(out_info->application_name, buffer, sizeof(out_info->application_name) - 1);
out_info->application_name[sizeof(out_info->application_name) - 1] = '\0';
found_any_value = true;
}

/* Read connection timeout (stored as a decimal integer string) */
if (read_ini_string(dsn_name, DSN_KEY_TIMEOUT, buffer, sizeof(buffer)) > 0) {
if (read_ini_string_from(dsn_name, DSN_KEY_TIMEOUT, buffer, sizeof(buffer), ini_file) > 0) {
unsigned long timeout_value = strtoul(buffer, NULL, 10);
out_info->connect_timeout = (unsigned int)timeout_value;
found_any_value = true;
Expand All @@ -124,6 +144,19 @@ bool dsn_config_read(const char *dsn_name, ConnectionInfo *out_info)
return found_any_value;
}

bool dsn_config_read(const char *dsn_name, ConnectionInfo *out_info)
{
return dsn_config_read_impl(dsn_name, out_info, ODBC_INI_FILE);
}

bool dsn_config_read_file(const char *dsn_name, ConnectionInfo *out_info,
const char *ini_file)
{
if (!ini_file || ini_file[0] == '\0')
return dsn_config_read(dsn_name, out_info);
return dsn_config_read_impl(dsn_name, out_info, ini_file);
}

#else /* !HAVE_ODBCINST */

/* DSN lookup is not available without libodbcinst.
Expand All @@ -136,4 +169,13 @@ bool dsn_config_read(const char *dsn_name, ConnectionInfo *out_info)
return false;
}

bool dsn_config_read_file(const char *dsn_name, ConnectionInfo *out_info,
const char *ini_file)
{
(void)dsn_name;
(void)out_info;
(void)ini_file;
return false;
}

#endif /* HAVE_ODBCINST */
14 changes: 14 additions & 0 deletions src/dsn_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,18 @@
*/
bool dsn_config_read(const char *dsn_name, ConnectionInfo *out_info);

/*
* Read connection parameters for the named DSN from a specific INI file.
*
* Same behavior as dsn_config_read, but reads from the specified file path
* instead of the system ODBC configuration. On Windows, a full path causes
* SQLGetPrivateProfileString to read from the file rather than the registry.
* On Unix (unixODBC), the filename is passed directly.
*
* This is primarily useful for testing without modifying the system ODBC
* configuration.
*/
bool dsn_config_read_file(const char *dsn_name, ConnectionInfo *out_info,
const char *ini_file);

#endif /* PSQLODBC2_DSN_CONFIG_H */
5 changes: 4 additions & 1 deletion tests/test_catalog.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@
*-------------------------------------------------------------------------
*/
#include <assert.h>
#include <dlfcn.h>
#include "test_platform.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
#endif
#include <sql.h>
#include <sqlext.h>

Expand Down
5 changes: 4 additions & 1 deletion tests/test_column_binding.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@
*-------------------------------------------------------------------------
*/
#include <assert.h>
#include <dlfcn.h>
#include "test_platform.h"
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
#endif
#include <sql.h>
#include <sqlext.h>

Expand Down
5 changes: 4 additions & 1 deletion tests/test_connection_attributes.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
#endif
#include <sql.h>
#include <sqlext.h>
#include <sqltypes.h>
Expand All @@ -28,7 +31,7 @@
#define LIB_HANDLE HMODULE
#define LIB_ERROR_MSG() "LoadLibrary failed"
#else
#include <dlfcn.h>
#include "test_platform.h"
#define LOAD_LIBRARY(path) dlopen(path, RTLD_NOW)
#define GET_SYMBOL(lib, name) dlsym(lib, name)
#define CLOSE_LIBRARY(lib) dlclose(lib)
Expand Down
5 changes: 4 additions & 1 deletion tests/test_connection_lifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
#endif
#include <sql.h>
#include <sqltypes.h>
#include <sqlext.h>
Expand All @@ -27,7 +30,7 @@
#define LIB_HANDLE HMODULE
#define LIB_ERROR_MSG() "LoadLibrary failed"
#else
#include <dlfcn.h>
#include "test_platform.h"
#define LOAD_LIBRARY(path) dlopen(path, RTLD_NOW)
#define GET_SYMBOL(lib, name) dlsym(lib, name)
#define CLOSE_LIBRARY(lib) dlclose(lib)
Expand Down
5 changes: 4 additions & 1 deletion tests/test_driver_load.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
*/
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
#endif
#include <sql.h>
#include <sqltypes.h>

Expand All @@ -25,7 +28,7 @@
#define LIB_HANDLE HMODULE
#define LIB_ERROR_MSG() "LoadLibrary failed"
#else
#include <dlfcn.h>
#include "test_platform.h"
#define LOAD_LIBRARY(path) dlopen(path, RTLD_NOW)
#define GET_SYMBOL(lib, name) dlsym(lib, name)
#define CLOSE_LIBRARY(lib) dlclose(lib)
Expand Down
Loading
Loading