Non-blocking, event-driven Swift client for Microsoft SQL Server built on SwiftNIO.
TDSNIO is a client implementation of the Tabular Data Stream (TDS) protocol, with APIs for connecting to, authenticating with, querying, and pooling connections to SQL Server.
TDSNIO is currently pre-release software and public APIs may change substantially before a stable release.
- A
TDSConnectionfor connecting to SQL Server, running SQL batches, and reading results. - Async/await APIs for queries, row streams, and connection pooling.
- Swift string interpolation for parameterized SQL through
sp_executesql. - Automatic conversions between common Swift/Foundation types and SQL Server values.
- Integrated logging with swift-log.
- Support for TLS negotiation using swift-nio-ssl.
- Support for
Network.frameworktransports when available on Apple platforms. - A
TDSClientconnection pool backed by PostgresNIO's_ConnectionPoolModule.
TDSNIO targets TDS 7.4 and TDS 8.0 capable SQL Server deployments.
| Version | Tested |
|---|---|
| SQL Server 2019 | Not yet part of CI |
| SQL Server 2022 | Yes, in GitHub Actions |
| Azure SQL Database | Not yet part of CI |
The current CI suite runs integration tests against
mcr.microsoft.com/mssql/server:2022-latest.
TDSNIO currently requires Swift 6.1 or newer.
dependencies: [
.package(url: "https://github.com/aaronjedwards/tds-nio.git", exact: "0.1.0-alpha.1"),
]Add TDSNIO to the target that uses it:
targets: [
.target(name: "MyTarget", dependencies: [
.product(name: "TDSNIO", package: "tds-nio"),
])
]Create a TDSConnection.Configuration with the SQL Server host, credentials,
and optional initial database:
import Logging
import TDSNIO
let configuration = TDSConnection.Configuration(
host: "127.0.0.1",
port: 1433,
username: "sa",
password: "your-strong-password",
database: "app_database"
)
let logger = Logger(label: "tds-nio")
let connection = try await TDSConnection.connect(
configuration: configuration,
id: 1,
logger: logger
)
try await connection.close()You can also build the same configuration from a SQL Server-style connection string:
let configuration = try TDSConnection.Configuration(
connectionString: "Server=127.0.0.1,1433;User Id=sa;Password=your-strong-password;Database=app_database"
)All connections can use TDSConnection.Configuration.TLS. Use .prefer(_:)
or .require(_:) with a NIOSSLContext when TLS should be negotiated:
import NIOSSL
import TDSNIO
let sslContext = try NIOSSLContext(configuration: .makeClientConfiguration())
let configuration = TDSConnection.Configuration(
host: "sql.example.com",
username: "sa",
password: "your-strong-password",
database: "app_database",
tls: .require(sslContext)
)Use query(_:) when you want rows from the first result set:
let rows = try await connection.query("""
SELECT id, username, created_at
FROM users
ORDER BY id
""")
for try await row in rows {
let id = try row.decode(column: "id", as: Int.self)
let username = try row.decode(column: "username", as: String.self)
let createdAt = try row.decode(column: "created_at", as: Date.self)
print(id, username, createdAt)
}Rows can also be decoded positionally:
let rows = try await connection.query("SELECT id, username FROM users")
for try await (id, username) in rows.decode((Int, String).self) {
print(id, username)
}Use execute(_:) when you need the full TDSQueryResult, including rows
affected, output parameters, return status, or additional result-set metadata:
let result = try await connection.execute("""
UPDATE users
SET is_active = 1
WHERE last_login_at IS NOT NULL
""")
print(result.rowsAffected ?? 0)TDSQuery supports Swift string interpolation for bind parameters. Interpolated
values are sent as parameters through sp_executesql, rather than being pasted
into the SQL string:
let id = 42
let username = "fancyuser"
try await connection.execute("""
INSERT INTO users (id, username)
VALUES (\(id), \(username))
""")Optional values can be interpolated as well:
let lastLoginAt: Date? = nil
try await connection.execute("""
UPDATE users
SET last_login_at = \(lastLoginAt)
WHERE id = \(id)
""")The following common Swift and Foundation types can be bound into queries and decoded from rows:
BoolUInt8,Int16,Int32,Int,Int64Float,Double,DecimalString[UInt8],ByteBuffer,DataUUID,TDSGUIDDate,TDSDate,TDSTime,TDSDateTime,TDSDateTimeOffsetTDSJSON,TDSJSONValueTDSTableValuedParameter
For applications that need to reuse connections, create a TDSClient from a
connection configuration and run it in a long-lived task:
import Logging
import TDSNIO
var options = TDSClient.Options()
options.maximumConnections = 10
let client = TDSClient(
configuration: configuration,
options: options,
backgroundLogger: Logger(label: "tds-nio.pool")
)
Task {
await client.run()
}
try await client.withConnection { connection in
let rows = try await connection.query("SELECT id, username FROM users")
for try await (id, username) in rows.decode((Int, String).self) {
print(id, username)
}
}withConnection(_:) leases a connection for the closure's lifetime and returns
it to the pool afterward. The connection is marked for reset before its next
request so pooled sessions do not accidentally share session state between
callers.
Pre-release changes will be documented on the GitHub releases page once tags exist.
Copyright (c) 2026 Aaron Edwards and TDSNIO project authors.
This project contains code and design work influenced by other open source projects. See NOTICE.txt for attribution details.
Microsoft SQL Server is a trademark of Microsoft Corporation. Any use of the trademark is for identification only and does not imply affiliation with or endorsement by Microsoft.
Swift is a trademark of Apple Inc. Any use of the trademark is for identification only and does not imply affiliation with or endorsement by Apple.