Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2f6fc86
Open Phase 6: bump to 1.0.0-preview.1
lgamorim Jul 12, 2026
20f18a6
Strong-name all assemblies with a shared key
lgamorim Jul 12, 2026
298b959
Freeze the public API with declared-surface baselines
lgamorim Jul 12, 2026
d2a7641
Narrow the migrations surface to consumer-facing types
lgamorim Jul 12, 2026
419aa22
Complete NuGet package metadata
lgamorim Jul 12, 2026
fd073cb
Make CI builds reproducible with deterministic paths
lgamorim Jul 12, 2026
a110ea4
Add CHANGELOG documenting the 1.0.0-rc.1 release
lgamorim Jul 12, 2026
edae77f
Add gated nuget.org release workflow
lgamorim Jul 12, 2026
01f3df6
Document coexistence, shipping decisions, and RC status
lgamorim Jul 12, 2026
a2be01a
Publish a DocFX documentation site to GitHub Pages
lgamorim Jul 12, 2026
5b37ad0
Close Phase 6 at 1.0.0-rc.1 for release-candidate review
lgamorim Jul 12, 2026
a948a5c
Add the SqlBound logo to the README, packages, and docs site
lgamorim Jul 12, 2026
3113ba2
Link the published docs site from the NuGet README
lgamorim Jul 12, 2026
56952ad
Mirror the repo README's license wording in the NuGet README
lgamorim Jul 12, 2026
f295eaa
Stop scanning MySQL @@ system variables as parameters
lgamorim Jul 13, 2026
b440106
Require a matching expected-version to publish a release
lgamorim Jul 13, 2026
6083525
Match MySQL's whitespace rule for -- line comments
lgamorim Jul 13, 2026
b2d8fc5
Report scalar conversion failures with both types named
lgamorim Jul 13, 2026
4107abf
Delete WAL sidecar files when dropping a SQLite database
lgamorim Jul 13, 2026
ec82205
Complete the using directives in both README examples
lgamorim Jul 13, 2026
e6e5ccf
Use Markdown image syntax for the NuGet README logo
lgamorim Jul 13, 2026
c0b5552
Use a dedicated square glyph for the NuGet package icon
lgamorim Jul 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Docs

# Builds the DocFX documentation site and deploys it to GitHub Pages on every push
# to master. Requires GitHub Pages to be enabled for the repository with the source
# set to "GitHub Actions" (Settings -> Pages); until then the build still runs but
# the deploy step has nothing to publish to.
on:
push:
branches:
- master
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

# Allow one concurrent deployment; a newer push supersedes an in-progress run.
concurrency:
group: pages
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
global-json-file: global.json

- name: Install DocFX
run: dotnet tool install --global docfx

- name: Build site
run: docfx docfx.json

- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: _site

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
82 changes: 82 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Release

# Builds and packs the release on any v*.*.* tag, but never publishes on its own.
# Publishing to nuget.org happens only through a deliberate manual run of this
# workflow with publish=true and an expected-version that matches what the selected
# ref packs (run it from the release tag, not master) — and only once a maintainer
# has added the NUGET_API_KEY repository secret. A plain tag push produces verified
# packages as build artifacts for review; it does not release them.
on:
push:
tags:
- 'v*.*.*'
workflow_dispatch:
inputs:
publish:
description: 'Publish packages to nuget.org (leave unchecked to only build and pack)'
type: boolean
default: false
expected-version:
description: 'The exact PackageVersion this run must publish (e.g. 1.0.0); required when publish is checked'
type: string
default: ''

jobs:
release:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
global-json-file: global.json

- name: Restore
run: dotnet restore

- name: Build
run: dotnet build --configuration Release --no-restore

- name: Test
run: dotnet test --configuration Release --no-build

- name: Pack
run: dotnet pack --configuration Release --no-build --output artifacts

- name: Upload NuGet artifacts
uses: actions/upload-artifact@v4
with:
name: nuget-packages
path: |
artifacts/*.nupkg
artifacts/*.snupkg

- name: Verify the version being published
# A workflow_dispatch run builds whatever ref was selected in the "Run workflow"
# dropdown — master by default, not the release tag. Requiring the maintainer to
# type the exact version and matching it against the packed output makes publishing
# a stale or unintended ref fail here instead of reaching nuget.org.
if: ${{ github.event_name == 'workflow_dispatch' && inputs.publish }}
run: |
expected='${{ inputs.expected-version }}'
if [ -z "$expected" ]; then
echo "::error::publish=true requires expected-version to be set."
exit 1
fi
if [ ! -f "artifacts/SqlBound.${expected}.nupkg" ]; then
echo "::error::This run packed $(ls artifacts/SqlBound.[0-9]*.nupkg), not version ${expected}. Select the release tag as the run's ref and set expected-version to its version."
exit 1
fi

- name: Publish to nuget.org
# Gate: only a manual workflow_dispatch with publish=true reaches this step.
# Tag pushes stop after packing. Publishing 1.0.0 is an opt-in maintainer action.
if: ${{ github.event_name == 'workflow_dispatch' && inputs.publish }}
run: >
dotnet nuget push "artifacts/*.nupkg"
--api-key "${{ secrets.NUGET_API_KEY }}"
--source https://api.nuget.org/v3/index.json
--skip-duplicate
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -430,3 +430,7 @@ FodyWeavers.xsd

# JetBrains Rider
.idea/

# DocFX documentation site (generated)
_site/
api/
124 changes: 124 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Changelog

All notable changes to this project are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [1.0.0-rc.1] - 2026-07-12

First release candidate for 1.0 (Phase 6 — Ship, M16). Freezes the public API
and finalizes packaging ahead of the stable 1.0.0 release. No functional
changes to the query, verification, or migration engines from `0.5.0` beyond
the MySQL scanner fix below.

### Added

- Strong-named assemblies across the whole library set, sharing one committed
key so strong-named consumers can reference SqlBound.
- Public-API baselines (`PublicAPI.Shipped.txt` / `PublicAPI.Unshipped.txt`)
on every shipping library, enforced at build time by
`Microsoft.CodeAnalysis.PublicApiAnalyzers`, so post-1.0 surface changes are
caught mechanically.
- A package-oriented NuGet README embedded in every package, plus per-package
`PackageTags`.
- `ContinuousIntegrationBuild` for deterministic, path-normalized CI output;
SourceLink (SDK-provided) verified to map symbols to the committed sources.
- Guide: "Using SqlBound alongside Dapper" (`docs/dapper-coexistence.md`).
- ADR 0008: public-API stability policy, package-signing rationale, and the
documentation-site decision.
- A DocFX documentation site published to GitHub Pages.
- A tag-triggered NuGet release workflow (publish deliberately gated off until
the 1.0.0 promotion, and additionally guarded by a required
`expected-version` input that must match the packed output).

### Fixed

- The MySQL parameter scanner no longer mistakes `@@` system variables (e.g.
`@@sql_mode`, `@@session.sql_mode`) for parameter placeholders, which made
`prepare` declare a bogus parameter and verification demand a method
parameter that should not exist.
- The MySQL parameter scanner now applies MySQL's actual line-comment rule:
`--` starts a comment only when followed by whitespace or the end of the
statement, so an expression like `1--@x` no longer swallows the rest of the
line.
- `SqlSession.FetchScalarAsync<T>` reports a failed scalar conversion as an
`InvalidOperationException` naming the actual and requested types (instead
of leaking a bare `InvalidCastException`), and its no-value error message
now acknowledges that the result may have been a database NULL.
- `sqlbound database drop` for SQLite also removes stale `-wal`/`-shm`
sidecar files a crashed process may have left beside the database file.

### Changed

- Narrowed the `SqlBound.Migrations` public surface: `MigrationPlan`,
`MigrationReverter`, and `MigrationStatusReport` are now `internal` (pure
decision helpers composed by `MigrationRunner`; widening later is
non-breaking, narrowing after 1.0 would not be).

## [0.5.0] - 2026-07-12

Phase 5 — Migrations & CLI (M13–M15).

### Added

- SQL-file migration model (paired `{version}_{name}.up.sql` / `.down.sql`,
timestamp versions, SHA-256 checksums) and the `_sqlbound_migrations` ledger.
- Provider-neutral migration engine (`migrate add` / `run` / `revert` /
`status`) with checksum-drift and out-of-order safety checks, plus per-migration
transactions where the provider supports transactional DDL.
- `database create` / `drop` CLI commands.
- Cross-provider support for migrations and database administration across SQL
Server, SQLite, PostgreSQL, and MySQL.
- ADR 0006 (migration file format) and ADR 0007 (MySQL non-transactional DDL).

## [0.4.0] - 2026-07-12

Phase 4 — Providers (M10–M12).

### Added

- Introspection providers for SQLite, PostgreSQL, and MySQL alongside SQL
Server, each with real type mapping and describe fidelity verified against a
live database.
- The `SqlBound.Introspection` abstraction (`IQueryDescriber`) that all
providers implement.

## [0.3.0] - 2026-07-11

Phase 3 — Verification (M7–M9).

### Added

- SQL Server schema introspection, `SQLB###` diagnostics, and committed offline
JSON snapshots (`.sqlbound/`) so the in-IDE analyzer validates without a
database connection.

## [0.2.0] - 2026-07-11

Phase 2 — Codegen (M4–M6).

### Added

- Reflection-free row materialization via an incremental source generator,
query-shape support, Native AOT compatibility, and benchmarks versus Dapper
and raw ADO.NET.

## [0.1.0] - 2026-07-11

Phase 1 — Bedrock (M1–M3).

### Added

- Solution skeleton and CI, the `System.Data.Common`-based execution core, and
the Dapper-coexistence sample that doubles as a permanent CI regression test.

[Unreleased]: https://github.com/lgamorim/sqlbound/compare/v1.0.0-rc.1...HEAD
[1.0.0-rc.1]: https://github.com/lgamorim/sqlbound/compare/v0.5.0...v1.0.0-rc.1
[0.5.0]: https://github.com/lgamorim/sqlbound/compare/v0.4.0...v0.5.0
[0.4.0]: https://github.com/lgamorim/sqlbound/compare/v0.3.0...v0.4.0
[0.3.0]: https://github.com/lgamorim/sqlbound/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/lgamorim/sqlbound/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/lgamorim/sqlbound/releases/tag/v0.1.0
38 changes: 37 additions & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,38 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

<!--
Strong-naming is a one-way, identity-level decision fixed at 1.0: a strong-named consumer
cannot reference an unsigned assembly, and every data-access peer (EF Core, Dapper, Npgsql,
Microsoft.Data.*) is signed. Every project shares one committed key (the key is not a secret;
strong naming is an identity token, not a security boundary). PublicKey lets the SDK stamp each
InternalsVisibleTo with the friend's key automatically.
-->
<PropertyGroup Label="Strong naming">
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)sqlbound.snk</AssemblyOriginatorKeyFile>
<PublicKey>002400000480000094000000060200000024000052534131000400000100010099008ae592c0e403ce8ee0fad0005f95e61c709799847b62d56c375c739ac245fb993ccb7912a3d983024e1478ab13ba15094ae76aac09342324d3746124c97c9ac21cb8b877948400a7b7689f74ab55771620618f0a685fadb784a4a7636f9270647efb8a4fbaded853792609197b6fb2d271f178438f06a77c0746523a97ca</PublicKey>
</PropertyGroup>

<!--
Reproducible builds. SourceLink ships in the .NET 8+ SDK and is active via PublishRepositoryUrl
+ EmbedUntrackedSources below, so consumers can step into the exact committed sources from the
snupkg symbols. ContinuousIntegrationBuild normalizes source paths for byte-reproducible output;
it is gated on CI (GitHub Actions sets CI=true) because it also disables local path mapping that
debuggers rely on. Deterministic is the SDK default — pinned here to make the intent explicit.
-->
<PropertyGroup Label="Reproducible builds">
<Deterministic>true</Deterministic>
<ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>
</PropertyGroup>

<PropertyGroup Label="Package metadata">
<Authors>Luís Amorim</Authors>
<PackageVersion>0.5.0</PackageVersion>
<PackageVersion>1.0.0-rc.1</PackageVersion>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageIcon>icon.png</PackageIcon>
<PackageTags>sql;ado.net;dapper;source-generator;native-aot;sqlx;query;database;migrations</PackageTags>
<PackageProjectUrl>https://github.com/lgamorim/sqlbound</PackageProjectUrl>
<RepositoryUrl>https://github.com/lgamorim/sqlbound</RepositoryUrl>
<RepositoryType>git</RepositoryType>
Expand All @@ -23,6 +51,14 @@
<EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>

<!-- The shared NuGet README and icon (packed at the package root, referenced by the
PackageReadmeFile / PackageIcon properties above). The wordmark logo is not packed:
both READMEs load it by absolute URL, so it need not travel inside every package. -->
<ItemGroup Condition="'$(IsPackable)' != 'false'">
<None Include="$(MSBuildThisFileDirectory)docs\nuget\README.md" Pack="true" PackagePath="\" Visible="false" />
<None Include="$(MSBuildThisFileDirectory)assets\icon.png" Pack="true" PackagePath="\" Visible="false" />
</ItemGroup>

<!--
Test projects are exempt from public XML-doc enforcement and are never packed (coding-standards.md).
GenerateDocumentationFile stays true even here: Roslyn requires it to run IDE0005 (unused usings)
Expand Down
20 changes: 20 additions & 0 deletions Directory.Build.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project>

<!--
Public-API surface tracking. Opted into per project with <TrackPublicApi>true</TrackPublicApi>
(set only on the shipping library packages — not tests, the CLI tool, or the Roslyn component,
whose surfaces are the command line and the SQLB### diagnostics respectively). Wired here in
.targets rather than .props so it is evaluated after each project body has set its own flags.

The analyzer fails the build (RS0016/RS0017 under TreatWarningsAsErrors) if the public surface
drifts from the committed baseline, making the post-1.0 SemVer commitment tool-enforced. Until
1.0.0 actually ships, the whole surface lives in PublicAPI.Unshipped.txt; the GA promotion moves
it to PublicAPI.Shipped.txt.
-->
<ItemGroup Condition="'$(TrackPublicApi)' == 'true'">
<PackageReference Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="3.3.4" PrivateAssets="all" />
<AdditionalFiles Include="$(MSBuildProjectDirectory)\PublicAPI.Shipped.txt" />
<AdditionalFiles Include="$(MSBuildProjectDirectory)\PublicAPI.Unshipped.txt" />
</ItemGroup>

</Project>
Loading
Loading