Skip to content

What silently degrades on a managed SQL Server database

The one managed host the code recognises by name switches certificate validation on, and the same move narrows the monitoring and maintenance surface.

The Connections card reads N/A. The cache-hit ratio says “Not measured”. The Queries panel is an empty list with a sentence about a permission. Nothing is broken: the host name ended in .database.windows.net, and on Azure SQL Database restricted DMVs narrow monitoring in the same step that the transport starts validating the certificate it is handed. Two changes off one host suffix, and they are worth reading together.

The host suffix that changes the TLS behaviour

buildConfig() in src/lib/db/providers/sql/mssql.ts sets encrypt: true for every SQL Server connection, and then decides certificate validation from the host name alone:

const isAzure = host.endsWith('.database.windows.net');
config.options.trustServerCertificate = !isAzure;

That is the whole detection. There is no vendor field, no probe, no account lookup - a host that ends in that suffix validates the certificate, and a host that does not, does not. An explicit connection.ssl overrides it:

connection.ssl.mode encrypt trustServerCertificate
unset true false for Azure, true otherwise
disable false -
require true true (encrypt, skip validation)
verify-system / verify-ca / verify-full true false (validate)

The three verifying modes build one identical tedious call. Tedious exposes a single knob and this provider never reads connection.ssl.caCert, so verify-ca and verify-full do not deliver the CA pinning their names promise. They validate the chain and the name against the host’s own trust store, which is what verify-system already means.

Why validation is on there and off elsewhere

Because the default that is right for a managed host is wrong for a laptop. The provider document gives the reason for the non-Azure branch in one clause: it trusts a self-signed certificate so that on-prem dev servers connect without a CA. A containerised SQL Server presents the certificate the engine generated at first start, and validating that one fails. Measured on 2026-08-25 against mcr.microsoft.com/mssql/server:2022-latest: { encrypt: true, trustServerCertificate: true } connected and reported sys.dm_exec_connections.encrypt_option = TRUE, while { encrypt: true, trustServerCertificate: false } was refused with “Failed to connect to 127.0.0.1:1433 - self signed certificate”.

Out of the box, against a non-Azure host with no explicit ssl setting, the connection is encrypted but not authenticated. It is protected against a passive reader on the wire and not against a machine in the middle of it. If that server has a certificate you trust, set the SSL mode to verify-system and the default stops applying.

The paste box carries the same fact. An ADO.NET string with Encrypt=True and no TrustServerCertificate keyword maps to verify-full, faithful to Microsoft.Data.SqlClient 4.0 and later - which means a string that used to connect to a self-signed on-prem server can now be refused. Paste TrustServerCertificate=True beside it, or set the mode to require on the form after pasting.

The restricted DMVs, and what Azure SQL Database monitoring loses

Monitoring on this provider is DMV monitoring. getMonitoringData() fans out reads over sys.dm_exec_sessions, sys.dm_os_performance_counters, sys.dm_exec_query_stats joined to sys.dm_exec_sql_text, sys.dm_exec_requests, sys.dm_db_index_usage_stats, and sys.database_files. Every one of those sub-queries is independently privilege-guarded, because every one of them can be refused on its own.

Server-scoped DMVs need VIEW SERVER STATE (VIEW SERVER PERFORMANCE STATE on SQL Server 2022 and later, which VIEW SERVER STATE implies). Microsoft’s reference for sys.dm_exec_sessions states the managed case separately: on Azure SQL Database the view requires VIEW DATABASE STATE, which cannot be granted in master. That is the mechanism behind the empty panel. It is a permission model, not an outage, and it is the same shape a least-privilege login gets on a machine you own.

Maintenance narrows in the same direction. The toolkit here is analyze / check / optimize / kill, labelled Update Statistics, Check Database, Rebuild Indexes and Kill Session. Check Database issues DBCC CHECKDB WITH NO_INFOMSGS, which takes no object, so it is offered as a whole-database card and never as a per-table control - POST /api/db/maintenance answers 400 to {type:"check", target:"Orders"} rather than quietly checking the database while naming one table. The whole toolkit, and the audit trail, are admin-only.

On the managed service some server-scoped dynamic management views and the database check operation behave differently or are restricted, so parts of monitoring and maintenance degrade to N/A or empty. The other managed variants have not been reachable and are untested.

What survives is worth naming too, because it is not nothing. Where the grants exist, blocked-session detection is real here (blocking_session_id > 0 from sys.dm_exec_sessions joined to sys.dm_exec_requests) and index scan counts are real usage data from sys.dm_db_index_usage_stats, seeks plus scans plus lookups. Both readings sit under the limit the feature list already states, that what each panel can show is bounded by what the engine reports. On this engine that reads as the widest DMV surface of any engine in Studio, and a dashboard a single permission can still empty.

How the panels report that restriction

The rule this codebase applies is that an absence must not be published as a measurement. A refused read is never rounded down to zero.

Reading On a refusal On a genuine zero
Cache-hit ratio getHealth().cacheHitRatio is "N/A", getPerformanceMetrics() omits it, the tab renders “Not measured” kept, shown as 0.0%
Health connection count the key is absent from the object and from the POST /api/db/health body reported as 0
Monitoring Connections card N/A over “not published”, and the sample is dropped from the trend chart drawn as a point
Slow queries, sessions, table and index stats [] [] with the real answer, which reads the same

The Connections card is the one that earned this design. The count was once initialised to 0 and the guard left that 0 standing, so a busy server reported itself idle on the strength of a permission error, and every refresh added a real zero point to the sparkline. The cache-hit ratio had the matching bug in the other direction: an unreadable ratio was published as 0%, and the Overview card rates a low ratio “Needs tuning”, so a least-privilege login saw a cache fault SQL Server had never reported.

getPerformanceMetrics() reports only the buffer-cache hit ratio - no queries-per-second, no deadlocks, no buffer-pool usage - and omits even that when sys.dm_os_performance_counters cannot be read. The Queries panel’s empty state names the cause: query stats come from sys.dm_exec_query_stats, which needs the VIEW SERVER STATE permission. The grant is the part a DBA can act on, which is why the panel names it.

Managed variants that have not been reached at all

Azure SQL Database is the only managed offering this code recognises by name, and recognising a host suffix is not the same as having measured the service. The restriction above is Microsoft’s documented requirement for those views, read against what this provider does with a refused read; the live figures in this post were measured on SQL Server 2022 CU26 and on the 2022-latest container image, not on a managed instance. Azure SQL Managed Instance, Microsoft Fabric and Azure Synapse sit on the provider README’s list of managed services no instance was reachable for, tracked in libredb-studio issue #424. They have no row, no tier and no capability claim.

Nothing above is stated about them. Which branch of the certificate check a host takes is decided by its name and nothing else, and what any of those three do with server-scoped DMVs, with DBCC CHECKDB, or with a certificate has not been measured here.

Untested is not unsupported, and it is not supported either. It is the third state, and it is the one this post publishes.