Introduce a new `SrvHost` connection string property that enables a single
DNS name to represent an entire high-availability PostgreSQL cluster. When
set, Npgsql resolves `_postgresql._tcp.<SrvHost>` SRV records at
`NpgsqlDataSource` build time and uses the returned host/port pairs as the
multi-host list, sorted by priority (ascending) then weight (descending) per
RFC 2782.
### Changes
**`NpgsqlConnectionStringBuilder`**
- New `SrvHost` string property, exposed as the `SrvHost` keyword in
connection strings (e.g. `SrvHost=cluster.example.com`).
- `PostProcessAndValidate` updated to allow a null/empty `Host` when `SrvHost`
is set, and to enforce mutual exclusivity: supplying both throws
`ArgumentException`.
**`SrvLookup.cs`** (new)
- Static helper class that queries `_postgresql._tcp.<srvHost>` via the
`DnsClient` NuGet package, sorts SRV records by priority / weight, strips
trailing FQDN dots, and returns a comma-separated `host:port,host:port,...`
string ready for use as `NpgsqlConnectionStringBuilder.Host`.
- `SortAndBuild(IEnumerable<SrvRecord>)` is `internal` so unit tests can
exercise sorting logic without a live DNS server.
**`NpgsqlSlimDataSourceBuilder` / `NpgsqlDataSourceBuilder`**
- New `SrvLookupClient` property (`DnsClient.ILookupClient?`). When `null`
(the default), the OS resolver is used. Inject a custom client in tests to
return deterministic SRV records without hitting DNS.
- `Build()` and `BuildMultiHost()` call `ResolveSrvIfNeeded()` before
`PostProcessAndValidate()`, expanding SRV results into the `Host` property
so the existing multi-host path handles all subsequent connection logic.
**Dependency**
- `DnsClient 1.8.0` added to `Directory.Packages.props` and `Npgsql.csproj`.
### New test project — `Npgsql.SrvTests`
Isolated from the main `Npgsql.Tests` assembly (which requires a live
PostgreSQL server) so SRV unit tests can run on any machine without a
database.
Unit tests cover:
- Connection-string roundtrip and keyword parsing.
- `SrvHost` / `Host` mutual exclusivity.
- RFC 2782 sort order: priority ascending, weight descending.
- Trailing-dot stripping from FQDNs returned by DnsClient.
- Priority/weight ordering mirroring the real records at `mmatvei.ru`.
- Empty result set throws `NpgsqlException`.
`ResolveSrvLive` performs an end-to-end DNS lookup against
`_postgresql._tcp.mmatvei.ru` (four real SRV records, priorities 96–100)
and verifies ordering. The test skips automatically if the records are
unreachable. Set `NPGSQL_TEST_SRV_DNS=<ip>` to force a specific nameserver
(useful when the system resolver has a stale negative cache).
### Usage
```csharp
// Connection string keyword
var ds = NpgsqlDataSource.Create(
"SrvHost=cluster.example.com;Database=app;Username=app_user");
// Builder API
var builder = new NpgsqlDataSourceBuilder();
builder.ConnectionStringBuilder.SrvHost = "cluster.example.com";
builder.ConnectionStringBuilder.Database = "app";
var ds = builder.Build();
```
### Connection string format
```
SrvHost=cluster.example.com;Database=mydb;Username=myuser;Password=...
```
### Notes
- SRV resolution happens once at `Build()` time. Re-build the data source to
re-query DNS (matches how `NpgsqlMultiHostDataSource` works today).
- `TargetSessionAttributes` (e.g. `read-write`, `primary`, `standby`) work
unchanged with the resolved host list.
- `SrvHost` and `Host` are mutually exclusive; mixing them throws
`ArgumentException` at build time.
Made-with: Cursor