sqlproof 0.1.0a1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlproof-0.1.0a1/.github/workflows/ci.yml +38 -0
- sqlproof-0.1.0a1/.github/workflows/deploy-website.yml +41 -0
- sqlproof-0.1.0a1/.github/workflows/nightly.yml +19 -0
- sqlproof-0.1.0a1/.github/workflows/release.yml +36 -0
- sqlproof-0.1.0a1/.gitignore +13 -0
- sqlproof-0.1.0a1/CHANGELOG.md +58 -0
- sqlproof-0.1.0a1/PKG-INFO +248 -0
- sqlproof-0.1.0a1/README.md +199 -0
- sqlproof-0.1.0a1/SPEC.md +1188 -0
- sqlproof-0.1.0a1/examples/ecommerce/schema.sql +11 -0
- sqlproof-0.1.0a1/examples/ecommerce/test_orders.py +20 -0
- sqlproof-0.1.0a1/examples/ripenn_scoring/schema.sql +4 -0
- sqlproof-0.1.0a1/examples/ripenn_scoring/test_scoring.py +13 -0
- sqlproof-0.1.0a1/pyproject.toml +119 -0
- sqlproof-0.1.0a1/src/sqlproof/__init__.py +32 -0
- sqlproof-0.1.0a1/src/sqlproof/_version.py +1 -0
- sqlproof-0.1.0a1/src/sqlproof/cli.py +151 -0
- sqlproof-0.1.0a1/src/sqlproof/client.py +159 -0
- sqlproof-0.1.0a1/src/sqlproof/config.py +42 -0
- sqlproof-0.1.0a1/src/sqlproof/contrib/__init__.py +3 -0
- sqlproof-0.1.0a1/src/sqlproof/contrib/supabase.py +136 -0
- sqlproof-0.1.0a1/src/sqlproof/core.py +344 -0
- sqlproof-0.1.0a1/src/sqlproof/coverage/__init__.py +6 -0
- sqlproof-0.1.0a1/src/sqlproof/coverage/diversity.py +11 -0
- sqlproof-0.1.0a1/src/sqlproof/coverage/plpgsql.py +5 -0
- sqlproof-0.1.0a1/src/sqlproof/coverage/schema_shape.py +7 -0
- sqlproof-0.1.0a1/src/sqlproof/exceptions.py +47 -0
- sqlproof-0.1.0a1/src/sqlproof/generators/__init__.py +21 -0
- sqlproof-0.1.0a1/src/sqlproof/generators/columns.py +93 -0
- sqlproof-0.1.0a1/src/sqlproof/generators/constraints.py +181 -0
- sqlproof-0.1.0a1/src/sqlproof/generators/functions.py +9 -0
- sqlproof-0.1.0a1/src/sqlproof/generators/graph.py +51 -0
- sqlproof-0.1.0a1/src/sqlproof/generators/rows.py +153 -0
- sqlproof-0.1.0a1/src/sqlproof/generators/sampling.py +15 -0
- sqlproof-0.1.0a1/src/sqlproof/generators/well_known.py +59 -0
- sqlproof-0.1.0a1/src/sqlproof/pytest_plugin.py +24 -0
- sqlproof-0.1.0a1/src/sqlproof/reporter/__init__.py +5 -0
- sqlproof-0.1.0a1/src/sqlproof/reporter/console.py +20 -0
- sqlproof-0.1.0a1/src/sqlproof/reporter/json_io.py +26 -0
- sqlproof-0.1.0a1/src/sqlproof/runners/__init__.py +14 -0
- sqlproof-0.1.0a1/src/sqlproof/runners/db.py +48 -0
- sqlproof-0.1.0a1/src/sqlproof/runners/migration.py +51 -0
- sqlproof-0.1.0a1/src/sqlproof/runners/overload.py +41 -0
- sqlproof-0.1.0a1/src/sqlproof/runners/property.py +119 -0
- sqlproof-0.1.0a1/src/sqlproof/runners/rls.py +40 -0
- sqlproof-0.1.0a1/src/sqlproof/runners/stateful.py +36 -0
- sqlproof-0.1.0a1/src/sqlproof/schema/__init__.py +27 -0
- sqlproof-0.1.0a1/src/sqlproof/schema/dependency_graph.py +38 -0
- sqlproof-0.1.0a1/src/sqlproof/schema/fingerprint.py +34 -0
- sqlproof-0.1.0a1/src/sqlproof/schema/introspect.py +229 -0
- sqlproof-0.1.0a1/src/sqlproof/schema/model.py +98 -0
- sqlproof-0.1.0a1/src/sqlproof/schema/parse_sql.py +206 -0
- sqlproof-0.1.0a1/src/sqlproof/testing.py +101 -0
- sqlproof-0.1.0a1/src/sqlproof/types.py +34 -0
- sqlproof-0.1.0a1/tests/benchmarks/test_generation_benchmark.py +25 -0
- sqlproof-0.1.0a1/tests/conftest.py +1 -0
- sqlproof-0.1.0a1/tests/integration/test_postgres_execution.py +47 -0
- sqlproof-0.1.0a1/tests/meta/test_meta_properties.py +99 -0
- sqlproof-0.1.0a1/tests/unit/test_cli_smoke.py +65 -0
- sqlproof-0.1.0a1/tests/unit/test_contrib_supabase.py +296 -0
- sqlproof-0.1.0a1/tests/unit/test_db_manager.py +122 -0
- sqlproof-0.1.0a1/tests/unit/test_generators_core.py +511 -0
- sqlproof-0.1.0a1/tests/unit/test_package_scaffold.py +58 -0
- sqlproof-0.1.0a1/tests/unit/test_runner_slice.py +353 -0
- sqlproof-0.1.0a1/tests/unit/test_schema_core.py +268 -0
- sqlproof-0.1.0a1/tests/unit/test_state_machine.py +156 -0
- sqlproof-0.1.0a1/tests/unit/test_v01_surfaces.py +119 -0
- sqlproof-0.1.0a1/uv.lock +1459 -0
- sqlproof-0.1.0a1/website/astro.config.mjs +42 -0
- sqlproof-0.1.0a1/website/package-lock.json +7429 -0
- sqlproof-0.1.0a1/website/package.json +16 -0
- sqlproof-0.1.0a1/website/public/CNAME +1 -0
- sqlproof-0.1.0a1/website/public/favicon.svg +4 -0
- sqlproof-0.1.0a1/website/src/content/config.ts +6 -0
- sqlproof-0.1.0a1/website/src/content/docs/api/check-options.md +63 -0
- sqlproof-0.1.0a1/website/src/content/docs/api/sqlproof-class.md +138 -0
- sqlproof-0.1.0a1/website/src/content/docs/api/state-machine.md +153 -0
- sqlproof-0.1.0a1/website/src/content/docs/api/table-customization.md +39 -0
- sqlproof-0.1.0a1/website/src/content/docs/examples/orders.md +59 -0
- sqlproof-0.1.0a1/website/src/content/docs/getting-started.md +73 -0
- sqlproof-0.1.0a1/website/src/content/docs/guides/ci-cd.md +67 -0
- sqlproof-0.1.0a1/website/src/content/docs/guides/custom-generators.md +52 -0
- sqlproof-0.1.0a1/website/src/content/docs/guides/fk-distributions.md +53 -0
- sqlproof-0.1.0a1/website/src/content/docs/guides/local-dev.md +54 -0
- sqlproof-0.1.0a1/website/src/content/docs/guides/security.md +43 -0
- sqlproof-0.1.0a1/website/src/content/docs/guides/supabase.md +134 -0
- sqlproof-0.1.0a1/website/src/pages/index.astro +165 -0
- sqlproof-0.1.0a1/website/src/styles/custom.css +304 -0
- sqlproof-0.1.0a1/website/tsconfig.json +5 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
python:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: ${{ matrix.python-version }}
|
|
19
|
+
- uses: astral-sh/setup-uv@v5
|
|
20
|
+
- run: uv sync --extra dev
|
|
21
|
+
- run: uv run ruff check src/ tests/
|
|
22
|
+
- run: uv run pyright
|
|
23
|
+
- run: uv run mypy src/sqlproof/
|
|
24
|
+
- run: uv run pytest --cov=sqlproof --cov-fail-under=95
|
|
25
|
+
|
|
26
|
+
website:
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
- uses: actions/setup-node@v4
|
|
31
|
+
with:
|
|
32
|
+
node-version: 20
|
|
33
|
+
cache: npm
|
|
34
|
+
cache-dependency-path: website/package-lock.json
|
|
35
|
+
- run: npm ci
|
|
36
|
+
working-directory: website
|
|
37
|
+
- run: npm run build
|
|
38
|
+
working-directory: website
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: Deploy website to GitHub Pages
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths:
|
|
7
|
+
- 'website/**'
|
|
8
|
+
- '.github/workflows/deploy-website.yml'
|
|
9
|
+
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
deploy:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
permissions:
|
|
16
|
+
contents: write
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- name: Checkout
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- name: Set up Node
|
|
23
|
+
uses: actions/setup-node@v4
|
|
24
|
+
with:
|
|
25
|
+
node-version: 20
|
|
26
|
+
cache: npm
|
|
27
|
+
cache-dependency-path: website/package-lock.json
|
|
28
|
+
|
|
29
|
+
- name: Install dependencies
|
|
30
|
+
run: npm ci
|
|
31
|
+
working-directory: website
|
|
32
|
+
|
|
33
|
+
- name: Build
|
|
34
|
+
run: npm run build
|
|
35
|
+
working-directory: website
|
|
36
|
+
|
|
37
|
+
- name: Deploy to GitHub Pages
|
|
38
|
+
uses: peaceiris/actions-gh-pages@v4
|
|
39
|
+
with:
|
|
40
|
+
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
41
|
+
publish_dir: website/dist
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: Nightly
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
schedule:
|
|
5
|
+
- cron: "0 2 * * *"
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
extended:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: actions/setup-python@v5
|
|
14
|
+
with:
|
|
15
|
+
python-version: "3.11"
|
|
16
|
+
- uses: astral-sh/setup-uv@v5
|
|
17
|
+
- run: uv sync --extra dev
|
|
18
|
+
- run: uv run pytest tests/meta tests/unit
|
|
19
|
+
- run: uv run pytest tests/benchmarks || true
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: actions/setup-python@v5
|
|
14
|
+
with:
|
|
15
|
+
python-version: "3.11"
|
|
16
|
+
- uses: astral-sh/setup-uv@v5
|
|
17
|
+
- run: uv sync --extra dev
|
|
18
|
+
- run: uv run pytest -m "not nocover"
|
|
19
|
+
- run: uv build
|
|
20
|
+
- uses: actions/upload-artifact@v4
|
|
21
|
+
with:
|
|
22
|
+
name: dist
|
|
23
|
+
path: dist/
|
|
24
|
+
|
|
25
|
+
publish:
|
|
26
|
+
needs: build
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
environment: pypi
|
|
29
|
+
permissions:
|
|
30
|
+
id-token: write
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/download-artifact@v4
|
|
33
|
+
with:
|
|
34
|
+
name: dist
|
|
35
|
+
path: dist/
|
|
36
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to SqlProof will be documented here. The format is based on
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres
|
|
5
|
+
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). While SqlProof
|
|
6
|
+
remains in `0.x`, minor versions may include breaking changes.
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0a1] - 2026-05-04
|
|
11
|
+
|
|
12
|
+
First public release. Early-stage alpha — APIs are unstable.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **Stateful testing.** New `sqlproof.testing.SqlProofStateMachine` base class
|
|
17
|
+
that wires Hypothesis's `RuleBasedStateMachine` into SqlProof: each example
|
|
18
|
+
leases an isolated `SqlProofClient` via `proof.client_for_dataset(...)`, and
|
|
19
|
+
writes are rolled back between examples. Subclasses define `@rule`s and
|
|
20
|
+
`@invariant`s as usual; `on_setup()` runs once per example with `self.db`
|
|
21
|
+
ready. `self.enter(cm)` adopts a context manager for the example's lifetime
|
|
22
|
+
(useful for JWT claims, savepoints, mocked clocks). Run via
|
|
23
|
+
`proof.run_state_machine(MyMachine, settings=...)`.
|
|
24
|
+
- **Supabase contrib helpers** in `sqlproof.contrib.supabase`:
|
|
25
|
+
- `as_supabase_user(db, user_id, role=...)` — context manager that sets
|
|
26
|
+
`request.jwt.claims` for the duration of the block, so PostgREST/Supabase
|
|
27
|
+
helpers (`auth.uid()`, `auth.jwt()`) resolve to the given user. Restores
|
|
28
|
+
prior claim on exit; nested invocations stack and unwind correctly.
|
|
29
|
+
- `seed_supabase_test_users(db, count)` — calls Supabase's auth admin API to
|
|
30
|
+
(re)create N deterministic test users with the
|
|
31
|
+
`sqlproof_<n>@test.invalid` email pattern. Idempotent.
|
|
32
|
+
- `seed_test_users_directly(db, count)` — alternative SQL-only path that
|
|
33
|
+
inserts into `auth.users` via the existing connection. Lets tests run
|
|
34
|
+
when the admin API key is unavailable but the test connection has write
|
|
35
|
+
access to the auth schema (e.g. local Supabase).
|
|
36
|
+
- **Dataset generation flexibility:**
|
|
37
|
+
- Shrinkable per-table size strategies (pass a Hypothesis integer strategy
|
|
38
|
+
where `SizeSpec` is accepted; size shrinks alongside data).
|
|
39
|
+
- Column-level overrides keyed by `"<table>.<column>"`: fixed values,
|
|
40
|
+
Hypothesis strategies, or callable derivers.
|
|
41
|
+
- External-table FK sampling: register an `ExternalTableSpec` to draw FK
|
|
42
|
+
values from a live external parent table (e.g. `auth.users`).
|
|
43
|
+
- **Project specification document** (`SPEC.md`) capturing mission,
|
|
44
|
+
architecture, API surface, and design constraints.
|
|
45
|
+
|
|
46
|
+
### Known limitations
|
|
47
|
+
|
|
48
|
+
- Schema introspection covers tables, columns, FKs, CHECK constraints, UNIQUE
|
|
49
|
+
constraints, and enums. Exclusion constraints, partial unique indexes, and
|
|
50
|
+
generated columns are not yet honored.
|
|
51
|
+
- Some Postgres types (range types, composite types, custom domains) are not
|
|
52
|
+
yet supported.
|
|
53
|
+
- The pytest plugin entry point exists but the CLI flags and reporter wiring
|
|
54
|
+
are still stabilizing.
|
|
55
|
+
- No deprecation policy yet — breaking changes ship freely in `0.x`.
|
|
56
|
+
|
|
57
|
+
[Unreleased]: https://github.com/alialavia/sqlproof/compare/v0.1.0a1...HEAD
|
|
58
|
+
[0.1.0a1]: https://github.com/alialavia/sqlproof/releases/tag/v0.1.0a1
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sqlproof
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: Property-based testing for PostgreSQL schemas and SQL behavior. Early-stage alpha — APIs may change.
|
|
5
|
+
Project-URL: Homepage, https://sqlproof.com
|
|
6
|
+
Project-URL: Documentation, https://sqlproof.com
|
|
7
|
+
Project-URL: Repository, https://github.com/alialavia/sqlproof
|
|
8
|
+
Project-URL: Issues, https://github.com/alialavia/sqlproof/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/alialavia/sqlproof/blob/main/CHANGELOG.md
|
|
10
|
+
Author: SqlProof contributors
|
|
11
|
+
License: MIT
|
|
12
|
+
Keywords: hypothesis,postgres,postgresql,property-based-testing,rls,sql,testing
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Framework :: Hypothesis
|
|
15
|
+
Classifier: Framework :: Pytest
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Database
|
|
24
|
+
Classifier: Topic :: Database :: Database Engines/Servers
|
|
25
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
26
|
+
Classifier: Topic :: Software Development :: Testing
|
|
27
|
+
Classifier: Typing :: Typed
|
|
28
|
+
Requires-Python: >=3.11
|
|
29
|
+
Requires-Dist: hypothesis>=6.100
|
|
30
|
+
Requires-Dist: pglast>=6.0
|
|
31
|
+
Requires-Dist: psycopg[binary]>=3.1
|
|
32
|
+
Requires-Dist: rich>=13.0
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: mutmut; extra == 'dev'
|
|
35
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
36
|
+
Requires-Dist: pyright; extra == 'dev'
|
|
37
|
+
Requires-Dist: pytest-benchmark; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
39
|
+
Requires-Dist: pytest-xdist; extra == 'dev'
|
|
40
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
41
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
42
|
+
Requires-Dist: syrupy; extra == 'dev'
|
|
43
|
+
Requires-Dist: uv; extra == 'dev'
|
|
44
|
+
Provides-Extra: pydantic
|
|
45
|
+
Requires-Dist: pydantic>=2.0; extra == 'pydantic'
|
|
46
|
+
Provides-Extra: testcontainers
|
|
47
|
+
Requires-Dist: testcontainers[postgres]>=4.0; extra == 'testcontainers'
|
|
48
|
+
Description-Content-Type: text/markdown
|
|
49
|
+
|
|
50
|
+
# SqlProof
|
|
51
|
+
|
|
52
|
+
[](https://github.com/alialavia/sqlproof/actions/workflows/ci.yml)
|
|
53
|
+
[](https://codecov.io/gh/alialavia/sqlproof)
|
|
54
|
+
[](https://pypi.org/project/sqlproof/)
|
|
55
|
+
|
|
56
|
+
**→ Full docs: [sqlproof.com](https://sqlproof.com)**
|
|
57
|
+
|
|
58
|
+
> ⚠️ **Early-stage alpha (`0.1.0a1`).** APIs are unstable and may change without
|
|
59
|
+
> deprecation warnings until 0.x stabilizes. Postgres edge cases and Hypothesis
|
|
60
|
+
> shrink behavior are still being discovered, and coverage of the schema surface
|
|
61
|
+
> area is incomplete. **Do not rely on this for production test suites yet.**
|
|
62
|
+
> Bug reports and reproductions welcome —
|
|
63
|
+
> [open an issue](https://github.com/alialavia/sqlproof/issues).
|
|
64
|
+
|
|
65
|
+
Property-based testing for PostgreSQL schemas and SQL behavior. Define properties about
|
|
66
|
+
your database code; SqlProof generates valid datasets with Hypothesis, executes your
|
|
67
|
+
queries through `psycopg`, and saves the smallest counterexample it finds.
|
|
68
|
+
|
|
69
|
+
## Install
|
|
70
|
+
|
|
71
|
+
Alpha releases are gated behind a pre-release flag so you don't get one by accident:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install --pre sqlproof
|
|
75
|
+
# or:
|
|
76
|
+
uv add --prerelease=allow sqlproof
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Requires Python 3.11+ and PostgreSQL 13+.
|
|
80
|
+
|
|
81
|
+
## Quick Start
|
|
82
|
+
|
|
83
|
+
Given a schema file:
|
|
84
|
+
|
|
85
|
+
```sql
|
|
86
|
+
-- schema.sql
|
|
87
|
+
CREATE TABLE orders (
|
|
88
|
+
id SERIAL PRIMARY KEY,
|
|
89
|
+
customer_id INTEGER NOT NULL,
|
|
90
|
+
total NUMERIC(10,2) NOT NULL CHECK (total >= 0)
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
CREATE TABLE line_items (
|
|
94
|
+
id SERIAL PRIMARY KEY,
|
|
95
|
+
order_id INTEGER NOT NULL REFERENCES orders(id),
|
|
96
|
+
quantity INTEGER NOT NULL CHECK (quantity > 0),
|
|
97
|
+
price NUMERIC(10,2) NOT NULL CHECK (price > 0)
|
|
98
|
+
);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Write property tests with pytest:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from sqlproof import SqlProof, sqlproof
|
|
105
|
+
|
|
106
|
+
proof = SqlProof.from_schema_file("./schema.sql")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@sqlproof(proof, sizes={"orders": 20, "line_items": 50}, runs=50)
|
|
110
|
+
def test_no_orphan_line_items(db):
|
|
111
|
+
rows = db.query("""
|
|
112
|
+
SELECT li.id
|
|
113
|
+
FROM line_items li
|
|
114
|
+
LEFT JOIN orders o ON li.order_id = o.id
|
|
115
|
+
WHERE o.id IS NULL
|
|
116
|
+
""")
|
|
117
|
+
assert rows == []
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
SqlProof will:
|
|
121
|
+
|
|
122
|
+
1. Parse your schema (tables, columns, FKs, CHECK constraints, enums)
|
|
123
|
+
2. Topologically order tables by foreign-key dependencies
|
|
124
|
+
3. Generate datasets that respect common type, FK, CHECK, UNIQUE, and NOT NULL constraints
|
|
125
|
+
4. Run your property with Hypothesis-managed execution and shrinking
|
|
126
|
+
5. Save the shrunk counterexample as JSON when a property fails
|
|
127
|
+
|
|
128
|
+
## API
|
|
129
|
+
|
|
130
|
+
See the full API reference at [sqlproof.com](https://sqlproof.com).
|
|
131
|
+
|
|
132
|
+
### Quick reference
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
proof = SqlProof.from_schema_file("./schema.sql")
|
|
136
|
+
proof = SqlProof.from_connection_string("postgresql://localhost/postgres")
|
|
137
|
+
|
|
138
|
+
proof.check("name", sizes={"orders": 10}, property=lambda db: ...)
|
|
139
|
+
proof.invariant(
|
|
140
|
+
"no bad rows",
|
|
141
|
+
sizes={"orders": 10},
|
|
142
|
+
query="SELECT id FROM orders WHERE total < 0",
|
|
143
|
+
expect_empty=True,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
proof.disconnect()
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Schema Sources
|
|
150
|
+
|
|
151
|
+
**SQL file** — SqlProof parses `CREATE TABLE`, `CREATE TYPE ... AS ENUM`, foreign keys, CHECK constraints, UNIQUE constraints, and column types directly from `.sql` files.
|
|
152
|
+
|
|
153
|
+
**Connection string** — Pass a `postgresql://` URL and SqlProof introspects the live database via `information_schema` and `pg_catalog`.
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
proof = SqlProof.from_connection_string("postgresql://localhost:5432/mydb")
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Custom Column Generators
|
|
160
|
+
|
|
161
|
+
SqlProof maps PostgreSQL types to Hypothesis strategies and refines simple range,
|
|
162
|
+
`IN (...)`, length, FK, and unique constraints. The public customization API is present
|
|
163
|
+
for v0.1 and will grow with richer per-column strategy overrides.
|
|
164
|
+
|
|
165
|
+
### The `db` Client
|
|
166
|
+
|
|
167
|
+
The property function receives a `SqlProofClient`:
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
rows = db.query("SELECT id, total FROM orders WHERE total >= %s", 0)
|
|
171
|
+
total = db.scalar("SELECT count(*) FROM orders")
|
|
172
|
+
typed = db.query_typed("SELECT id, total FROM orders", OrderRow)
|
|
173
|
+
data = db.get_generated_data()
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
- `query()` returns a list of dictionaries.
|
|
177
|
+
- `query_typed()` maps rows into `TypedDict`, dataclass, or Pydantic models.
|
|
178
|
+
- `get_generated_data()` returns the dataset for the current run.
|
|
179
|
+
|
|
180
|
+
## Failure Output
|
|
181
|
+
|
|
182
|
+
When a property fails, SqlProof throws with a formatted counterexample:
|
|
183
|
+
|
|
184
|
+
```text
|
|
185
|
+
Property failed: order totals match sum of line items
|
|
186
|
+
Failure: AssertionError: expected totals to match
|
|
187
|
+
Row context: {'order_id': 1}
|
|
188
|
+
Dataset shape: {'orders': {'rows': 1}, 'line_items': {'rows': 2}}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Counterexamples are written under `.sqlproof/failures/` and can be inspected with:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
sqlproof report .sqlproof/failures/test_name.json
|
|
195
|
+
sqlproof report .sqlproof/failures/test_name.json --format json
|
|
196
|
+
sqlproof replay .sqlproof/failures/test_name.json
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## How It Works
|
|
200
|
+
|
|
201
|
+
1. **Schema parsing** — Reads your SQL file (or introspects a live DB) to extract tables, columns, types, foreign keys, CHECK/UNIQUE constraints, and enums
|
|
202
|
+
|
|
203
|
+
2. **Dependency ordering** — Topologically sorts tables by foreign key references so parent rows are always inserted first
|
|
204
|
+
|
|
205
|
+
3. **Data generation** — Maps PostgreSQL types to Hypothesis strategies and applies constraint-aware generation for CHECK, UNIQUE, NOT NULL, and FK constraints
|
|
206
|
+
|
|
207
|
+
4. **Isolated execution** — Schema-file proofs run against an in-memory client for fast local feedback. DSN-backed proofs introspect PostgreSQL, insert generated data inside savepoints, run the property, then roll back the run.
|
|
208
|
+
|
|
209
|
+
5. **Shrinking** — When a property fails, Hypothesis shrinks the dataset to find the simplest counterexample
|
|
210
|
+
|
|
211
|
+
## Supported PostgreSQL Types
|
|
212
|
+
|
|
213
|
+
`integer`, `smallint`, `bigint`, `serial`, `bigserial`, `numeric(p,s)`, `real`, `double precision`, `boolean`, `text`, `varchar(n)`, `char(n)`, `uuid`, `timestamp`, `timestamptz`, `date`, `time`, `json`, `jsonb`, `bytea`, and custom `ENUM` types.
|
|
214
|
+
|
|
215
|
+
## Development
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
git clone https://github.com/your-org/sqlproof.git
|
|
219
|
+
cd sqlproof
|
|
220
|
+
uv sync --extra dev
|
|
221
|
+
|
|
222
|
+
uv run pytest
|
|
223
|
+
uv run ruff check src tests
|
|
224
|
+
uv run pyright src/sqlproof
|
|
225
|
+
uv run mypy src/sqlproof
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Postgres-backed tests
|
|
229
|
+
|
|
230
|
+
Integration tests are optional and read `SQLPROOF_TEST_DATABASE_URL`:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
SQLPROOF_TEST_DATABASE_URL='postgresql://postgres:postgres@127.0.0.1:5432/postgres' uv run pytest tests/integration
|
|
234
|
+
uv run pytest tests/benchmarks
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
The integration tests create a temporary schema named `sqlproof_it_*` and drop it at the end.
|
|
238
|
+
|
|
239
|
+
### Why SqlProof tests itself with properties
|
|
240
|
+
|
|
241
|
+
SqlProof uses Hypothesis internally, and its own tests use properties for schema
|
|
242
|
+
fingerprinting, dependency ordering, FK validity, constraint generation, shrinking,
|
|
243
|
+
parser idempotence, and counterexample replay. This keeps the library honest about
|
|
244
|
+
the same invariants it asks users to write.
|
|
245
|
+
|
|
246
|
+
## License
|
|
247
|
+
|
|
248
|
+
MIT
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# SqlProof
|
|
2
|
+
|
|
3
|
+
[](https://github.com/alialavia/sqlproof/actions/workflows/ci.yml)
|
|
4
|
+
[](https://codecov.io/gh/alialavia/sqlproof)
|
|
5
|
+
[](https://pypi.org/project/sqlproof/)
|
|
6
|
+
|
|
7
|
+
**→ Full docs: [sqlproof.com](https://sqlproof.com)**
|
|
8
|
+
|
|
9
|
+
> ⚠️ **Early-stage alpha (`0.1.0a1`).** APIs are unstable and may change without
|
|
10
|
+
> deprecation warnings until 0.x stabilizes. Postgres edge cases and Hypothesis
|
|
11
|
+
> shrink behavior are still being discovered, and coverage of the schema surface
|
|
12
|
+
> area is incomplete. **Do not rely on this for production test suites yet.**
|
|
13
|
+
> Bug reports and reproductions welcome —
|
|
14
|
+
> [open an issue](https://github.com/alialavia/sqlproof/issues).
|
|
15
|
+
|
|
16
|
+
Property-based testing for PostgreSQL schemas and SQL behavior. Define properties about
|
|
17
|
+
your database code; SqlProof generates valid datasets with Hypothesis, executes your
|
|
18
|
+
queries through `psycopg`, and saves the smallest counterexample it finds.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
Alpha releases are gated behind a pre-release flag so you don't get one by accident:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install --pre sqlproof
|
|
26
|
+
# or:
|
|
27
|
+
uv add --prerelease=allow sqlproof
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Requires Python 3.11+ and PostgreSQL 13+.
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
Given a schema file:
|
|
35
|
+
|
|
36
|
+
```sql
|
|
37
|
+
-- schema.sql
|
|
38
|
+
CREATE TABLE orders (
|
|
39
|
+
id SERIAL PRIMARY KEY,
|
|
40
|
+
customer_id INTEGER NOT NULL,
|
|
41
|
+
total NUMERIC(10,2) NOT NULL CHECK (total >= 0)
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
CREATE TABLE line_items (
|
|
45
|
+
id SERIAL PRIMARY KEY,
|
|
46
|
+
order_id INTEGER NOT NULL REFERENCES orders(id),
|
|
47
|
+
quantity INTEGER NOT NULL CHECK (quantity > 0),
|
|
48
|
+
price NUMERIC(10,2) NOT NULL CHECK (price > 0)
|
|
49
|
+
);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Write property tests with pytest:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from sqlproof import SqlProof, sqlproof
|
|
56
|
+
|
|
57
|
+
proof = SqlProof.from_schema_file("./schema.sql")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@sqlproof(proof, sizes={"orders": 20, "line_items": 50}, runs=50)
|
|
61
|
+
def test_no_orphan_line_items(db):
|
|
62
|
+
rows = db.query("""
|
|
63
|
+
SELECT li.id
|
|
64
|
+
FROM line_items li
|
|
65
|
+
LEFT JOIN orders o ON li.order_id = o.id
|
|
66
|
+
WHERE o.id IS NULL
|
|
67
|
+
""")
|
|
68
|
+
assert rows == []
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
SqlProof will:
|
|
72
|
+
|
|
73
|
+
1. Parse your schema (tables, columns, FKs, CHECK constraints, enums)
|
|
74
|
+
2. Topologically order tables by foreign-key dependencies
|
|
75
|
+
3. Generate datasets that respect common type, FK, CHECK, UNIQUE, and NOT NULL constraints
|
|
76
|
+
4. Run your property with Hypothesis-managed execution and shrinking
|
|
77
|
+
5. Save the shrunk counterexample as JSON when a property fails
|
|
78
|
+
|
|
79
|
+
## API
|
|
80
|
+
|
|
81
|
+
See the full API reference at [sqlproof.com](https://sqlproof.com).
|
|
82
|
+
|
|
83
|
+
### Quick reference
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
proof = SqlProof.from_schema_file("./schema.sql")
|
|
87
|
+
proof = SqlProof.from_connection_string("postgresql://localhost/postgres")
|
|
88
|
+
|
|
89
|
+
proof.check("name", sizes={"orders": 10}, property=lambda db: ...)
|
|
90
|
+
proof.invariant(
|
|
91
|
+
"no bad rows",
|
|
92
|
+
sizes={"orders": 10},
|
|
93
|
+
query="SELECT id FROM orders WHERE total < 0",
|
|
94
|
+
expect_empty=True,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
proof.disconnect()
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Schema Sources
|
|
101
|
+
|
|
102
|
+
**SQL file** — SqlProof parses `CREATE TABLE`, `CREATE TYPE ... AS ENUM`, foreign keys, CHECK constraints, UNIQUE constraints, and column types directly from `.sql` files.
|
|
103
|
+
|
|
104
|
+
**Connection string** — Pass a `postgresql://` URL and SqlProof introspects the live database via `information_schema` and `pg_catalog`.
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
proof = SqlProof.from_connection_string("postgresql://localhost:5432/mydb")
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Custom Column Generators
|
|
111
|
+
|
|
112
|
+
SqlProof maps PostgreSQL types to Hypothesis strategies and refines simple range,
|
|
113
|
+
`IN (...)`, length, FK, and unique constraints. The public customization API is present
|
|
114
|
+
for v0.1 and will grow with richer per-column strategy overrides.
|
|
115
|
+
|
|
116
|
+
### The `db` Client
|
|
117
|
+
|
|
118
|
+
The property function receives a `SqlProofClient`:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
rows = db.query("SELECT id, total FROM orders WHERE total >= %s", 0)
|
|
122
|
+
total = db.scalar("SELECT count(*) FROM orders")
|
|
123
|
+
typed = db.query_typed("SELECT id, total FROM orders", OrderRow)
|
|
124
|
+
data = db.get_generated_data()
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
- `query()` returns a list of dictionaries.
|
|
128
|
+
- `query_typed()` maps rows into `TypedDict`, dataclass, or Pydantic models.
|
|
129
|
+
- `get_generated_data()` returns the dataset for the current run.
|
|
130
|
+
|
|
131
|
+
## Failure Output
|
|
132
|
+
|
|
133
|
+
When a property fails, SqlProof throws with a formatted counterexample:
|
|
134
|
+
|
|
135
|
+
```text
|
|
136
|
+
Property failed: order totals match sum of line items
|
|
137
|
+
Failure: AssertionError: expected totals to match
|
|
138
|
+
Row context: {'order_id': 1}
|
|
139
|
+
Dataset shape: {'orders': {'rows': 1}, 'line_items': {'rows': 2}}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Counterexamples are written under `.sqlproof/failures/` and can be inspected with:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
sqlproof report .sqlproof/failures/test_name.json
|
|
146
|
+
sqlproof report .sqlproof/failures/test_name.json --format json
|
|
147
|
+
sqlproof replay .sqlproof/failures/test_name.json
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## How It Works
|
|
151
|
+
|
|
152
|
+
1. **Schema parsing** — Reads your SQL file (or introspects a live DB) to extract tables, columns, types, foreign keys, CHECK/UNIQUE constraints, and enums
|
|
153
|
+
|
|
154
|
+
2. **Dependency ordering** — Topologically sorts tables by foreign key references so parent rows are always inserted first
|
|
155
|
+
|
|
156
|
+
3. **Data generation** — Maps PostgreSQL types to Hypothesis strategies and applies constraint-aware generation for CHECK, UNIQUE, NOT NULL, and FK constraints
|
|
157
|
+
|
|
158
|
+
4. **Isolated execution** — Schema-file proofs run against an in-memory client for fast local feedback. DSN-backed proofs introspect PostgreSQL, insert generated data inside savepoints, run the property, then roll back the run.
|
|
159
|
+
|
|
160
|
+
5. **Shrinking** — When a property fails, Hypothesis shrinks the dataset to find the simplest counterexample
|
|
161
|
+
|
|
162
|
+
## Supported PostgreSQL Types
|
|
163
|
+
|
|
164
|
+
`integer`, `smallint`, `bigint`, `serial`, `bigserial`, `numeric(p,s)`, `real`, `double precision`, `boolean`, `text`, `varchar(n)`, `char(n)`, `uuid`, `timestamp`, `timestamptz`, `date`, `time`, `json`, `jsonb`, `bytea`, and custom `ENUM` types.
|
|
165
|
+
|
|
166
|
+
## Development
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
git clone https://github.com/your-org/sqlproof.git
|
|
170
|
+
cd sqlproof
|
|
171
|
+
uv sync --extra dev
|
|
172
|
+
|
|
173
|
+
uv run pytest
|
|
174
|
+
uv run ruff check src tests
|
|
175
|
+
uv run pyright src/sqlproof
|
|
176
|
+
uv run mypy src/sqlproof
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Postgres-backed tests
|
|
180
|
+
|
|
181
|
+
Integration tests are optional and read `SQLPROOF_TEST_DATABASE_URL`:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
SQLPROOF_TEST_DATABASE_URL='postgresql://postgres:postgres@127.0.0.1:5432/postgres' uv run pytest tests/integration
|
|
185
|
+
uv run pytest tests/benchmarks
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The integration tests create a temporary schema named `sqlproof_it_*` and drop it at the end.
|
|
189
|
+
|
|
190
|
+
### Why SqlProof tests itself with properties
|
|
191
|
+
|
|
192
|
+
SqlProof uses Hypothesis internally, and its own tests use properties for schema
|
|
193
|
+
fingerprinting, dependency ordering, FK validity, constraint generation, shrinking,
|
|
194
|
+
parser idempotence, and counterexample replay. This keeps the library honest about
|
|
195
|
+
the same invariants it asks users to write.
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
MIT
|