mssql_driver_with_native_tls
A Dart-first driver for Microsoft SQL Server, built on the TDS 7.4 wire protocol. SQL Server protocol handling, authentication, queries, pooling, transactions, type encoding/decoding, Bulk Load, and related functionality are implemented in Dart. Native code is used only for the TLS transport.
The native OpenSSL helper is currently required for TLS on Windows, Linux, Android, macOS, and iOS. This is the compatibility trade-off for reliable encrypted multi-packet requests, Bulk Load, and Attention cancellation on a standard Dart SDK. Unencrypted connections continue to use only Dart and TCP.
dart pub add mssql_driver_with_native_tls
Cleartext (encrypt: false) needs nothing else. For TLS (encrypt: true),
hook/build.dart automatically downloads the prebuilt OpenSSL helper from the
native GitHub Release pinned by this package. The native Release tag may be
older than the Dart package version when the native code has not changed. No
manual copy or local CMake/OpenSSL installation is required on supported
targets; see Native TLS helper for the platform matrix.
Override the helper path with MSSQL_TLS_LIBRARY when needed. For offline
builds, local helpers, testing, and releasing, see DEV_README.md.
Security of downloaded native assets is protected by in-repo SHA-256 pins
(lib/src/native_tls/native_tls_pins.dart), each release zip’s SHA256SUMS,
and GitHub artifact attestations on every release binary; the build hook
refuses mismatched helpers.
Choosing a SQL Server package
There are several Dart and Flutter packages for connecting to Microsoft SQL Server. They use different architectures and target different use cases.
mssql_driver_with_native_tls is a TDS driver implemented primarily in Dart. SQL Server protocol handling, authentication, queries, pooling, transactions, type encoding/decoding, Bulk Load, and related functionality are implemented by the package itself. Native code is used only for the TLS transport.
Architecture and platform comparison
| Package | Architecture | SQL Server protocol | Native dependency | Platforms |
|---|---|---|---|---|
mssql_driver_with_native_tls |
Dart TDS driver + native TLS | Implemented in Dart | Small OpenSSL TLS helper | Windows, Linux, Android, macOS, iOS |
mssql |
Pure-Dart TDS 7.4/8.0 driver | Implemented in Dart | None; encrypted TDS uses 512-byte packet alignment and padding | Windows, Linux, macOS, Android, iOS |
mssql experimental fork (v0.6.x) |
Pure-Dart TDS 7.4 driver | Implemented in Dart | None; encrypted connections require a patched Dart SDK | Platforms supported by the patched Dart runtime |
mssql_native |
Flutter/Dart FFI around FreeTDS | FreeTDS | FreeTDS (bundled) | Windows, Linux, macOS, Android, iOS |
mssql_connection |
Flutter/Dart FFI wrapper around FreeTDS | FreeTDS | FreeTDS | Windows, Linux, macOS, Android, iOS |
mssql_io |
Flutter plugin around FreeTDS | FreeTDS | FreeTDS | Windows, Linux, macOS, Android, iOS; Web through a backend proxy |
mssql_connect |
Flutter SQL Server plugin | Platform implementation | Platform-specific | Android, Windows |
The main architectural difference is that mssql_driver_with_native_tls is not a Dart wrapper around FreeTDS. It implements the TDS protocol directly in Dart and uses a small native OpenSSL component only where the current Dart TLS API cannot provide the required encrypted-write semantics.
Quick start
import 'package:mssql_driver_with_native_tls/mssql_driver_with_native_tls.dart';
final conn = await MssqlConnection.connect(
host: 'localhost',
port: 1433,
user: 'sa',
password: 'P@ssw0rd',
database: 'MyDb',
);
final result = await conn.query('SELECT id, name FROM users WHERE id = @id', {'id': 1});
print(result[0]['name']); // Alice
await conn.close();
LAN / on-prem cookbook
Defaults favor local SQL Server / Docker Edge as well as production. Common LAN patterns:
Connection strings
final conn = await MssqlConnection.connectFromString(
'Server=10.0.0.5,1433;Database=app;User Id=sa;Password=…;'
'Encrypt=false;TrustServerCertificate=true;App Name=my-pos;',
);
// Named instance (SQL Browser UDP 1434 when port omitted):
// Server=sql01\SQLEXPRESS;User Id=sa;Password=…;Encrypt=false;
// URL form: sqlserver://sa:…@10.0.0.5:1433?database=app&encrypt=false
User Id=DOMAIN\user opens NTLM. Pool: MssqlPoolConfig.fromConnectionString(…, max: 10).
Timeouts & identity
final conn = await MssqlConnection.connect(
host: '10.0.0.5',
user: 'sa',
password: '…',
database: 'app',
encrypt: false, // or true + trustServerCertificate for LAN TLS
appName: 'my-pos', // sys.dm_exec_sessions.program_name
timeout: Duration(seconds: 10), // full login handshake
queryTimeout: Duration(seconds: 30), // Attention + drain on expiry
);
await conn.query('SELECT 1', const {}, Duration(seconds: 2)); // per-call
Protocol limits
Protocol size limits are enabled by default to avoid allocating unexpectedly
large server-controlled values. Tune them for workloads that intentionally read
large LOB values, use MssqlProtocolLimits.sqlServerMaximums for documented SQL
Server ceilings, or MssqlProtocolLimits.unlimited for the old compatibility
behaviour.
final conn = await MssqlConnection.connect(
host: '10.0.0.5',
user: 'sa',
password: '…',
encrypt: false,
// Limits below are equivalent to the default: const MssqlProtocolLimits().
protocolLimits: const MssqlProtocolLimits(
maximumTokenBytes: 16 * 1024 * 1024,
maximumValueBytes: 64 * 1024 * 1024,
maximumPlpChunkBytes: 4 * 1024 * 1024,
maximumColumns: 4096,
maximumResultSets: 256,
),
);
Pool health & session reset
final pool = MssqlPool(MssqlPoolConfig(
host: '10.0.0.5',
user: 'sa',
password: '…',
database: 'app',
encrypt: false,
validateOnAcquire: true, // default — SELECT 1; discard dead sockets
resetOnRelease: true, // default — TDS RESETCONNECTION (clears #temp / USE)
));
final conn = await pool.acquire();
try {
await conn.execute('…');
} finally {
await pool.release(conn); // async — always await
}
resetOnRelease clears session temp tables and restores the login database. Disable only if you intentionally share #temp across borrowers.
Named instances
await MssqlConnection.connect(
host: r'sql01\SQLEXPRESS', // or host: 'sql01', instanceName: 'SQLEXPRESS'
user: 'sa',
password: '…',
encrypt: false,
);
// Explicit port skips Browser: r'sql01\SQLEXPRESS,15001'
Bulk insert & TVP
await conn.bulkInsert('dbo.Items', ['Id', 'Name'], [
[1, 'a'],
[2, 'b'],
]);
// Destination nullability is detected automatically. To skip that metadata
// query, describe every column explicitly:
await conn.bulkInsert(
'dbo.Items',
['Id', 'Name'],
const [
[3, 'c'],
],
columnTypes: const [
BulkColumn('Id', BulkColumnType.bigInt, nullable: false),
BulkColumn('Name', BulkColumnType.nVarChar, nullable: true),
],
);
// Use an operation handle when Bulk Load must be cancellable. cancel()
// completes after SQL Server acknowledges Attention and the connection is
// reusable; result throws MssqlOperationCancelledException if cancellation wins.
final bulk = conn.startBulkInsert(
'dbo.Items',
['Id', 'Name'],
List.generate(100000, (i) => [i, 'item-$i']),
);
final result = bulk.result;
if (await bulk.transferStarted) {
await bulk.cancel();
}
try {
await result;
} on MssqlOperationCancelledException {
// The Bulk Load statement was cancelled and its response was drained.
}
// Requires: CREATE TYPE dbo.IdList AS TABLE (Id BIGINT);
await conn.query('SELECT Id FROM @ids', {
'ids': MssqlTvp(
typeName: 'dbo.IdList',
columns: [BulkColumn('Id', BulkColumnType.bigInt)],
rows: [[1], [2], [3]],
),
});
NTLM (domain SQL)
await MssqlConnection.connectNtlm(
host: 'sql01',
domain: 'CONTOSO',
user: 'bob',
password: '…',
encrypt: true,
trustServerCertificate: true,
);
Stored procedures (OUTPUT / RETURN)
final r = await conn.call('dbo.MyProc', {
'inVal': 5,
'outVal': MssqlOutput(0), // or MssqlOutput(null, 'nvarchar(100)')
});
print(r.returnStatus); // RETURN integer, if any
print(r.output['outVal']);
print(r.resultSets); // SELECT sets inside the proc
Diagnostics & transient retry
conn.onInfoMessage = (info) {
print('INFO ${info.number}: ${info.message}'); // PRINT / RAISERROR < 11
};
// Pool: connectRetries default 2; optional INFO fan-out
final pool = MssqlPool(MssqlPoolConfig(
host: '10.0.0.5',
user: 'sa',
password: '…',
encrypt: false,
connectRetries: 2,
onInfoMessage: (info) => print(info.message),
));
// App-level retry for deadlocks / brief disconnects
await MssqlTransient.retry(
() => conn.query('UPDATE …'),
retries: 2,
);
Isolation & savepoints
await conn.transaction((c) async {
await c.execute('INSERT …');
await c.savepoint('sp1');
await c.execute('UPDATE …');
await c.rollbackTo('sp1'); // outer txn still open
}, isolation: MssqlIsolationLevel.repeatableRead);
Parameters and SQL Server-specific types
Use ordinary Dart values first. The driver infers the usual SQL Server types:
await conn.query('SELECT @id, @price, @enabled, @name, @created, @data, @none', {
'id': 42, // bigint
'price': 19.99, // float
'enabled': true, // bit
'name': 'Ada', // nvarchar
'created': DateTime.now().toUtc(), // datetime2
'data': [0xDE, 0xAD], // varbinary(max)
'none': null,
});
Use an Mssql* helper when the SQL Server type cannot be represented by a
bare Dart value, or when its exact width, encoding, precision, or legacy type
matters:
await conn.query('SELECT @g, @m, @d, @dec, @v, @day, @tod, @dt, @sd, @x, @b, @n, @c, @bin, @rv', {
'g': MssqlGuid('6F9619FF-8B86-D011-B42D-00C04FC964FF'),
'm': MssqlMoney(1234.56),
'd': MssqlDateTimeOffset(
DateTime.utc(2024, 1, 1, 4, 30),
offset: Duration(hours: 5, minutes: 30),
),
'dec': MssqlDecimal(19.99, precision: 10, scale: 2),
'v': MssqlVarchar('bob'),
'day': MssqlDate(2024, 7, 24),
'tod': MssqlTime(hour: 14, minute: 30, second: 0),
'dt': MssqlDateTime(DateTime.utc(2024, 3, 15, 10, 30)),
'sd': MssqlSmallDateTime(DateTime.utc(2024, 3, 15, 10, 30)),
'x': MssqlXml('<root/>'),
'b': MssqlVarbinary([0xDE, 0xAD], length: 16),
'n': MssqlNVarchar('lan', length: 32),
'c': MssqlNChar('ab', length: 4),
'bin': MssqlBinary([1, 2], length: 4),
'rv': MssqlRowVersion.parse('0x00000000000000FF'),
});
Always On / HA
Connection establishment supports Always On read-only routing, parallel dialing of multi-subnet listeners, and an initial database-mirroring failover partner. This is not transparent failover for an already-running command: an in-flight connection lost during a role change must be retried by the application.
// Read-only routing via AG listener (database required)
final ro = await MssqlConnection.connect(
host: 'ag-listener',
database: 'app',
user: 'sa',
password: '…',
readOnlyIntent: true, // ApplicationIntent=ReadOnly
multiSubnetFailover: true, // parallel-dial multi-subnet listener
);
// Mirroring partner if primary is down at connect time
final conn = await MssqlConnection.connectFromString(
'Server=sql1;Failover Partner=sql2;Database=app;'
'User Id=sa;Password=…;Encrypt=true;TrustServerCertificate=true;',
);
KeepAlive + session init
final conn = await MssqlConnection.connect(
host: '10.0.0.5',
user: 'sa',
password: '…',
encrypt: false,
keepAlive: Duration(seconds: 30), // TCP keepalive (0 = off)
sessionInitSql: 'SET XACT_ABORT ON; SET LOCK_TIMEOUT 5000;',
);
// Same knobs on the pool; SessionInitSQL re-runs after resetOnRelease
final pool = MssqlPool(MssqlPoolConfig(
host: '10.0.0.5',
user: 'sa',
password: '…',
encrypt: false,
keepAlive: Duration(seconds: 30),
sessionInitSql: 'SET XACT_ABORT ON;',
));
API reference
MssqlConnection
Connecting
// SQL Server authentication (username + password)
final conn = await MssqlConnection.connect(
host: 'localhost', // required
port: 1433, // optional, default 1433
user: 'sa', // required
password: 'P@ssw0rd', // required
database: 'MyDb', // optional, default ''
encrypt: true, // optional, default true; set false for local dev containers
trustServerCertificate: false, // optional, accept self-signed certs
timeout: Duration(seconds: 15), // optional, full login handshake (default 15s)
queryTimeout: Duration(seconds: 30), // optional, default query deadline
appName: 'mssql-dart', // optional, program_name in DMVs
);
// Azure AD authentication
final conn = await MssqlConnection.connectAzureAd(
host: 'server.database.windows.net',
azureAdAuth: AzureAdAuth.fromToken(token), // pre-acquired bearer token
database: 'MyDb',
trustServerCertificate: false,
);
Querying
// Returns all rows buffered in a MssqlResult
final result = await conn.query('SELECT id, name FROM users');
// With named parameters (@name syntax)
final result = await conn.query(
'SELECT * FROM orders WHERE customer = @cust AND active = @flag',
{'cust': 'Acme', 'flag': true},
);
// Access by column name or zero-based index
final name = result[0]['name']; // by name
final first = result[0].valueAt(0); // by index
final cols = result[0].columnNames; // ['id', 'name']
final values = result[0].values; // [1, 'Alice']
// Rows and counts
result.rows; // List<MssqlRow>
result.rowsAffected; // int
result.length; // row count
result.isEmpty; // bool
Executing (DML / DDL)
// Returns rows affected
final n = await conn.execute(
'INSERT INTO logs (msg) VALUES (@msg)',
{'msg': 'hello'},
);
print(n); // 1
Multiple result sets
final multi = await conn.queryMultiple('SELECT 1 AS a; SELECT 2 AS b');
final first = multi.first; // MssqlResult for first SELECT
final second = multi.second; // MssqlResult for second SELECT
final all = multi.all; // List<MssqlResult>
Streaming large result sets
await for (final row in conn.queryStream('SELECT * FROM bigTable')) {
process(row);
}
// With parameters
await for (final row in conn.queryStream(
'SELECT * FROM events WHERE date > @since',
{'since': DateTime.now().subtract(Duration(days: 7))},
)) {
print(row['event_type']);
}
Transactions
// Callback form — commits on success, rolls back on any exception
await conn.transaction((c) async {
await c.execute('INSERT INTO accounts (id, balance) VALUES (1, 100)');
await c.execute('INSERT INTO accounts (id, balance) VALUES (2, 200)');
}, isolation: MssqlIsolationLevel.serializable);
// Manual form + savepoints (SQL Server SAVE / ROLLBACK TRANSACTION)
await conn.beginTransaction(isolation: MssqlIsolationLevel.readCommitted);
try {
await conn.execute('UPDATE accounts SET balance = balance - 50 WHERE id = 1');
await conn.savepoint('after_debit');
await conn.execute('UPDATE accounts SET balance = balance + 50 WHERE id = 2');
// undo only the credit:
await conn.rollbackTo('after_debit');
await conn.commitTransaction();
} catch (_) {
await conn.rollbackTransaction();
rethrow;
}
Connection state
conn.isOpen; // bool — false after close() or a fatal error
conn.database; // String — current database (tracks USE / ENVCHANGE)
conn.initialDatabase; // String — database from login
conn.appName; // String — login program_name
await conn.resetSession(); // TDS RESETCONNECTION + SELECT 1
await conn.resetDatabase(); // USE back to initialDatabase
await conn.cancel(); // Attention cancel in-flight query
await conn.close();
MssqlPool
A connection pool with configurable min/max, idle reaping, and acquire timeouts. Mirrors the node-mssql / tarn pool model.
Creating a pool
final pool = MssqlPool(MssqlPoolConfig(
host: 'localhost',
port: 1433,
user: 'sa',
password: 'P@ssw0rd',
database: 'MyDb',
encrypt: true,
trustServerCertificate: false,
min: 2, // minimum idle connections (default 0)
max: 10, // maximum total connections (default 10)
idleTimeout: Duration(seconds: 30), // close idle connections after (default 30s)
acquireTimeout: Duration(seconds: 15), // throw if no connection within (default 15s)
connectionTimeout: Duration(seconds: 15), // full login handshake (default 15s)
validateOnAcquire: true, // probe idle sockets (default true)
resetOnRelease: true, // TDS RESETCONNECTION (default true)
));
// Pre-warm min connections (optional)
await pool.open();
Pool observability
print(pool.stats);
// MssqlPoolStats(total=2 idle=1 inUse=1 pending=0 max=10 created=… …)
print('${pool.size} ${pool.available} ${pool.borrowed} ${pool.pending}');
pool.onEvent = (e) {
// created / destroyed / acquired / released / acquireTimeout /
// validationFailed / resetFailed
log.info('${e.kind} ${e.stats}');
};
Pool query methods
// Same signatures as MssqlConnection
final result = await pool.query('SELECT * FROM users WHERE id = @id', {'id': 1});
final n = await pool.execute('DELETE FROM tmp WHERE expired = 1');
final multi = await pool.queryMultiple('SELECT 1; SELECT 2');
await for (final row in pool.queryStream('SELECT * FROM bigTable')) {
process(row);
}
Pool transactions
await pool.transaction((conn) async {
await conn.execute('INSERT INTO orders ...');
await conn.execute('UPDATE inventory ...');
// commits on return, rolls back on throw
});
Manual acquire / release
final conn = await pool.acquire();
try {
await conn.execute('...');
} finally {
await pool.release(conn);
}
Closing the pool
await pool.close(); // closes idle connections, rejects any pending acquires
MssqlException
All driver and server errors throw MssqlException:
try {
await conn.query('SELECT * FROM nonexistent');
} on MssqlException catch (e) {
print(e.message); // SQL Server error message
print(e.errorCode); // SQL Server error number (e.g. 208 = invalid object name)
print(e.severity); // TDS severity level (nullable int)
print(e.precedingErrors); // List<MssqlException> — earlier errors from the same batch
}
Parameters
Named parameters use @name placeholders. Supported Dart → SQL type mappings:
| Dart type | SQL Server type |
|---|---|
int |
BIGINT |
double |
FLOAT |
bool |
BIT |
String |
NVARCHAR(MAX) or NVARCHAR |
List<int> |
VARBINARY(MAX) |
DateTime |
DATETIME2(7) |
MssqlGuid |
uniqueidentifier |
MssqlMoney / MssqlSmallMoney |
money / smallmoney |
MssqlDateTimeOffset |
datetimeoffset |
MssqlDecimal |
decimal(p,s) / numeric(p,s) |
MssqlVarchar |
varchar (Latin-1; bare String → nvarchar) |
MssqlDate |
date |
MssqlTime |
time(s) |
MssqlDateTime / MssqlSmallDateTime |
datetime / smalldatetime |
MssqlXml |
xml (bare String → nvarchar) |
MssqlVarbinary |
varbinary(n) / varbinary(max) (bare List<int> → max) |
MssqlNVarchar / MssqlNChar |
nvarchar(n|max) / nchar(n) |
MssqlBinary / MssqlRowVersion |
binary(n) / rowversion compare (binary(8)) |
MssqlTvp |
user-defined table type (… READONLY) |
MssqlOutput |
OUTPUT / INPUT-OUTPUT (with call) |
null |
NULL (any type) |
Supported SQL Server types (read)
| Category | Types |
|---|---|
| Integer | TINYINT, SMALLINT, INT, BIGINT, BIT |
| Float | REAL (→ double), FLOAT (→ double) |
| Decimal | DECIMAL, NUMERIC (→ MssqlDecimal) |
| Money | MONEY (→ MssqlMoney), SMALLMONEY (→ MssqlSmallMoney) |
| String | VARCHAR, NVARCHAR, CHAR, NCHAR, TEXT, NTEXT, VARCHAR(MAX), NVARCHAR(MAX) |
| Binary | VARBINARY, BINARY, IMAGE, VARBINARY(MAX) (→ List<int>) |
| Date/Time | DATE, DATETIME, DATETIME2, SMALLDATETIME, TIME, DATETIMEOFFSET (→ DateTime) |
| GUID | UNIQUEIDENTIFIER (→ String in xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx form) |
| XML | XML (→ String) |
| Misc | SQL_VARIANT (→ decoded inner value), UDT (→ raw List<int>) |
| Null | NULL for any type (→ null) |
TLS / Encryption
TDS 7.x wraps the TLS handshake in PRELOGIN packets, then switches to raw TLS for the rest of the session. On Windows, Linux, Android, macOS, and iOS, encrypted connections use the bundled native OpenSSL transport. It serializes TLS reads and writes so multi-packet requests, Bulk Load, and Attention cancellation remain reliable. Cleartext connections continue to use Dart and TCP only.
On Apple platforms, default certificate validation uses the macOS/iOS system
trust store and hostname policy through Security.framework. Explicit
trustedCertificateFile and trustedCertificateDirectory values continue to
use the supplied PEM roots.
// Production (Azure SQL, SQL Server with TLS)
final conn = await MssqlConnection.connect(
host: 'server.database.windows.net',
encrypt: true, // default true
trustServerCertificate: false, // validate cert (default false)
// Optional: custom PEM trust roots
// trustedCertificateFile: 'ca.pem',
// trustedCertificateDirectory: 'certs',
// After Always On redirect to an IP, keep validating against the AG name:
// hostNameInCertificate: 'ag-listener.contoso.local',
...
);
// Local dev container (no TLS)
final conn = await MssqlConnection.connect(
host: 'localhost',
encrypt: false,
...
);
// Local dev container with self-signed cert
final conn = await MssqlConnection.connect(
host: 'localhost',
encrypt: true,
trustServerCertificate: true,
...
);
If the server requires encryption (forceencryption=1) but the client passes
encrypt: false, connect fails immediately with a clear
MssqlException (Server requires encryption…) — it does not advertise
“not supported” and then upgrade (that path caused SQL Error 17828).
Connection-string keys: Encrypt, TrustServerCertificate,
TrustedCertificateFile (CAFile), TrustedCertificateDirectory (CAPath),
and HostNameInCertificate (also on sqlserver:// URLs). The same options
exist on MssqlPoolConfig / MssqlPoolConfig.fromConnectionString.
Requirements
- Dart SDK >= 3.13
- macOS 12+ or iOS 15+ when targeting Apple platforms
- SQL Server 2008 R2 or later (TDS 7.4 / protocol 0x04000074)
- Azure SQL Database / Azure SQL Edge
- Port 1433 (or custom) reachable from the Dart process
- For
encrypt: true: network access on first build sohook/build.dartcan download the platform helper (or a local build — see DEV_README.md)
Native TLS helper
TLS uses a small OpenSSL-backed shared library. Published package versions
download it automatically via the Dart build hook from the pinned native GitHub
Release. Multiple Dart package versions can reuse the same native Release when
the helper is unchanged. Security of those downloads is protected by in-repo
SHA-256 pins and each zip’s SHA256SUMS.
| Platform | Library | Release asset |
|---|---|---|
| Windows x64 | mssql_tls.dll |
mssql-tls-windows-x64.zip |
| Linux x64 | libmssql_tls.so |
mssql-tls-linux-x64.zip |
| Android | libmssql_tls.so |
mssql-tls-android.zip (per ABI) |
| macOS arm64 / x64 | libmssql_tls.dylib |
mssql-tls-macos.zip |
| iOS device arm64; Simulator arm64 / x64 | libmssql_tls.dylib |
mssql-tls-ios.zip |
Flutter bundles Apple code assets into the application and signs them with the
app. An iOS app connecting to a SQL Server on the local network must also
declare an appropriate NSLocalNetworkUsageDescription in its Info.plist.
Local builds, CI artifacts, testing, and releasing: DEV_README.md.
Limitations
- Tested with SQL Server 2017, 2019, 2022, and 2025. Earlier versions from SQL Server 2012 onward should be protocol-compatible through TDS 7.4 but are not currently tested.
- TLS on Windows, Linux, Android, macOS, and iOS requires the native OpenSSL helper. A pure-Dart TLS fallback is not provided in 0.5.0; use 0.4.1 only when its encrypted multi-packet and Bulk Load limitations are acceptable.
- Azure AD authentication requires a bearer token supplied by the caller (e.g. obtained via
azure_identity); the driver does not fetch tokens itself. - Prepared statement handles (
sp_prepare/sp_execute) are not supported. All parameterized queries usesp_executesql, which SQL Server plan-caches by query hash, so repeated-query performance is similar in practice.
Libraries
- mssql_driver_with_native_tls
- Dart TDS 7.4 driver for Microsoft SQL Server with native OpenSSL TLS.