Skip to content

Add an mssql Persister for SQL Server and Azure SQL - #295

Open
nltgpeterskoglund wants to merge 4 commits into
tinyplex:mainfrom
nltgpeterskoglund:mssql-persister
Open

Add an mssql Persister for SQL Server and Azure SQL#295
nltgpeterskoglund wants to merge 4 commits into
tinyplex:mainfrom
nltgpeterskoglund:mssql-persister

Conversation

@nltgpeterskoglund

Copy link
Copy Markdown

Adds persister-mssql, binding to the mssql package so that SQL Server,
Azure SQL Database and Azure SQL Managed Instance are all covered by one
Persister. It follows the persister-pg shape: you pass in a ConnectionPool
you have configured yourself, which keeps TinyBase out of the authentication
story entirely — including the passwordless Entra ID options that Microsoft
recommends for applications hosted in Azure.

const pool = await sql.connect({
  server: 'myserver.database.windows.net',
  database: 'mydb',
  authentication: {type: 'azure-active-directory-default'},
  options: {encrypt: true},
});
const persister = await createMsSqlPersister(store, pool, 'my_tinybase');
await persister.startAutoPersisting();

Scope

JSON serialization only, for both Store and MergeableStore. Tabular mapping
is deliberately left out; it would need per-table change detection, which is
the part of this that SQL Server makes expensive. json.test.ts and
mergeable-json.test.ts therefore run a new ALL_JSON_VARIANTS roll-up, while
tabular.test.ts stays on ALL_VARIANTS.

Change detection

There is no LISTEN/NOTIFY equivalent available across every flavor of SQL
Server — Service Broker is not on Azure SQL Database, and Change Tracking needs
ALTER DATABASE, which is more privilege than a library should assume. So
auto-loading adds a rowversion column and polls it. SQL Server maintains that
column itself on every insert and update, so writes from other clients are
still picked up.

The one subtlety: the dialect's querySchema excludes DATA_TYPE='timestamp',
which hides the column from the shared schema handling. Without that,
saveTable would drop it as an unaccounted-for column on every save. Excluding
it is also just correct, since a rowversion column can never be written to.

Please look closely at these

Three small parameters are threaded through the shared database code
(commands.ts, common.ts, json.ts, tabular.ts), because T-SQL differs
from both existing dialects:

  • trueConditionWHERE(true) is not valid T-SQL; it needs 1=1.
  • rowIdColumnTypenvarchar(max) cannot be a primary key, so the row Id
    column needs a narrower type than the others.
  • the drop-column keyword — T-SQL spells it DROP COLUMN.

All three default to the current spelling, so SQLite and PostgreSQL emit
byte-identical SQL. The test harness also gains a dialect discriminator in
place of the isPostgres boolean. Happy to reshape any of this if you would
prefer it done differently.

Separately, getLabelBlocks in the gulpfile memoized its Map before filling
it, so concurrent copyDefinition calls could see it empty. Adding a module
was enough to make that surface as Missing docs label. It now memoizes the
promise instead.

Testing

Run against SQL Server 2022 and PostgreSQL 17, both in Docker: zero failures
for mssql, pg, postgres, pglite and every SQLite variant across the
persister suites, and both docs.js examples execute. The remaining failures
in my environment are pre-existing Windows ones, unrelated to this change.

docker run -d -p 1433:1433 -e ACCEPT_EULA=Y \
  -e MSSQL_SA_PASSWORD=<password> mcr.microsoft.com/mssql/server:2022-latest
export TINYBASE_MSSQL_PASSWORD=<password>
npx vitest run --project unit-persisters --retry=0

The matrix variant reads TINYBASE_MSSQL_SERVER, TINYBASE_MSSQL_PORT,
TINYBASE_MSSQL_USER and TINYBASE_MSSQL_PASSWORD rather than hard-coding a
connection string, since SQL Server cannot be trust-authenticated the way the
local PostgreSQL variants are.

Two things left for you:

  • coverage.json is untouched. It is only rewritten by a fully green
    gulp testUnit, which I could not reach on Windows.
  • @since 9.8.0 is a guess at the next version.

Opened alongside #294, where I asked about the scope first — happy to close
this and rework it there if you would rather it landed differently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant