Microsoft SQL Server access governance — every statement reviewed before it runs
Last updated
AccessFlow governs Microsoft SQL Server as a full query proxy: your team connects to AccessFlow, not to the database, and every statement is parsed, checked against a schema allow-list, risk-scored, routed to an approver, and only then executed — under column masking, row-level security and a row cap. The pipeline itself — parse, risk-score, route, approve, execute, audit — is the same for every relational engine and is described in full on the database access governance page. What follows is what is specific to Microsoft SQL Server.
- Family
- Relational
- Query language
- SQL
- Connects via
- Database driver
- Default port
1433- Default SSL mode
REQUIRE- Install
- One click — checksum-verified download
The driver and the connection
The driver is com.microsoft.sqlserver:mssql-jdbc, resolved from the connector catalog on first use and verified before it loads.
The datasource takes host, port (1433 by default), database, username and password — AES-256-GCM encrypted at rest, decrypted once when AccessFlow opens its connections, or held as a secret-manager reference. SSL defaults to REQUIRE.
AccessFlow builds the connection string from the datasource fields using the template the connector manifest declares, so what actually reaches the driver is predictable and reviewable:
jdbc:sqlserver://{host}:{port};databaseName={database_name}
com.microsoft.sqlserver.jdbc.SQLServerDriver
The driver is fetched the first time a datasource of this type is created or tested, and every driver AccessFlow loads is kept separate from every other one. Two datasources on different driver versions — even two Microsoft SQL Server datasources — never share a copy and cannot interfere with each other.
What AccessFlow parses
Statements are parsed before anything else happens, and the schema allow-list is checked against the tables the statement really references rather than the raw text. T-SQL bracket-quoted identifiers are read as identifiers, and the resulting classification drives the capability check and the approval chain.
What it refuses
Multi-statement input is refused, except for a BEGIN; … COMMIT; envelope around a homogeneous write batch. Unparseable input is rejected with HTTP 422 before a connection is used.
Row-level security and masking
A policy becomes a bound predicate in the WHERE clause, and shapes the rewriter cannot provably narrow are refused rather than run unfiltered. Masking is applied after fetch. The interaction between those bound values and the SHOWPLAN path is the one place SQL Server behaves differently from the other engines — see below.
What the rewrite actually does
A policy restricting claims to the caller's region narrows the statement before SQL Server compiles it:
-- submitted SELECT TOP 100 [id], [amount] FROM [claims] WHERE [state] = 'OPEN'; -- executed (policy: claims.region = the caller's region) SELECT TOP 100 [id], [amount] FROM [claims] WHERE ([state] = 'OPEN') AND [claims].[region] = ?;
This is also the exact statement whose dry-run degrades. That bound parameter is the thing a plain Statement cannot carry, which is why the plan preview reports itself unsupported rather than planning something other than what would run. Execution is unaffected.
Introspection, dry-run and cost estimates
A dry-run plans the statement with SET SHOWPLAN_ALL ON. It is the one place in AccessFlow that steps outside the parameterised path every other query takes, and it is a documented, deliberate exception: the Microsoft driver returns no plan rows at all over the parameterised path, so a plain batch is the only shape that yields one. It carries no user values and it never executes the statement.
Where the dry-run degrades, and why that is the safe answer
A plain Statement cannot carry bound parameters, and row-level security emits its policy values as binds. Inlining them into the SQL text is not an option — that is precisely the string concatenation the proxy exists to prevent. So a dry-run of a statement whose rewrite produced binds reports itself unsupported, with a specific reason, rather than surfacing a driver error or quietly planning a statement other than the one that would run.
Real execution is unaffected: it binds the same predicate through a PreparedStatement exactly as every other engine does. The degradation is confined to the non-executing plan preview, which is the right place to lose a feature rather than a guarantee.