sqlrules 0.2.0__tar.gz → 0.3.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.3.0}/.gitignore +1 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/CHANGELOG.md +25 -0
- sqlrules-0.3.0/CONTRIBUTING.md +80 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/PKG-INFO +31 -4
- {sqlrules-0.2.0 → sqlrules-0.3.0}/README.md +30 -3
- {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/API.md +40 -3
- {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/COMPILER.md +6 -5
- {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/CONSTRAINTS.md +14 -3
- {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/DESIGN_DECISIONS.md +13 -11
- {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/DIALECT_SUPPORT.md +13 -9
- {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/ERRORS.md +22 -7
- {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/INTERNAL_API.md +9 -4
- {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/MILESTONES.md +1 -1
- sqlrules-0.3.0/docs/PLUGIN_SYSTEM.md +201 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/ROADMAP.md +13 -4
- {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/SPEC.md +17 -3
- {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/TESTING.md +14 -3
- {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/TRANSLATORS.md +27 -15
- {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/TYPE_SUPPORT.md +2 -2
- {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/index.md +3 -3
- sqlrules-0.3.0/packages/sqlrules-postgresql/README.md +16 -0
- sqlrules-0.3.0/packages/sqlrules-postgresql/pyproject.toml +28 -0
- sqlrules-0.3.0/packages/sqlrules-postgresql/src/sqlrules_postgresql/__init__.py +28 -0
- sqlrules-0.3.0/packages/sqlrules-postgresql/src/sqlrules_postgresql/pattern.py +16 -0
- sqlrules-0.3.0/packages/sqlrules-postgresql/tests/test_postgres_plugin.py +33 -0
- sqlrules-0.3.0/packages/sqlrules-sqlite/README.md +19 -0
- sqlrules-0.3.0/packages/sqlrules-sqlite/pyproject.toml +28 -0
- sqlrules-0.3.0/packages/sqlrules-sqlite/src/sqlrules_sqlite/__init__.py +33 -0
- sqlrules-0.3.0/packages/sqlrules-sqlite/src/sqlrules_sqlite/pattern.py +19 -0
- sqlrules-0.3.0/packages/sqlrules-sqlite/tests/test_sqlite_plugin.py +33 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/pyproject.toml +2 -1
- {sqlrules-0.2.0 → sqlrules-0.3.0}/src/sqlrules/__init__.py +6 -1
- {sqlrules-0.2.0 → sqlrules-0.3.0}/src/sqlrules/compiler.py +67 -2
- sqlrules-0.3.0/src/sqlrules/conformance.py +146 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/src/sqlrules/errors.py +22 -6
- {sqlrules-0.2.0 → sqlrules-0.3.0}/src/sqlrules/ir.py +5 -0
- sqlrules-0.3.0/src/sqlrules/plugins.py +57 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/src/sqlrules/translators.py +72 -5
- {sqlrules-0.2.0 → sqlrules-0.3.0}/tests/conftest.py +5 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/tests/test_errors.py +1 -1
- sqlrules-0.3.0/tests/test_plugins.py +364 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/tests/test_v02_features.py +2 -0
- sqlrules-0.2.0/CONTRIBUTING.md +0 -57
- sqlrules-0.2.0/docs/PLUGIN_SYSTEM.md +0 -252
- {sqlrules-0.2.0 → sqlrules-0.3.0}/LICENSE +0 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/benchmarks/__init__.py +0 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/benchmarks/bench_compile.py +0 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/ARCHITECTURE.md +0 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/CONTRIBUTING.md +0 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/NON_GOALS.md +0 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/PERFORMANCE.md +0 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/PHILOSOPHY.md +0 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/VISION.md +0 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/src/sqlrules/cache.py +0 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/src/sqlrules/columns.py +0 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/src/sqlrules/constraints.py +0 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/src/sqlrules/inspectors.py +0 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/src/sqlrules/py.typed +0 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/tests/test_correctness.py +0 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/tests/test_coverage_edges.py +0 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/tests/test_helpers.py +0 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/tests/test_literal_enum.py +0 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/tests/test_numeric_constraints.py +0 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/tests/test_string_constraints.py +0 -0
- {sqlrules-0.2.0 → sqlrules-0.3.0}/tests/test_v01_features.py +0 -0
|
@@ -5,6 +5,30 @@ 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.3.0] - 2026-07-10
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Versioned plugin API: `SQLRulesPlugin`, `PLUGIN_API_VERSION` (`"1"`),
|
|
13
|
+
and `PluginError`
|
|
14
|
+
- `Compiler(plugins=..., on_conflict=..., dialect=...)` for explicit
|
|
15
|
+
plugin registration (no auto-discovery)
|
|
16
|
+
- Expanded registry API: `register_constraint`, conflict policies
|
|
17
|
+
(`raise` / `replace` / `ignore`), `copy()`, `operators()`, and
|
|
18
|
+
`__contains__`
|
|
19
|
+
- `InvalidTranslatorError` raised for non-callable or wrong-arity
|
|
20
|
+
translators at registration time
|
|
21
|
+
- `Diagnostic.code` (e.g. `unsupported_constraint` on warn/ignore skips)
|
|
22
|
+
- Optional `CompilationContext.dialect` hint (never auto-detected)
|
|
23
|
+
- Plugin conformance helpers in `sqlrules.conformance`
|
|
24
|
+
- Official starter dialect packages:
|
|
25
|
+
- `sqlrules-postgresql` (`pattern` → `~`)
|
|
26
|
+
- `sqlrules-sqlite` (`pattern` → `REGEXP`)
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- Package version bumped to 0.3.0
|
|
31
|
+
|
|
8
32
|
## [0.2.0] - 2026-07-10
|
|
9
33
|
|
|
10
34
|
### Added
|
|
@@ -84,5 +108,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
84
108
|
- Docs clarify that `on_unsupported` does not soften unsupported types;
|
|
85
109
|
plugin / two-phase compile designs are marked as future
|
|
86
110
|
|
|
111
|
+
[0.3.0]: https://github.com/eddiethedean/sqlrules/releases/tag/v0.3.0
|
|
87
112
|
[0.2.0]: https://github.com/eddiethedean/sqlrules/releases/tag/v0.2.0
|
|
88
113
|
[0.1.0]: https://github.com/eddiethedean/sqlrules/releases/tag/v0.1.0
|
|
@@ -0,0 +1,80 @@
|
|
|
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
|
+
pre-commit install
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Checks
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
ruff check .
|
|
27
|
+
ruff format --check .
|
|
28
|
+
mypy src/sqlrules
|
|
29
|
+
pytest tests packages/sqlrules-postgresql/tests packages/sqlrules-sqlite/tests
|
|
30
|
+
python -m build && twine check dist/*
|
|
31
|
+
python -m build packages/sqlrules-postgresql --outdir dist-plugins
|
|
32
|
+
python -m build packages/sqlrules-sqlite --outdir dist-plugins
|
|
33
|
+
twine check dist-plugins/*
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
CI also verifies `pyproject.toml` / `__version__` sync and that the built
|
|
37
|
+
wheel imports cleanly.
|
|
38
|
+
|
|
39
|
+
## Plugins
|
|
40
|
+
|
|
41
|
+
- Declare `name`, `api_version` (`PLUGIN_API_VERSION`), and `register(registry)`.
|
|
42
|
+
- Prefer `register_constraint(..., on_conflict=...)`.
|
|
43
|
+
- Run `sqlrules.conformance.run_basic_conformance(plugin)`.
|
|
44
|
+
- Official dialect packages live under `packages/` and should track the
|
|
45
|
+
plugin API major (`sqlrules>=0.3,<0.4` while `PLUGIN_API_VERSION == "1"`).
|
|
46
|
+
|
|
47
|
+
## Pull requests
|
|
48
|
+
|
|
49
|
+
- Keep changes focused.
|
|
50
|
+
- Add or update tests for every constraint or error path you touch.
|
|
51
|
+
- Match existing code style and documentation tone.
|
|
52
|
+
|
|
53
|
+
## Releasing
|
|
54
|
+
|
|
55
|
+
Releases are published to PyPI by pushing a version tag. Before tagging:
|
|
56
|
+
|
|
57
|
+
1. Confirm `pyproject.toml` and `sqlrules.__version__` match the changelog.
|
|
58
|
+
2. Run the full check suite (`ruff`, `mypy`, `pytest`).
|
|
59
|
+
3. Ensure CI is green on `main`.
|
|
60
|
+
|
|
61
|
+
Then create and push the tag (example for 0.3.0):
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
git tag -a v0.3.0 -m "sqlrules 0.3.0"
|
|
65
|
+
git push origin v0.3.0
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The [release workflow](.github/workflows/release.yml) runs CI, verifies the
|
|
69
|
+
tag matches `pyproject.toml` / `__version__`, builds the sdist/wheel, and
|
|
70
|
+
publishes **core** `sqlrules` with `PYPI_API_TOKEN`.
|
|
71
|
+
|
|
72
|
+
Dialect plugin packages under `packages/` are versioned independently and
|
|
73
|
+
are **not** published by the core release workflow. Publish them separately
|
|
74
|
+
after core `0.3.0` is on PyPI (they depend on `sqlrules>=0.3,<0.4`):
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
python -m build packages/sqlrules-postgresql
|
|
78
|
+
python -m build packages/sqlrules-sqlite
|
|
79
|
+
twine upload dist-plugins/* # after building with --outdir dist-plugins
|
|
80
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlrules
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.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,22 @@ 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 # pattern → PostgreSQL ~
|
|
88
|
+
pip install sqlrules-sqlite # pattern → SQLite REGEXP
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from sqlrules import Compiler
|
|
93
|
+
from sqlrules_postgresql import PostgresPlugin
|
|
94
|
+
|
|
95
|
+
compiler = Compiler(plugins=[PostgresPlugin()], dialect="postgresql")
|
|
96
|
+
rules = compiler.compile(UserFilter, users)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Supported constraints (0.3)
|
|
85
100
|
|
|
86
101
|
| Constraint | SQLAlchemy expression |
|
|
87
102
|
|---|---|
|
|
@@ -92,6 +107,8 @@ Requires Python 3.10+, Pydantic v2, and SQLAlchemy 2.x.
|
|
|
92
107
|
| `Enum` | `column.in_(...)` |
|
|
93
108
|
|
|
94
109
|
`pattern` is extracted into IR but has no portable core translator.
|
|
110
|
+
Use a custom registry translator or a dialect plugin
|
|
111
|
+
(`sqlrules-postgresql`, `sqlrules-sqlite`).
|
|
95
112
|
|
|
96
113
|
Unsupported constraints raise `UnsupportedConstraintError` by default.
|
|
97
114
|
Use `on_unsupported="warn"` or `"ignore"` to change that policy for unknown
|
|
@@ -103,6 +120,14 @@ constraint operators (unsupported types always raise).
|
|
|
103
120
|
sqlrules.compile(model, table, *, column_map=None, on_unsupported="raise", cache=True)
|
|
104
121
|
sqlrules.where(rules) # flatten all expressions
|
|
105
122
|
sqlrules.flatten(rules) # alias of where()
|
|
123
|
+
|
|
124
|
+
compiler = sqlrules.Compiler(
|
|
125
|
+
plugins=[...], # optional SQLRulesPlugin instances
|
|
126
|
+
on_conflict="raise", # raise | replace | ignore
|
|
127
|
+
dialect=None, # optional hint for translators
|
|
128
|
+
on_unsupported="raise",
|
|
129
|
+
cache=True,
|
|
130
|
+
)
|
|
106
131
|
```
|
|
107
132
|
|
|
108
133
|
## Non-goals
|
|
@@ -115,13 +140,15 @@ constraints into SQLAlchemy expressions.
|
|
|
115
140
|
|
|
116
141
|
See [`docs/index.md`](docs/index.md) for the full documentation set,
|
|
117
142
|
including the [spec](docs/SPEC.md), [API](docs/API.md),
|
|
118
|
-
[
|
|
143
|
+
[plugin system](docs/PLUGIN_SYSTEM.md), [architecture](docs/ARCHITECTURE.md),
|
|
144
|
+
and [roadmap](docs/ROADMAP.md).
|
|
119
145
|
|
|
120
146
|
## Development
|
|
121
147
|
|
|
122
148
|
```bash
|
|
123
149
|
pip install -e ".[dev]"
|
|
124
|
-
|
|
150
|
+
pip install -e packages/sqlrules-postgresql -e packages/sqlrules-sqlite
|
|
151
|
+
pytest tests packages/sqlrules-postgresql/tests packages/sqlrules-sqlite/tests
|
|
125
152
|
ruff check .
|
|
126
153
|
mypy src/sqlrules
|
|
127
154
|
python -m benchmarks.bench_compile
|
|
@@ -41,7 +41,22 @@ 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 # pattern → PostgreSQL ~
|
|
48
|
+
pip install sqlrules-sqlite # pattern → SQLite REGEXP
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from sqlrules import Compiler
|
|
53
|
+
from sqlrules_postgresql import PostgresPlugin
|
|
54
|
+
|
|
55
|
+
compiler = Compiler(plugins=[PostgresPlugin()], dialect="postgresql")
|
|
56
|
+
rules = compiler.compile(UserFilter, users)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Supported constraints (0.3)
|
|
45
60
|
|
|
46
61
|
| Constraint | SQLAlchemy expression |
|
|
47
62
|
|---|---|
|
|
@@ -52,6 +67,8 @@ Requires Python 3.10+, Pydantic v2, and SQLAlchemy 2.x.
|
|
|
52
67
|
| `Enum` | `column.in_(...)` |
|
|
53
68
|
|
|
54
69
|
`pattern` is extracted into IR but has no portable core translator.
|
|
70
|
+
Use a custom registry translator or a dialect plugin
|
|
71
|
+
(`sqlrules-postgresql`, `sqlrules-sqlite`).
|
|
55
72
|
|
|
56
73
|
Unsupported constraints raise `UnsupportedConstraintError` by default.
|
|
57
74
|
Use `on_unsupported="warn"` or `"ignore"` to change that policy for unknown
|
|
@@ -63,6 +80,14 @@ constraint operators (unsupported types always raise).
|
|
|
63
80
|
sqlrules.compile(model, table, *, column_map=None, on_unsupported="raise", cache=True)
|
|
64
81
|
sqlrules.where(rules) # flatten all expressions
|
|
65
82
|
sqlrules.flatten(rules) # alias of where()
|
|
83
|
+
|
|
84
|
+
compiler = sqlrules.Compiler(
|
|
85
|
+
plugins=[...], # optional SQLRulesPlugin instances
|
|
86
|
+
on_conflict="raise", # raise | replace | ignore
|
|
87
|
+
dialect=None, # optional hint for translators
|
|
88
|
+
on_unsupported="raise",
|
|
89
|
+
cache=True,
|
|
90
|
+
)
|
|
66
91
|
```
|
|
67
92
|
|
|
68
93
|
## Non-goals
|
|
@@ -75,13 +100,15 @@ constraints into SQLAlchemy expressions.
|
|
|
75
100
|
|
|
76
101
|
See [`docs/index.md`](docs/index.md) for the full documentation set,
|
|
77
102
|
including the [spec](docs/SPEC.md), [API](docs/API.md),
|
|
78
|
-
[
|
|
103
|
+
[plugin system](docs/PLUGIN_SYSTEM.md), [architecture](docs/ARCHITECTURE.md),
|
|
104
|
+
and [roadmap](docs/ROADMAP.md).
|
|
79
105
|
|
|
80
106
|
## Development
|
|
81
107
|
|
|
82
108
|
```bash
|
|
83
109
|
pip install -e ".[dev]"
|
|
84
|
-
|
|
110
|
+
pip install -e packages/sqlrules-postgresql -e packages/sqlrules-sqlite
|
|
111
|
+
pytest tests packages/sqlrules-postgresql/tests packages/sqlrules-sqlite/tests
|
|
85
112
|
ruff check .
|
|
86
113
|
mypy src/sqlrules
|
|
87
114
|
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,11 +86,34 @@ 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
|
+
|
|
94
|
+
class MyPlugin:
|
|
95
|
+
name = "my-plugin"
|
|
96
|
+
api_version = PLUGIN_API_VERSION
|
|
97
|
+
|
|
98
|
+
def register(self, registry):
|
|
99
|
+
registry.register_constraint(
|
|
100
|
+
"pattern",
|
|
101
|
+
lambda c, col, ctx: col.op("~")(c.value),
|
|
102
|
+
on_conflict="replace",
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
compiler = Compiler(plugins=[MyPlugin()])
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
See [PLUGIN_SYSTEM.md](PLUGIN_SYSTEM.md). Conformance helpers live in
|
|
109
|
+
`sqlrules.conformance`.
|
|
110
|
+
|
|
76
111
|
## Other exports
|
|
77
112
|
|
|
78
113
|
Also exported for advanced use and typing:
|
|
79
114
|
|
|
80
115
|
- `__version__`
|
|
116
|
+
- `PLUGIN_API_VERSION`, `SQLRulesPlugin`
|
|
81
117
|
- `SQLRulesWarning`
|
|
82
118
|
- `CompilationContext`, `Constraint`, `FieldDescriptor`, `FieldIR`, `ModelIR`,
|
|
83
119
|
`Diagnostic` (IR helpers)
|
|
@@ -93,9 +129,10 @@ All public exceptions inherit from `SQLRulesError`:
|
|
|
93
129
|
- `MissingColumnError`
|
|
94
130
|
- `UnsupportedConstraintError`
|
|
95
131
|
- `TranslatorError`
|
|
96
|
-
- `InvalidTranslatorError`
|
|
132
|
+
- `InvalidTranslatorError`
|
|
97
133
|
- `RegistryError`
|
|
98
134
|
- `ConfigurationError`
|
|
99
|
-
- `
|
|
135
|
+
- `PluginError`
|
|
136
|
+
- `InternalCompilerError` (reserved)
|
|
100
137
|
|
|
101
138
|
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
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
| `multiple_of` | `column % value == 0` |
|
|
12
12
|
| `Literal[...]` | `column.in_(...)` |
|
|
13
13
|
| `Enum` | `column.in_(member values)` |
|
|
14
|
-
| `pattern` | IR
|
|
14
|
+
| `pattern` | IR in core; translators via plugins / custom registry |
|
|
15
15
|
|
|
16
16
|
Accepted input forms for the same semantics include `Field(...)`,
|
|
17
17
|
`annotated_types` primitives, `Interval`, `Len`, `conint`/`constr`, and
|
|
@@ -20,12 +20,23 @@ Accepted input forms for the same semantics include `Field(...)`,
|
|
|
20
20
|
Constraints without a deterministic SQL equivalent are rejected by default.
|
|
21
21
|
See [TYPE_SUPPORT.md](TYPE_SUPPORT.md) and [ERRORS.md](ERRORS.md).
|
|
22
22
|
|
|
23
|
-
To translate `pattern`, register a custom translator:
|
|
23
|
+
To translate `pattern`, use a dialect plugin or register a custom translator:
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from sqlrules import Compiler
|
|
27
|
+
from sqlrules_postgresql import PostgresPlugin
|
|
28
|
+
|
|
29
|
+
compiler = Compiler(plugins=[PostgresPlugin()], dialect="postgresql")
|
|
30
|
+
```
|
|
24
31
|
|
|
25
32
|
```python
|
|
26
33
|
from sqlrules.translators import default_registry
|
|
27
34
|
|
|
28
35
|
registry = default_registry()
|
|
29
|
-
registry.
|
|
36
|
+
registry.register_constraint(
|
|
37
|
+
"pattern",
|
|
38
|
+
lambda c, col, ctx: col.op("~")(c.value),
|
|
39
|
+
on_conflict="replace",
|
|
40
|
+
)
|
|
30
41
|
compiler = sqlrules.Compiler(registry=registry)
|
|
31
42
|
```
|
|
@@ -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
|
|
|
@@ -36,8 +36,8 @@ Core emits portable SQLAlchemy Core expressions. The table below describes
|
|
|
36
36
|
|
|
37
37
|
Dialect Status Notes
|
|
38
38
|
------------ -------------------- --------------------------------
|
|
39
|
-
SQLite Core
|
|
40
|
-
PostgreSQL Core
|
|
39
|
+
SQLite Core + plugin `sqlrules-sqlite` adds `pattern` (REGEXP)
|
|
40
|
+
PostgreSQL Core + plugin `sqlrules-postgresql` adds `pattern` (`~`)
|
|
41
41
|
MySQL Core (planned) Enhanced features via future plugin
|
|
42
42
|
MariaDB Core (planned) Follows MySQL where practical
|
|
43
43
|
SQL Server Core (planned) Enhanced features via future plugin
|
|
@@ -63,31 +63,35 @@ These should compile identically across supported databases:
|
|
|
63
63
|
|
|
64
64
|
# PostgreSQL
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
Package: `sqlrules-postgresql`
|
|
67
|
+
|
|
68
|
+
0.3 enhancement:
|
|
69
|
+
|
|
70
|
+
- Native regular expressions via `pattern` → `column ~ value`
|
|
71
|
+
|
|
72
|
+
Potential later enhancements (0.4+):
|
|
67
73
|
|
|
68
|
-
- Native regular expressions
|
|
69
74
|
- ARRAY operators
|
|
70
75
|
- JSONB operators
|
|
71
76
|
- Range types
|
|
72
77
|
- Full-text search
|
|
73
78
|
|
|
74
|
-
Suggested package:
|
|
75
|
-
|
|
76
|
-
sqlrules-postgresql
|
|
77
|
-
|
|
78
79
|
------------------------------------------------------------------------
|
|
79
80
|
|
|
80
81
|
# SQLite
|
|
81
82
|
|
|
83
|
+
Package: `sqlrules-sqlite`
|
|
84
|
+
|
|
82
85
|
Goals:
|
|
83
86
|
|
|
84
87
|
- Reliable test platform
|
|
85
88
|
- Portable numeric comparisons
|
|
86
89
|
- Portable string-length operations
|
|
90
|
+
- `pattern` via `REGEXP` (application must enable REGEXP on the connection)
|
|
87
91
|
|
|
88
92
|
Limitations:
|
|
89
93
|
|
|
90
|
-
- REGEXP requires an extension
|
|
94
|
+
- REGEXP requires an extension / `create_function`
|
|
91
95
|
- Limited advanced JSON functionality
|
|
92
96
|
|
|
93
97
|
------------------------------------------------------------------------
|
|
@@ -25,10 +25,11 @@ SQLRulesError
|
|
|
25
25
|
├── MissingColumnError
|
|
26
26
|
├── UnsupportedConstraintError
|
|
27
27
|
├── TranslatorError
|
|
28
|
-
├── InvalidTranslatorError
|
|
28
|
+
├── InvalidTranslatorError
|
|
29
29
|
├── RegistryError
|
|
30
30
|
├── ConfigurationError
|
|
31
|
-
|
|
31
|
+
├── PluginError
|
|
32
|
+
└── InternalCompilerError (reserved)
|
|
32
33
|
```
|
|
33
34
|
|
|
34
35
|
All public exceptions inherit from `SQLRulesError`.
|
|
@@ -89,8 +90,9 @@ expression (unexpected SQLAlchemy errors wrapped by the registry).
|
|
|
89
90
|
|
|
90
91
|
# InvalidTranslatorError
|
|
91
92
|
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
Raised when registering a translator that is not callable or does not
|
|
94
|
+
accept at least three positional parameters (`constraint`, `column`,
|
|
95
|
+
`context`). Variadic `*args` translators are accepted.
|
|
94
96
|
|
|
95
97
|
------------------------------------------------------------------------
|
|
96
98
|
|
|
@@ -110,7 +112,14 @@ Missing operators raise `UnsupportedConstraintError`, not `RegistryError`.
|
|
|
110
112
|
|
|
111
113
|
Raised when compiler configuration is inconsistent.
|
|
112
114
|
|
|
113
|
-
|
|
115
|
+
Raised for an invalid `on_unsupported` or `on_conflict` mode.
|
|
116
|
+
|
|
117
|
+
------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
# PluginError
|
|
120
|
+
|
|
121
|
+
Raised when a plugin fails validation (missing `name` / `register`, or
|
|
122
|
+
`api_version` mismatch with `PLUGIN_API_VERSION`).
|
|
114
123
|
|
|
115
124
|
------------------------------------------------------------------------
|
|
116
125
|
|
|
@@ -151,11 +160,17 @@ After `compile` / `bind` / `compile_model`, inspect skipped constraints:
|
|
|
151
160
|
compiler = sqlrules.Compiler(on_unsupported="warn")
|
|
152
161
|
rules = compiler.compile(Model, table)
|
|
153
162
|
for diag in compiler.diagnostics:
|
|
154
|
-
print(diag.severity, diag.field, diag.operator, diag.message)
|
|
163
|
+
print(diag.code, diag.severity, diag.field, diag.operator, diag.message)
|
|
155
164
|
```
|
|
156
165
|
|
|
157
166
|
`Diagnostic` fields: `severity` (`"warning"` | `"info"`), `field`,
|
|
158
|
-
`operator`, `value`, `message`.
|
|
167
|
+
`operator`, `value`, `message`, `code`.
|
|
168
|
+
|
|
169
|
+
Stable diagnostic codes (0.3):
|
|
170
|
+
|
|
171
|
+
| Code | When |
|
|
172
|
+
|---|---|
|
|
173
|
+
| `unsupported_constraint` | Operator skipped under `warn` / `ignore` |
|
|
159
174
|
|
|
160
175
|
`compile_model` clears diagnostics. Diagnostics are separate from
|
|
161
176
|
exceptions and do not change the rules dict return type.
|
|
@@ -145,16 +145,19 @@ Suggested methods:
|
|
|
145
145
|
|
|
146
146
|
``` python
|
|
147
147
|
register(...)
|
|
148
|
+
register_constraint(..., on_conflict=...)
|
|
148
149
|
lookup(...)
|
|
149
150
|
translate(...)
|
|
151
|
+
operators()
|
|
152
|
+
copy()
|
|
150
153
|
```
|
|
151
154
|
|
|
152
155
|
Registry responsibilities:
|
|
153
156
|
|
|
154
157
|
- operator lookup
|
|
155
|
-
-
|
|
156
|
-
-
|
|
157
|
-
-
|
|
158
|
+
- conflict detection (`raise` / `replace` / `ignore`)
|
|
159
|
+
- translator validation
|
|
160
|
+
- hosting plugin registrations (via `Compiler(plugins=...)`)
|
|
158
161
|
|
|
159
162
|
------------------------------------------------------------------------
|
|
160
163
|
|
|
@@ -189,6 +192,7 @@ Suggested contents:
|
|
|
189
192
|
|
|
190
193
|
- compiler options (`on_unsupported`)
|
|
191
194
|
- diagnostics collector
|
|
195
|
+
- optional `dialect` hint (explicit; never auto-detected)
|
|
192
196
|
|
|
193
197
|
Avoid global state.
|
|
194
198
|
|
|
@@ -202,7 +206,8 @@ Internal diagnostics record:
|
|
|
202
206
|
- ignored skips (`severity="info"`)
|
|
203
207
|
|
|
204
208
|
Exposed after compile via `Compiler.diagnostics`. Diagnostics are
|
|
205
|
-
separate from exceptions.
|
|
209
|
+
separate from exceptions. Each diagnostic may include a stable `code`
|
|
210
|
+
(for example `unsupported_constraint`).
|
|
206
211
|
|
|
207
212
|
------------------------------------------------------------------------
|
|
208
213
|
|