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.
Files changed (86) hide show
  1. {sqlrules-0.2.0 → sqlrules-0.4.0}/.gitignore +1 -0
  2. {sqlrules-0.2.0 → sqlrules-0.4.0}/CHANGELOG.md +50 -0
  3. sqlrules-0.4.0/CONTRIBUTING.md +86 -0
  4. {sqlrules-0.2.0 → sqlrules-0.4.0}/PKG-INFO +44 -5
  5. {sqlrules-0.2.0 → sqlrules-0.4.0}/README.md +43 -4
  6. {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/API.md +45 -4
  7. {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/COMPILER.md +6 -5
  8. sqlrules-0.4.0/docs/CONSTRAINTS.md +55 -0
  9. {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/DESIGN_DECISIONS.md +13 -11
  10. {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/DIALECT_SUPPORT.md +43 -55
  11. {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/ERRORS.md +22 -7
  12. {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/INTERNAL_API.md +9 -4
  13. {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/MILESTONES.md +11 -5
  14. sqlrules-0.4.0/docs/PLUGIN_SYSTEM.md +210 -0
  15. {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/ROADMAP.md +14 -4
  16. {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/SPEC.md +22 -5
  17. {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/TESTING.md +14 -3
  18. {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/TRANSLATORS.md +33 -16
  19. {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/TYPE_SUPPORT.md +11 -9
  20. {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/index.md +3 -3
  21. sqlrules-0.4.0/packages/sqlrules-mssql/README.md +37 -0
  22. sqlrules-0.4.0/packages/sqlrules-mssql/pyproject.toml +28 -0
  23. sqlrules-0.4.0/packages/sqlrules-mssql/src/sqlrules_mssql/__init__.py +52 -0
  24. sqlrules-0.4.0/packages/sqlrules-mssql/src/sqlrules_mssql/json.py +70 -0
  25. sqlrules-0.4.0/packages/sqlrules-mssql/src/sqlrules_mssql/length.py +26 -0
  26. sqlrules-0.4.0/packages/sqlrules-mssql/tests/test_mssql_plugin.py +52 -0
  27. sqlrules-0.4.0/packages/sqlrules-mysql/README.md +36 -0
  28. sqlrules-0.4.0/packages/sqlrules-mysql/pyproject.toml +28 -0
  29. sqlrules-0.4.0/packages/sqlrules-mysql/src/sqlrules_mysql/__init__.py +39 -0
  30. sqlrules-0.4.0/packages/sqlrules-mysql/src/sqlrules_mysql/fulltext.py +20 -0
  31. sqlrules-0.4.0/packages/sqlrules-mysql/src/sqlrules_mysql/json.py +35 -0
  32. sqlrules-0.4.0/packages/sqlrules-mysql/src/sqlrules_mysql/pattern.py +22 -0
  33. sqlrules-0.4.0/packages/sqlrules-mysql/tests/test_mysql_plugin.py +45 -0
  34. sqlrules-0.4.0/packages/sqlrules-postgresql/README.md +53 -0
  35. sqlrules-0.4.0/packages/sqlrules-postgresql/pyproject.toml +28 -0
  36. sqlrules-0.4.0/packages/sqlrules-postgresql/src/sqlrules_postgresql/__init__.py +46 -0
  37. sqlrules-0.4.0/packages/sqlrules-postgresql/src/sqlrules_postgresql/array.py +25 -0
  38. sqlrules-0.4.0/packages/sqlrules-postgresql/src/sqlrules_postgresql/jsonb.py +25 -0
  39. sqlrules-0.4.0/packages/sqlrules-postgresql/src/sqlrules_postgresql/pattern.py +19 -0
  40. sqlrules-0.4.0/packages/sqlrules-postgresql/src/sqlrules_postgresql/range.py +25 -0
  41. sqlrules-0.4.0/packages/sqlrules-postgresql/tests/test_postgres_plugin.py +88 -0
  42. sqlrules-0.4.0/packages/sqlrules-sqlite/README.md +53 -0
  43. sqlrules-0.4.0/packages/sqlrules-sqlite/pyproject.toml +28 -0
  44. sqlrules-0.4.0/packages/sqlrules-sqlite/src/sqlrules_sqlite/__init__.py +48 -0
  45. sqlrules-0.4.0/packages/sqlrules-sqlite/src/sqlrules_sqlite/json.py +58 -0
  46. sqlrules-0.4.0/packages/sqlrules-sqlite/src/sqlrules_sqlite/pattern.py +25 -0
  47. sqlrules-0.4.0/packages/sqlrules-sqlite/src/sqlrules_sqlite/regexp.py +30 -0
  48. sqlrules-0.4.0/packages/sqlrules-sqlite/tests/test_sqlite_plugin.py +72 -0
  49. {sqlrules-0.2.0 → sqlrules-0.4.0}/pyproject.toml +2 -1
  50. {sqlrules-0.2.0 → sqlrules-0.4.0}/src/sqlrules/__init__.py +26 -1
  51. {sqlrules-0.2.0 → sqlrules-0.4.0}/src/sqlrules/compiler.py +67 -2
  52. sqlrules-0.4.0/src/sqlrules/conformance.py +160 -0
  53. {sqlrules-0.2.0 → sqlrules-0.4.0}/src/sqlrules/constraints.py +61 -11
  54. {sqlrules-0.2.0 → sqlrules-0.4.0}/src/sqlrules/errors.py +22 -6
  55. {sqlrules-0.2.0 → sqlrules-0.4.0}/src/sqlrules/ir.py +13 -0
  56. sqlrules-0.4.0/src/sqlrules/markers.py +86 -0
  57. sqlrules-0.4.0/src/sqlrules/plugins.py +57 -0
  58. {sqlrules-0.2.0 → sqlrules-0.4.0}/src/sqlrules/translators.py +72 -5
  59. {sqlrules-0.2.0 → sqlrules-0.4.0}/tests/conftest.py +5 -0
  60. {sqlrules-0.2.0 → sqlrules-0.4.0}/tests/test_correctness.py +12 -3
  61. {sqlrules-0.2.0 → sqlrules-0.4.0}/tests/test_errors.py +1 -1
  62. sqlrules-0.4.0/tests/test_plugins.py +365 -0
  63. {sqlrules-0.2.0 → sqlrules-0.4.0}/tests/test_v01_features.py +2 -2
  64. {sqlrules-0.2.0 → sqlrules-0.4.0}/tests/test_v02_features.py +26 -6
  65. sqlrules-0.4.0/tests/test_v04_markers.py +101 -0
  66. sqlrules-0.2.0/CONTRIBUTING.md +0 -57
  67. sqlrules-0.2.0/docs/CONSTRAINTS.md +0 -31
  68. sqlrules-0.2.0/docs/PLUGIN_SYSTEM.md +0 -252
  69. {sqlrules-0.2.0 → sqlrules-0.4.0}/LICENSE +0 -0
  70. {sqlrules-0.2.0 → sqlrules-0.4.0}/benchmarks/__init__.py +0 -0
  71. {sqlrules-0.2.0 → sqlrules-0.4.0}/benchmarks/bench_compile.py +0 -0
  72. {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/ARCHITECTURE.md +0 -0
  73. {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/CONTRIBUTING.md +0 -0
  74. {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/NON_GOALS.md +0 -0
  75. {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/PERFORMANCE.md +0 -0
  76. {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/PHILOSOPHY.md +0 -0
  77. {sqlrules-0.2.0 → sqlrules-0.4.0}/docs/VISION.md +0 -0
  78. {sqlrules-0.2.0 → sqlrules-0.4.0}/src/sqlrules/cache.py +0 -0
  79. {sqlrules-0.2.0 → sqlrules-0.4.0}/src/sqlrules/columns.py +0 -0
  80. {sqlrules-0.2.0 → sqlrules-0.4.0}/src/sqlrules/inspectors.py +0 -0
  81. {sqlrules-0.2.0 → sqlrules-0.4.0}/src/sqlrules/py.typed +0 -0
  82. {sqlrules-0.2.0 → sqlrules-0.4.0}/tests/test_coverage_edges.py +0 -0
  83. {sqlrules-0.2.0 → sqlrules-0.4.0}/tests/test_helpers.py +0 -0
  84. {sqlrules-0.2.0 → sqlrules-0.4.0}/tests/test_literal_enum.py +0 -0
  85. {sqlrules-0.2.0 → sqlrules-0.4.0}/tests/test_numeric_constraints.py +0 -0
  86. {sqlrules-0.2.0 → sqlrules-0.4.0}/tests/test_string_constraints.py +0 -0
@@ -10,6 +10,7 @@ __pycache__/
10
10
  .coverage.*
11
11
  htmlcov/
12
12
  dist/
13
+ dist-plugins/
13
14
  build/
14
15
  *.egg-info/
15
16
  .DS_Store
@@ -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.2.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
- ## Supported constraints (0.2)
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 translator.
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
- [architecture](docs/ARCHITECTURE.md), and [roadmap](docs/ROADMAP.md).
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
- pytest
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
- ## Supported constraints (0.2)
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 translator.
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
- [architecture](docs/ARCHITECTURE.md), and [roadmap](docs/ROADMAP.md).
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
- pytest
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 metadata cache.
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` (IR helpers)
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` (reserved; not raised in 0.2)
136
+ - `InvalidTranslatorError`
97
137
  - `RegistryError`
98
138
  - `ConfigurationError`
99
- - `InternalCompilerError` (reserved; not raised in 0.2)
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
- (including a `pattern` translator).
148
+ - Register versioned plugins via `Compiler(plugins=[...])`
149
+ - Override operators with `register_constraint(..., on_conflict=...)`
150
+ - Optional `dialect=` hint on `CompilationContext`
149
151
 
150
- Future plugin hooks:
152
+ Reserved for later:
151
153
 
152
- - Register dialect-specific translators via a plugin API.
153
- - Override built-in mappings through versioned plugins.
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 (Future)
234
+ # Decision 9: Plugins Are Explicit
235
235
 
236
236
  ## Decision
237
237
 
238
- SQLRules 0.2 does **not** ship a plugin API. Customization today is limited
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
- A future release may add explicit plugin registration (for example
243
- `Compiler(plugins=[...])`) without automatic discovery.
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
- Do not treat plugin examples in design docs as available in 0.2. See
253
- [PLUGIN_SYSTEM.md](PLUGIN_SYSTEM.md) for the planned design.
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 custom `TranslatorRegistry` today, dialect
446
- plugins in 0.3+
447
+ - Translator support via `TranslatorRegistry` and dialect plugins
448
+ (`sqlrules-postgresql`, `sqlrules-sqlite`)
447
449
 
448
450
  ------------------------------------------------------------------------
449
451