sqlrules 0.2.0__tar.gz → 0.4.0__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.
- {sqlrules-0.2.0 → sqlrules-0.4.0}/.gitignore +1 -0
- {sqlrules-0.2.0 → sqlrules-0.4.0}/CHANGELOG.md +50 -0
- sqlrules-0.4.0/CONTRIBUTING.md +86 -0
- {sqlrules-0.2.0 → sqlrules-0.4.0}/PKG-INFO +44 -5
- {sqlrules-0.2.0 → sqlrules-0.4.0}/README.md +43 -4
- {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/API.md +45 -4
- {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/COMPILER.md +6 -5
- sqlrules-0.4.0/docs/CONSTRAINTS.md +55 -0
- {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/DESIGN_DECISIONS.md +13 -11
- {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/DIALECT_SUPPORT.md +43 -55
- {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/ERRORS.md +22 -7
- {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/INTERNAL_API.md +9 -4
- {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/MILESTONES.md +11 -5
- sqlrules-0.4.0/docs/PLUGIN_SYSTEM.md +210 -0
- {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/ROADMAP.md +14 -4
- {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/SPEC.md +22 -5
- {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/TESTING.md +14 -3
- {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/TRANSLATORS.md +33 -16
- {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/TYPE_SUPPORT.md +11 -9
- {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/index.md +3 -3
- sqlrules-0.4.0/packages/sqlrules-mssql/README.md +37 -0
- sqlrules-0.4.0/packages/sqlrules-mssql/pyproject.toml +28 -0
- sqlrules-0.4.0/packages/sqlrules-mssql/src/sqlrules_mssql/__init__.py +52 -0
- sqlrules-0.4.0/packages/sqlrules-mssql/src/sqlrules_mssql/json.py +70 -0
- sqlrules-0.4.0/packages/sqlrules-mssql/src/sqlrules_mssql/length.py +26 -0
- sqlrules-0.4.0/packages/sqlrules-mssql/tests/test_mssql_plugin.py +52 -0
- sqlrules-0.4.0/packages/sqlrules-mysql/README.md +36 -0
- sqlrules-0.4.0/packages/sqlrules-mysql/pyproject.toml +28 -0
- sqlrules-0.4.0/packages/sqlrules-mysql/src/sqlrules_mysql/__init__.py +39 -0
- sqlrules-0.4.0/packages/sqlrules-mysql/src/sqlrules_mysql/fulltext.py +20 -0
- sqlrules-0.4.0/packages/sqlrules-mysql/src/sqlrules_mysql/json.py +35 -0
- sqlrules-0.4.0/packages/sqlrules-mysql/src/sqlrules_mysql/pattern.py +22 -0
- sqlrules-0.4.0/packages/sqlrules-mysql/tests/test_mysql_plugin.py +45 -0
- sqlrules-0.4.0/packages/sqlrules-postgresql/README.md +53 -0
- sqlrules-0.4.0/packages/sqlrules-postgresql/pyproject.toml +28 -0
- sqlrules-0.4.0/packages/sqlrules-postgresql/src/sqlrules_postgresql/__init__.py +46 -0
- sqlrules-0.4.0/packages/sqlrules-postgresql/src/sqlrules_postgresql/array.py +25 -0
- sqlrules-0.4.0/packages/sqlrules-postgresql/src/sqlrules_postgresql/jsonb.py +25 -0
- sqlrules-0.4.0/packages/sqlrules-postgresql/src/sqlrules_postgresql/pattern.py +19 -0
- sqlrules-0.4.0/packages/sqlrules-postgresql/src/sqlrules_postgresql/range.py +25 -0
- sqlrules-0.4.0/packages/sqlrules-postgresql/tests/test_postgres_plugin.py +88 -0
- sqlrules-0.4.0/packages/sqlrules-sqlite/README.md +53 -0
- sqlrules-0.4.0/packages/sqlrules-sqlite/pyproject.toml +28 -0
- sqlrules-0.4.0/packages/sqlrules-sqlite/src/sqlrules_sqlite/__init__.py +48 -0
- sqlrules-0.4.0/packages/sqlrules-sqlite/src/sqlrules_sqlite/json.py +58 -0
- sqlrules-0.4.0/packages/sqlrules-sqlite/src/sqlrules_sqlite/pattern.py +25 -0
- sqlrules-0.4.0/packages/sqlrules-sqlite/src/sqlrules_sqlite/regexp.py +30 -0
- sqlrules-0.4.0/packages/sqlrules-sqlite/tests/test_sqlite_plugin.py +72 -0
- {sqlrules-0.2.0 → sqlrules-0.4.0}/pyproject.toml +2 -1
- {sqlrules-0.2.0 → sqlrules-0.4.0}/src/sqlrules/__init__.py +26 -1
- {sqlrules-0.2.0 → sqlrules-0.4.0}/src/sqlrules/compiler.py +67 -2
- sqlrules-0.4.0/src/sqlrules/conformance.py +160 -0
- {sqlrules-0.2.0 → sqlrules-0.4.0}/src/sqlrules/constraints.py +61 -11
- {sqlrules-0.2.0 → sqlrules-0.4.0}/src/sqlrules/errors.py +22 -6
- {sqlrules-0.2.0 → sqlrules-0.4.0}/src/sqlrules/ir.py +13 -0
- sqlrules-0.4.0/src/sqlrules/markers.py +86 -0
- sqlrules-0.4.0/src/sqlrules/plugins.py +57 -0
- {sqlrules-0.2.0 → sqlrules-0.4.0}/src/sqlrules/translators.py +72 -5
- {sqlrules-0.2.0 → sqlrules-0.4.0}/tests/conftest.py +5 -0
- {sqlrules-0.2.0 → sqlrules-0.4.0}/tests/test_correctness.py +12 -3
- {sqlrules-0.2.0 → sqlrules-0.4.0}/tests/test_errors.py +1 -1
- sqlrules-0.4.0/tests/test_plugins.py +365 -0
- {sqlrules-0.2.0 → sqlrules-0.4.0}/tests/test_v01_features.py +2 -2
- {sqlrules-0.2.0 → sqlrules-0.4.0}/tests/test_v02_features.py +26 -6
- sqlrules-0.4.0/tests/test_v04_markers.py +101 -0
- sqlrules-0.2.0/CONTRIBUTING.md +0 -57
- sqlrules-0.2.0/docs/CONSTRAINTS.md +0 -31
- sqlrules-0.2.0/docs/PLUGIN_SYSTEM.md +0 -252
- {sqlrules-0.2.0 → sqlrules-0.4.0}/LICENSE +0 -0
- {sqlrules-0.2.0 → sqlrules-0.4.0}/benchmarks/__init__.py +0 -0
- {sqlrules-0.2.0 → sqlrules-0.4.0}/benchmarks/bench_compile.py +0 -0
- {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/ARCHITECTURE.md +0 -0
- {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/CONTRIBUTING.md +0 -0
- {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/NON_GOALS.md +0 -0
- {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/PERFORMANCE.md +0 -0
- {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/PHILOSOPHY.md +0 -0
- {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/VISION.md +0 -0
- {sqlrules-0.2.0 → sqlrules-0.4.0}/src/sqlrules/cache.py +0 -0
- {sqlrules-0.2.0 → sqlrules-0.4.0}/src/sqlrules/columns.py +0 -0
- {sqlrules-0.2.0 → sqlrules-0.4.0}/src/sqlrules/inspectors.py +0 -0
- {sqlrules-0.2.0 → sqlrules-0.4.0}/src/sqlrules/py.typed +0 -0
- {sqlrules-0.2.0 → sqlrules-0.4.0}/tests/test_coverage_edges.py +0 -0
- {sqlrules-0.2.0 → sqlrules-0.4.0}/tests/test_helpers.py +0 -0
- {sqlrules-0.2.0 → sqlrules-0.4.0}/tests/test_literal_enum.py +0 -0
- {sqlrules-0.2.0 → sqlrules-0.4.0}/tests/test_numeric_constraints.py +0 -0
- {sqlrules-0.2.0 → sqlrules-0.4.0}/tests/test_string_constraints.py +0 -0
|
@@ -5,6 +5,54 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0/).
|
|
7
7
|
|
|
8
|
+
## [0.4.0] - 2026-07-10
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `ConstraintMarker` protocol and shared markers in `sqlrules.markers`:
|
|
13
|
+
`JsonContains`, `JsonHasKey`, `ArrayContains`, `ArrayOverlap`,
|
|
14
|
+
`RangeContains`, `RangeOverlap`, `FullTextMatch`
|
|
15
|
+
- `PatternSpec` IR value for `pattern` (preserves `ignore_case` from
|
|
16
|
+
`re.Pattern` flags); `pattern_text()` helper for plugins
|
|
17
|
+
- `list` / `dict` annotations for marker-driven fields (portable
|
|
18
|
+
constraints on containers still raise)
|
|
19
|
+
- Expanded dialect plugins:
|
|
20
|
+
- `sqlrules-postgresql` — `~` / `~*`, JSONB, ARRAY, range operators
|
|
21
|
+
- `sqlrules-sqlite` — `register_regexp()`, flag-aware REGEXP, JSON helpers
|
|
22
|
+
- `sqlrules-mysql` — REGEXP, JSON, `fulltext_match`
|
|
23
|
+
- `sqlrules-mssql` — JSON helpers, `LEN` overrides for length constraints
|
|
24
|
+
- Conformance helpers accept optional `model` / `table` / `field` for
|
|
25
|
+
non-`pattern` operators
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- Package version bumped to 0.4.0
|
|
30
|
+
- Official plugin packages depend on `sqlrules>=0.4,<0.5`
|
|
31
|
+
|
|
32
|
+
## [0.3.0] - 2026-07-10
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- Versioned plugin API: `SQLRulesPlugin`, `PLUGIN_API_VERSION` (`"1"`),
|
|
37
|
+
and `PluginError`
|
|
38
|
+
- `Compiler(plugins=..., on_conflict=..., dialect=...)` for explicit
|
|
39
|
+
plugin registration (no auto-discovery)
|
|
40
|
+
- Expanded registry API: `register_constraint`, conflict policies
|
|
41
|
+
(`raise` / `replace` / `ignore`), `copy()`, `operators()`, and
|
|
42
|
+
`__contains__`
|
|
43
|
+
- `InvalidTranslatorError` raised for non-callable or wrong-arity
|
|
44
|
+
translators at registration time
|
|
45
|
+
- `Diagnostic.code` (e.g. `unsupported_constraint` on warn/ignore skips)
|
|
46
|
+
- Optional `CompilationContext.dialect` hint (never auto-detected)
|
|
47
|
+
- Plugin conformance helpers in `sqlrules.conformance`
|
|
48
|
+
- Official starter dialect packages:
|
|
49
|
+
- `sqlrules-postgresql` (`pattern` → `~`)
|
|
50
|
+
- `sqlrules-sqlite` (`pattern` → `REGEXP`)
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
|
|
54
|
+
- Package version bumped to 0.3.0
|
|
55
|
+
|
|
8
56
|
## [0.2.0] - 2026-07-10
|
|
9
57
|
|
|
10
58
|
### Added
|
|
@@ -84,5 +132,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
84
132
|
- Docs clarify that `on_unsupported` does not soften unsupported types;
|
|
85
133
|
plugin / two-phase compile designs are marked as future
|
|
86
134
|
|
|
135
|
+
[0.4.0]: https://github.com/eddiethedean/sqlrules/releases/tag/v0.4.0
|
|
136
|
+
[0.3.0]: https://github.com/eddiethedean/sqlrules/releases/tag/v0.3.0
|
|
87
137
|
[0.2.0]: https://github.com/eddiethedean/sqlrules/releases/tag/v0.2.0
|
|
88
138
|
[0.1.0]: https://github.com/eddiethedean/sqlrules/releases/tag/v0.1.0
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for helping improve SQLRules.
|
|
4
|
+
|
|
5
|
+
## Principles
|
|
6
|
+
|
|
7
|
+
- Keep the public API minimal.
|
|
8
|
+
- Every feature must map directly to SQLAlchemy expressions.
|
|
9
|
+
- Unsupported Pydantic features should not be approximated.
|
|
10
|
+
- Prefer fail-fast behavior over silent semantic changes.
|
|
11
|
+
- Update relevant docs before or alongside implementation.
|
|
12
|
+
|
|
13
|
+
## Development setup
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
python -m venv .venv
|
|
17
|
+
source .venv/bin/activate
|
|
18
|
+
pip install -e ".[dev]"
|
|
19
|
+
pip install -e packages/sqlrules-postgresql -e packages/sqlrules-sqlite \
|
|
20
|
+
-e packages/sqlrules-mysql -e packages/sqlrules-mssql
|
|
21
|
+
pre-commit install
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Checks
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
ruff check .
|
|
28
|
+
ruff format --check .
|
|
29
|
+
mypy src/sqlrules
|
|
30
|
+
pytest tests packages/sqlrules-postgresql/tests packages/sqlrules-sqlite/tests \
|
|
31
|
+
packages/sqlrules-mysql/tests packages/sqlrules-mssql/tests
|
|
32
|
+
python -m build && twine check dist/*
|
|
33
|
+
python -m build packages/sqlrules-postgresql --outdir dist-plugins
|
|
34
|
+
python -m build packages/sqlrules-sqlite --outdir dist-plugins
|
|
35
|
+
python -m build packages/sqlrules-mysql --outdir dist-plugins
|
|
36
|
+
python -m build packages/sqlrules-mssql --outdir dist-plugins
|
|
37
|
+
twine check dist-plugins/*
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
CI also verifies `pyproject.toml` / `__version__` sync and that the built
|
|
41
|
+
wheel imports cleanly.
|
|
42
|
+
|
|
43
|
+
## Plugins
|
|
44
|
+
|
|
45
|
+
- Declare `name`, `api_version` (`PLUGIN_API_VERSION`), and `register(registry)`.
|
|
46
|
+
- Prefer `register_constraint(..., on_conflict=...)`.
|
|
47
|
+
- Run `sqlrules.conformance.run_basic_conformance(plugin)`.
|
|
48
|
+
- Official dialect packages live under `packages/` and should track the
|
|
49
|
+
core minor (`sqlrules>=0.4,<0.5` while `PLUGIN_API_VERSION == "1"`).
|
|
50
|
+
|
|
51
|
+
## Pull requests
|
|
52
|
+
|
|
53
|
+
- Keep changes focused.
|
|
54
|
+
- Add or update tests for every constraint or error path you touch.
|
|
55
|
+
- Match existing code style and documentation tone.
|
|
56
|
+
|
|
57
|
+
## Releasing
|
|
58
|
+
|
|
59
|
+
Releases are published to PyPI by pushing a version tag. Before tagging:
|
|
60
|
+
|
|
61
|
+
1. Confirm `pyproject.toml` and `sqlrules.__version__` match the changelog.
|
|
62
|
+
2. Run the full check suite (`ruff`, `mypy`, `pytest`).
|
|
63
|
+
3. Ensure CI is green on `main`.
|
|
64
|
+
|
|
65
|
+
Then create and push the tag (example for 0.4.0):
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
git tag -a v0.4.0 -m "sqlrules 0.4.0"
|
|
69
|
+
git push origin v0.4.0
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The [release workflow](.github/workflows/release.yml) runs CI, verifies the
|
|
73
|
+
tag matches `pyproject.toml` / `__version__`, builds the sdist/wheel, and
|
|
74
|
+
publishes **core** `sqlrules` with `PYPI_API_TOKEN`.
|
|
75
|
+
|
|
76
|
+
Dialect plugin packages under `packages/` are versioned independently and
|
|
77
|
+
are **not** published by the core release workflow. Publish them separately
|
|
78
|
+
after core `0.4.0` is on PyPI (they depend on `sqlrules>=0.4,<0.5`):
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
python -m build packages/sqlrules-postgresql --outdir dist-plugins
|
|
82
|
+
python -m build packages/sqlrules-sqlite --outdir dist-plugins
|
|
83
|
+
python -m build packages/sqlrules-mysql --outdir dist-plugins
|
|
84
|
+
python -m build packages/sqlrules-mssql --outdir dist-plugins
|
|
85
|
+
twine upload dist-plugins/*
|
|
86
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlrules
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Compile constrained Pydantic models into SQLAlchemy WHERE-rule dictionaries.
|
|
5
5
|
Project-URL: Homepage, https://github.com/eddiethedean/sqlrules
|
|
6
6
|
Project-URL: Documentation, https://github.com/eddiethedean/sqlrules#readme
|
|
@@ -81,7 +81,30 @@ pip install sqlrules
|
|
|
81
81
|
|
|
82
82
|
Requires Python 3.10+, Pydantic v2, and SQLAlchemy 2.x.
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
Optional dialect plugins:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pip install sqlrules-postgresql # ~ / ~*, JSONB, ARRAY, range
|
|
88
|
+
pip install sqlrules-sqlite # REGEXP helper + JSON
|
|
89
|
+
pip install sqlrules-mysql # REGEXP, JSON, full-text
|
|
90
|
+
pip install sqlrules-mssql # JSON + LEN string ops
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from typing import Annotated, Any
|
|
95
|
+
from pydantic import BaseModel, Field
|
|
96
|
+
from sqlrules import Compiler, JsonContains
|
|
97
|
+
from sqlrules_postgresql import PostgresPlugin
|
|
98
|
+
|
|
99
|
+
class RowFilter(BaseModel):
|
|
100
|
+
name: Annotated[str, Field(pattern=r"^A")]
|
|
101
|
+
meta: Annotated[dict[str, Any], JsonContains({"active": True})]
|
|
102
|
+
|
|
103
|
+
compiler = Compiler(plugins=[PostgresPlugin()], dialect="postgresql")
|
|
104
|
+
rules = compiler.compile(RowFilter, table)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Supported constraints (0.4)
|
|
85
108
|
|
|
86
109
|
| Constraint | SQLAlchemy expression |
|
|
87
110
|
|---|---|
|
|
@@ -91,7 +114,11 @@ Requires Python 3.10+, Pydantic v2, and SQLAlchemy 2.x.
|
|
|
91
114
|
| `Literal[...]` | `column.in_(...)` |
|
|
92
115
|
| `Enum` | `column.in_(...)` |
|
|
93
116
|
|
|
94
|
-
`pattern` is extracted into IR but has no portable core
|
|
117
|
+
`pattern` is extracted into IR (`PatternSpec`) but has no portable core
|
|
118
|
+
translator. Use a dialect plugin or a custom registry translator.
|
|
119
|
+
|
|
120
|
+
Dialect markers (`JsonContains`, `ArrayContains`, `RangeContains`,
|
|
121
|
+
`FullTextMatch`, …) live in `sqlrules.markers` and require a dialect plugin.
|
|
95
122
|
|
|
96
123
|
Unsupported constraints raise `UnsupportedConstraintError` by default.
|
|
97
124
|
Use `on_unsupported="warn"` or `"ignore"` to change that policy for unknown
|
|
@@ -103,6 +130,14 @@ constraint operators (unsupported types always raise).
|
|
|
103
130
|
sqlrules.compile(model, table, *, column_map=None, on_unsupported="raise", cache=True)
|
|
104
131
|
sqlrules.where(rules) # flatten all expressions
|
|
105
132
|
sqlrules.flatten(rules) # alias of where()
|
|
133
|
+
|
|
134
|
+
compiler = sqlrules.Compiler(
|
|
135
|
+
plugins=[...], # optional SQLRulesPlugin instances
|
|
136
|
+
on_conflict="raise", # raise | replace | ignore
|
|
137
|
+
dialect=None, # optional hint for translators
|
|
138
|
+
on_unsupported="raise",
|
|
139
|
+
cache=True,
|
|
140
|
+
)
|
|
106
141
|
```
|
|
107
142
|
|
|
108
143
|
## Non-goals
|
|
@@ -115,13 +150,17 @@ constraints into SQLAlchemy expressions.
|
|
|
115
150
|
|
|
116
151
|
See [`docs/index.md`](docs/index.md) for the full documentation set,
|
|
117
152
|
including the [spec](docs/SPEC.md), [API](docs/API.md),
|
|
118
|
-
[
|
|
153
|
+
[plugin system](docs/PLUGIN_SYSTEM.md), [architecture](docs/ARCHITECTURE.md),
|
|
154
|
+
and [roadmap](docs/ROADMAP.md).
|
|
119
155
|
|
|
120
156
|
## Development
|
|
121
157
|
|
|
122
158
|
```bash
|
|
123
159
|
pip install -e ".[dev]"
|
|
124
|
-
|
|
160
|
+
pip install -e packages/sqlrules-postgresql -e packages/sqlrules-sqlite \
|
|
161
|
+
-e packages/sqlrules-mysql -e packages/sqlrules-mssql
|
|
162
|
+
pytest tests packages/sqlrules-postgresql/tests packages/sqlrules-sqlite/tests \
|
|
163
|
+
packages/sqlrules-mysql/tests packages/sqlrules-mssql/tests
|
|
125
164
|
ruff check .
|
|
126
165
|
mypy src/sqlrules
|
|
127
166
|
python -m benchmarks.bench_compile
|
|
@@ -41,7 +41,30 @@ pip install sqlrules
|
|
|
41
41
|
|
|
42
42
|
Requires Python 3.10+, Pydantic v2, and SQLAlchemy 2.x.
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
Optional dialect plugins:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install sqlrules-postgresql # ~ / ~*, JSONB, ARRAY, range
|
|
48
|
+
pip install sqlrules-sqlite # REGEXP helper + JSON
|
|
49
|
+
pip install sqlrules-mysql # REGEXP, JSON, full-text
|
|
50
|
+
pip install sqlrules-mssql # JSON + LEN string ops
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from typing import Annotated, Any
|
|
55
|
+
from pydantic import BaseModel, Field
|
|
56
|
+
from sqlrules import Compiler, JsonContains
|
|
57
|
+
from sqlrules_postgresql import PostgresPlugin
|
|
58
|
+
|
|
59
|
+
class RowFilter(BaseModel):
|
|
60
|
+
name: Annotated[str, Field(pattern=r"^A")]
|
|
61
|
+
meta: Annotated[dict[str, Any], JsonContains({"active": True})]
|
|
62
|
+
|
|
63
|
+
compiler = Compiler(plugins=[PostgresPlugin()], dialect="postgresql")
|
|
64
|
+
rules = compiler.compile(RowFilter, table)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Supported constraints (0.4)
|
|
45
68
|
|
|
46
69
|
| Constraint | SQLAlchemy expression |
|
|
47
70
|
|---|---|
|
|
@@ -51,7 +74,11 @@ Requires Python 3.10+, Pydantic v2, and SQLAlchemy 2.x.
|
|
|
51
74
|
| `Literal[...]` | `column.in_(...)` |
|
|
52
75
|
| `Enum` | `column.in_(...)` |
|
|
53
76
|
|
|
54
|
-
`pattern` is extracted into IR but has no portable core
|
|
77
|
+
`pattern` is extracted into IR (`PatternSpec`) but has no portable core
|
|
78
|
+
translator. Use a dialect plugin or a custom registry translator.
|
|
79
|
+
|
|
80
|
+
Dialect markers (`JsonContains`, `ArrayContains`, `RangeContains`,
|
|
81
|
+
`FullTextMatch`, …) live in `sqlrules.markers` and require a dialect plugin.
|
|
55
82
|
|
|
56
83
|
Unsupported constraints raise `UnsupportedConstraintError` by default.
|
|
57
84
|
Use `on_unsupported="warn"` or `"ignore"` to change that policy for unknown
|
|
@@ -63,6 +90,14 @@ constraint operators (unsupported types always raise).
|
|
|
63
90
|
sqlrules.compile(model, table, *, column_map=None, on_unsupported="raise", cache=True)
|
|
64
91
|
sqlrules.where(rules) # flatten all expressions
|
|
65
92
|
sqlrules.flatten(rules) # alias of where()
|
|
93
|
+
|
|
94
|
+
compiler = sqlrules.Compiler(
|
|
95
|
+
plugins=[...], # optional SQLRulesPlugin instances
|
|
96
|
+
on_conflict="raise", # raise | replace | ignore
|
|
97
|
+
dialect=None, # optional hint for translators
|
|
98
|
+
on_unsupported="raise",
|
|
99
|
+
cache=True,
|
|
100
|
+
)
|
|
66
101
|
```
|
|
67
102
|
|
|
68
103
|
## Non-goals
|
|
@@ -75,13 +110,17 @@ constraints into SQLAlchemy expressions.
|
|
|
75
110
|
|
|
76
111
|
See [`docs/index.md`](docs/index.md) for the full documentation set,
|
|
77
112
|
including the [spec](docs/SPEC.md), [API](docs/API.md),
|
|
78
|
-
[
|
|
113
|
+
[plugin system](docs/PLUGIN_SYSTEM.md), [architecture](docs/ARCHITECTURE.md),
|
|
114
|
+
and [roadmap](docs/ROADMAP.md).
|
|
79
115
|
|
|
80
116
|
## Development
|
|
81
117
|
|
|
82
118
|
```bash
|
|
83
119
|
pip install -e ".[dev]"
|
|
84
|
-
|
|
120
|
+
pip install -e packages/sqlrules-postgresql -e packages/sqlrules-sqlite \
|
|
121
|
+
-e packages/sqlrules-mysql -e packages/sqlrules-mssql
|
|
122
|
+
pytest tests packages/sqlrules-postgresql/tests packages/sqlrules-sqlite/tests \
|
|
123
|
+
packages/sqlrules-mysql/tests packages/sqlrules-mssql/tests
|
|
85
124
|
ruff check .
|
|
86
125
|
mypy src/sqlrules
|
|
87
126
|
python -m benchmarks.bench_compile
|
|
@@ -31,6 +31,8 @@ binding. Unconstrained fields are omitted and do not require a column.
|
|
|
31
31
|
|
|
32
32
|
Unsupported types always raise, regardless of `on_unsupported`.
|
|
33
33
|
|
|
34
|
+
Module-level `compile` does not accept plugins; use `Compiler` for extensions.
|
|
35
|
+
|
|
34
36
|
## `where` / `flatten`
|
|
35
37
|
|
|
36
38
|
```python
|
|
@@ -49,6 +51,9 @@ Flatten a rule dictionary into a single list of expressions suitable for
|
|
|
49
51
|
compiler = sqlrules.Compiler(
|
|
50
52
|
on_unsupported="raise",
|
|
51
53
|
registry=None,
|
|
54
|
+
plugins=None,
|
|
55
|
+
on_conflict="raise",
|
|
56
|
+
dialect=None,
|
|
52
57
|
cache=True,
|
|
53
58
|
model_cache=None, # optional shared ModelIRCache; default is process-wide
|
|
54
59
|
)
|
|
@@ -62,8 +67,16 @@ rules = compiler.bind(model_ir, table, column_map=None)
|
|
|
62
67
|
compiler.diagnostics
|
|
63
68
|
```
|
|
64
69
|
|
|
70
|
+
| Parameter | Description |
|
|
71
|
+
|---|---|
|
|
72
|
+
| `plugins` | Optional sequence of `SQLRulesPlugin` objects registered at init |
|
|
73
|
+
| `on_conflict` | Default conflict policy for plugin `register()`: `"raise"`, `"replace"`, or `"ignore"` |
|
|
74
|
+
| `dialect` | Optional string hint stored on `CompilationContext` (never auto-detected) |
|
|
75
|
+
| `registry` | Optional base `TranslatorRegistry`; copied when `plugins` is non-empty |
|
|
76
|
+
|
|
65
77
|
Reusable compiler instance with a fixed unsupported-constraint policy,
|
|
66
|
-
optional custom translator registry, and optional Phase-1
|
|
78
|
+
optional plugins, optional custom translator registry, and optional Phase-1
|
|
79
|
+
metadata cache.
|
|
67
80
|
|
|
68
81
|
`compile_model` / `bind` separate static IR extraction from table binding so
|
|
69
82
|
the same `ModelIR` can be reused across tables.
|
|
@@ -73,14 +86,41 @@ on the same `Compiler` instance (diagnostics are per-instance and not locked).
|
|
|
73
86
|
The shared Phase-1 IR cache is thread-safe for concurrent reads/writes across
|
|
74
87
|
instances.
|
|
75
88
|
|
|
89
|
+
## Plugins
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from sqlrules import PLUGIN_API_VERSION, Compiler
|
|
93
|
+
from sqlrules.constraints import pattern_text
|
|
94
|
+
|
|
95
|
+
class MyPlugin:
|
|
96
|
+
name = "my-plugin"
|
|
97
|
+
api_version = PLUGIN_API_VERSION
|
|
98
|
+
|
|
99
|
+
def register(self, registry):
|
|
100
|
+
registry.register_constraint(
|
|
101
|
+
"pattern",
|
|
102
|
+
lambda c, col, ctx: col.op("~")(pattern_text(c.value)[0]),
|
|
103
|
+
on_conflict="replace",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
compiler = Compiler(plugins=[MyPlugin()])
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Dialect markers (`JsonContains`, `ArrayContains`, …) are extracted into IR
|
|
110
|
+
and translated by official dialect plugins. See [PLUGIN_SYSTEM.md](PLUGIN_SYSTEM.md).
|
|
111
|
+
Conformance helpers live in `sqlrules.conformance`.
|
|
112
|
+
|
|
76
113
|
## Other exports
|
|
77
114
|
|
|
78
115
|
Also exported for advanced use and typing:
|
|
79
116
|
|
|
80
117
|
- `__version__`
|
|
118
|
+
- `PLUGIN_API_VERSION`, `SQLRulesPlugin`
|
|
81
119
|
- `SQLRulesWarning`
|
|
82
120
|
- `CompilationContext`, `Constraint`, `FieldDescriptor`, `FieldIR`, `ModelIR`,
|
|
83
|
-
`Diagnostic`
|
|
121
|
+
`Diagnostic`, `PatternSpec`
|
|
122
|
+
- Markers: `ConstraintMarker`, `JsonContains`, `JsonHasKey`, `ArrayContains`,
|
|
123
|
+
`ArrayOverlap`, `RangeContains`, `RangeOverlap`, `FullTextMatch`
|
|
84
124
|
|
|
85
125
|
`DiagnosticsCollector` and `ModelIRCache` are internal (import from
|
|
86
126
|
`sqlrules.ir` / `sqlrules.cache` only if you accept instability).
|
|
@@ -93,9 +133,10 @@ All public exceptions inherit from `SQLRulesError`:
|
|
|
93
133
|
- `MissingColumnError`
|
|
94
134
|
- `UnsupportedConstraintError`
|
|
95
135
|
- `TranslatorError`
|
|
96
|
-
- `InvalidTranslatorError`
|
|
136
|
+
- `InvalidTranslatorError`
|
|
97
137
|
- `RegistryError`
|
|
98
138
|
- `ConfigurationError`
|
|
99
|
-
- `
|
|
139
|
+
- `PluginError`
|
|
140
|
+
- `InternalCompilerError` (reserved)
|
|
100
141
|
|
|
101
142
|
See [ERRORS.md](ERRORS.md) for details.
|
|
@@ -145,13 +145,14 @@ Translation remains deterministic.
|
|
|
145
145
|
Available today:
|
|
146
146
|
|
|
147
147
|
- Inject a custom `TranslatorRegistry` via `Compiler(registry=...)`
|
|
148
|
-
|
|
148
|
+
- Register versioned plugins via `Compiler(plugins=[...])`
|
|
149
|
+
- Override operators with `register_constraint(..., on_conflict=...)`
|
|
150
|
+
- Optional `dialect=` hint on `CompilationContext`
|
|
149
151
|
|
|
150
|
-
|
|
152
|
+
Reserved for later:
|
|
151
153
|
|
|
152
|
-
-
|
|
153
|
-
-
|
|
154
|
-
- Add new IR transforms.
|
|
154
|
+
- Compiler pass plugins
|
|
155
|
+
- Entry-point / auto-discovery
|
|
155
156
|
|
|
156
157
|
## Design Principles
|
|
157
158
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Supported Constraint Mapping
|
|
2
|
+
|
|
3
|
+
| Constraint | SQLAlchemy expression |
|
|
4
|
+
|---|---|
|
|
5
|
+
| `gt` | `column > value` |
|
|
6
|
+
| `ge` | `column >= value` |
|
|
7
|
+
| `lt` | `column < value` |
|
|
8
|
+
| `le` | `column <= value` |
|
|
9
|
+
| `min_length` | `func.length(column) >= value` |
|
|
10
|
+
| `max_length` | `func.length(column) <= value` |
|
|
11
|
+
| `multiple_of` | `column % value == 0` |
|
|
12
|
+
| `Literal[...]` | `column.in_(...)` |
|
|
13
|
+
| `Enum` | `column.in_(member values)` |
|
|
14
|
+
| `pattern` | IR (`PatternSpec`); translators via plugins / custom registry |
|
|
15
|
+
|
|
16
|
+
## Dialect markers (`sqlrules.markers`)
|
|
17
|
+
|
|
18
|
+
| Marker | IR operator | Typical plugin translation |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| `JsonContains` | `json_contains` | JSONB / JSON containment |
|
|
21
|
+
| `JsonHasKey` | `json_has_key` | JSON key existence |
|
|
22
|
+
| `ArrayContains` | `array_contains` | PostgreSQL array containment |
|
|
23
|
+
| `ArrayOverlap` | `array_overlap` | PostgreSQL array overlap |
|
|
24
|
+
| `RangeContains` | `range_contains` | PostgreSQL range `@>` |
|
|
25
|
+
| `RangeOverlap` | `range_overlap` | PostgreSQL range `&&` |
|
|
26
|
+
| `FullTextMatch` | `fulltext_match` | MySQL `MATCH ... AGAINST` |
|
|
27
|
+
|
|
28
|
+
Accepted input forms for portable constraints include `Field(...)`,
|
|
29
|
+
`annotated_types` primitives, `Interval`, `Len`, `conint`/`constr`, and
|
|
30
|
+
`StringConstraints` (length attributes; `pattern` becomes `PatternSpec` IR).
|
|
31
|
+
|
|
32
|
+
Constraints without a deterministic SQL equivalent are rejected by default.
|
|
33
|
+
See [TYPE_SUPPORT.md](TYPE_SUPPORT.md) and [ERRORS.md](ERRORS.md).
|
|
34
|
+
|
|
35
|
+
To translate `pattern` or markers, use a dialect plugin:
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from sqlrules import Compiler, JsonContains
|
|
39
|
+
from sqlrules_postgresql import PostgresPlugin
|
|
40
|
+
|
|
41
|
+
compiler = Compiler(plugins=[PostgresPlugin()], dialect="postgresql")
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from sqlrules.constraints import pattern_text
|
|
46
|
+
from sqlrules.translators import default_registry
|
|
47
|
+
|
|
48
|
+
registry = default_registry()
|
|
49
|
+
registry.register_constraint(
|
|
50
|
+
"pattern",
|
|
51
|
+
lambda c, col, ctx: col.op("~")(pattern_text(c.value)[0]),
|
|
52
|
+
on_conflict="replace",
|
|
53
|
+
)
|
|
54
|
+
compiler = sqlrules.Compiler(registry=registry)
|
|
55
|
+
```
|
|
@@ -231,16 +231,18 @@ SQL Server, or Oracle plugins.
|
|
|
231
231
|
|
|
232
232
|
------------------------------------------------------------------------
|
|
233
233
|
|
|
234
|
-
# Decision 9: Plugins Are Explicit
|
|
234
|
+
# Decision 9: Plugins Are Explicit
|
|
235
235
|
|
|
236
236
|
## Decision
|
|
237
237
|
|
|
238
|
-
SQLRules 0.
|
|
239
|
-
to injecting a `TranslatorRegistry` into `Compiler(registry=...)` (for
|
|
240
|
-
example a custom `pattern` translator).
|
|
238
|
+
SQLRules 0.3 ships an explicit plugin API:
|
|
241
239
|
|
|
242
|
-
|
|
243
|
-
|
|
240
|
+
```python
|
|
241
|
+
Compiler(plugins=[PostgresPlugin()], on_conflict="raise", dialect="postgresql")
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
There is no automatic discovery. Plugins declare `api_version` matching
|
|
245
|
+
`PLUGIN_API_VERSION`.
|
|
244
246
|
|
|
245
247
|
## Rationale
|
|
246
248
|
|
|
@@ -249,8 +251,8 @@ reproducibility, and keeps the compiler deterministic.
|
|
|
249
251
|
|
|
250
252
|
## Consequences
|
|
251
253
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
+
See [PLUGIN_SYSTEM.md](PLUGIN_SYSTEM.md). Official starter packages:
|
|
255
|
+
`sqlrules-postgresql` and `sqlrules-sqlite`.
|
|
254
256
|
|
|
255
257
|
------------------------------------------------------------------------
|
|
256
258
|
|
|
@@ -438,12 +440,12 @@ This may improve ergonomics while preserving dictionary output.
|
|
|
438
440
|
|
|
439
441
|
## Should Regex Be Core or Dialect Plugin?
|
|
440
442
|
|
|
441
|
-
**Decided for 0.2:**
|
|
443
|
+
**Decided for 0.2 / 0.3:**
|
|
442
444
|
|
|
443
445
|
- IR support in core (`Constraint(operator="pattern", ...)`)
|
|
444
446
|
- No portable core translator
|
|
445
|
-
- Translator support via
|
|
446
|
-
|
|
447
|
+
- Translator support via `TranslatorRegistry` and dialect plugins
|
|
448
|
+
(`sqlrules-postgresql`, `sqlrules-sqlite`)
|
|
447
449
|
|
|
448
450
|
------------------------------------------------------------------------
|
|
449
451
|
|