FreeTDSKit is a Swift wrapper around the FreeTDS DB-Library client, exposing a Swift-native API for connecting to Microsoft SQL Server and executing queries without pushing C types into the public interface.
The package is still evolving, but the current layout is intentionally simple: SwiftPM builds a small C shim, links vendored static libraries, and exposes the result through a Swift actor-based API.
Sources/FreeTDSKit: Swift API surface, includingTDSConnection,SQLResult, and type mapping.Sources/CFreeTDS: C shim plus vendored FreeTDS headers and static libraries.Support/buildandlinkfreetds.sh: rebuilds FreeTDS from source and copies the generated headers andlibsybdb.ainto the package.Support/generate_freetds_pc.sh: generates a Homebrewfreetds.pcfile and symlink for local tooling/Xcode header discovery.Tests/FreeTDSKitTests: fast unit tests.Tests/FreeTDSKitIntegrationTests: Docker-backed SQL Server integration tests.
This package does not expect user to install FreeTDS separately at build time. Instead, the package vendors the important C artifacts inside Sources/CFreeTDS:
- FreeTDS headers under
Sources/CFreeTDS/include libsybdb.alibssl.alibcrypto.a
Package.swift builds CFreeTDS as a C target and passes SwiftPM linker flags pointing at that directory:
.unsafeFlags([
"-L\(Context.packageDirectory)/Sources/CFreeTDS",
"-lsybdb",
"-lssl",
"-lcrypto",
"-liconv",
])That means the package resolves as a self-contained SwiftPM dependency as long as those vendored archives and headers are kept in sync.
First add FreeTDSKit to your application in Xcode or run this:
swift package add https://github.com/oliwonders/FreeTDSKit.gitimport FreeTDSKit
let config = ConnectionConfiguration(
host: "your_server",
port: 1438,
username: "your_user",
password: "your_password",
database: "your_database"
)
let connection = try TDSConnection(configuration: config)
let result = try await connection.execute(queryString: "SELECT id, name FROM users")
print(result.rows)
struct User: Decodable {
let id: Int
let name: String
}
for try await user in connection.query(queryString: "SELECT id, name FROM users", as: User.self) {
print(user)
}
await connection.close()On SQL failures, the thrown error includes the detailed SQL Server message captured by the C wrapper.
The upgrade workflow is based on vendoring a new static FreeTDS build into Sources/CFreeTDS.
Edit Support/buildandlinkfreetds.sh and change:
FREETDS_VERSION="1.5.2"Run:
./Support/buildandlinkfreetds.shWhat that script does:
- Downloads the selected FreeTDS release tarball.
- Configures it with
--disable-shared --enable-static. - Builds and installs it into
Support/build/static-freetds. - Copies generated headers into
Sources/CFreeTDS/include. - Copies
libsybdb.aintoSources/CFreeTDS/.
Important limitation: the script only refreshes FreeTDS itself. This repo also vendors libssl.a and libcrypto.a, so if the new FreeTDS build needs different OpenSSL artifacts you need to refresh those archives separately and keep them compatible with the new libsybdb.a.
The C target is defined in Package.swift, and the bridge module is declared in Sources/CFreeTDS/module.modulemap. After replacing vendored artifacts, run:
swift build
swift testIf the new FreeTDS version changes transitive requirements, update the linker flags in Package.swift accordingly.
The package exposes the linked library version via:
FreeTDSKit.getFreeTDSVersion()The unit test suite already exercises that path.
Most package consumers should not need a system-wide FreeTDS install because the package vendors the native artifacts. The extra linking helper script exists for local development on macOS, especially when Xcode or local tooling needs help finding sybdb.h.
Run:
./Support/generate_freetds_pc.shThat script:
- looks for the latest Homebrew
freetdsinstall under/opt/homebrew/Cellar/freetds - writes a
freetds.pcfile into that keg - creates
/opt/homebrew/lib/pkgconfig/freetds.pc - checks the result with
pkgconf --cflags freetds
Use it when local developer tooling cannot locate FreeTDS headers cleanly. It is not part of the normal SwiftPM consumer flow.
Run the fast unit test suite with:
swift testThese tests live in Tests/FreeTDSKitTests and cover type mapping, result handling, and the linked FreeTDS version surface.
Integration tests exercise real SQL Server connectivity and query behavior through the public API. They live in Tests/FreeTDSKitIntegrationTests and cover:
- connection success and failure cases
- query execution
- streaming queries
Decodablerow mapping- binary data handling
- spatial/geography fields
- insert/update/delete paths
The integration test fixture creates a SQL Server 2022 container and loads db-setup.sql, which provisions:
FreeTDSKitTestDBDataTypeTestUpdateTableTest
By default, swift test and Xcode will discover these tests but skip them unless FREETDSKIT_RUN_INTEGRATION_TESTS=1 is set in the environment.
- Docker Desktop
- Homebrew
sqlcmd
The helper script will attempt to install missing docker and sqlcmd packages via Homebrew.
Run the full setup and integration suite with:
FREETDSKIT_RUN_INTEGRATION_TESTS=1 \
Tests/FreeTDSKitIntegrationTests/run-integration-tests.shThat script:
- Exports default connection settings.
- Verifies
dockerandsqlcmdare installed. - Starts Docker if needed.
- Launches SQL Server with
docker compose. - Waits for the server to accept connections.
- Creates and seeds the test database if it does not already exist.
- Runs the Swift test command with the integration environment enabled.
The integration suite reads these variables:
FREETDSKIT_RUN_INTEGRATION_TESTSset to1to opt inFREETDSKIT_SQL_SERVERdefault:localhostFREETDSKIT_SQL_PORTdefault:1438FREETDSKIT_SQL_USERdefault:saFREETDSKIT_SQL_PASSWORDdefault:YourStrongPassword1FREETDSKIT_SQL_DBdefault:FreeTDSKitTestDB
Example:
FREETDSKIT_RUN_INTEGRATION_TESTS=1 \
FREETDSKIT_SQL_PASSWORD='YourStrongPassword1' \
FREETDSKIT_SQL_PORT=1438 \
Tests/FreeTDSKitIntegrationTests/run-integration-tests.shIf the SQL Server instance is already running and seeded, run:
FREETDSKIT_RUN_INTEGRATION_TESTS=1 \
swift test --disable-swift-testing --enable-xctest \
--filter FreeTDSKitIntegrationTestsThe integration targets are written with XCTest, so the manual command disables Swift Testing and enables XCTest explicitly.
If you are forking this package and want a clean starting point:
- Run
swift test. - Run
FREETDSKIT_RUN_INTEGRATION_TESTS=1 Tests/FreeTDSKitIntegrationTests/run-integration-tests.sh. - If you are upgrading FreeTDS, rebuild and re-vendor the C artifacts first.
- Keep
Sources/CFreeTDS/include,libsybdb.a,libssl.a, andlibcrypto.aaligned. - Verify the package still builds as a standalone SwiftPM dependency before publishing your fork.
See LICENSE.