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.
Files changed (65) hide show
  1. {sqlrules-0.2.0 → sqlrules-0.3.0}/.gitignore +1 -0
  2. {sqlrules-0.2.0 → sqlrules-0.3.0}/CHANGELOG.md +25 -0
  3. sqlrules-0.3.0/CONTRIBUTING.md +80 -0
  4. {sqlrules-0.2.0 → sqlrules-0.3.0}/PKG-INFO +31 -4
  5. {sqlrules-0.2.0 → sqlrules-0.3.0}/README.md +30 -3
  6. {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/API.md +40 -3
  7. {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/COMPILER.md +6 -5
  8. {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/CONSTRAINTS.md +14 -3
  9. {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/DESIGN_DECISIONS.md +13 -11
  10. {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/DIALECT_SUPPORT.md +13 -9
  11. {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/ERRORS.md +22 -7
  12. {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/INTERNAL_API.md +9 -4
  13. {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/MILESTONES.md +1 -1
  14. sqlrules-0.3.0/docs/PLUGIN_SYSTEM.md +201 -0
  15. {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/ROADMAP.md +13 -4
  16. {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/SPEC.md +17 -3
  17. {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/TESTING.md +14 -3
  18. {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/TRANSLATORS.md +27 -15
  19. {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/TYPE_SUPPORT.md +2 -2
  20. {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/index.md +3 -3
  21. sqlrules-0.3.0/packages/sqlrules-postgresql/README.md +16 -0
  22. sqlrules-0.3.0/packages/sqlrules-postgresql/pyproject.toml +28 -0
  23. sqlrules-0.3.0/packages/sqlrules-postgresql/src/sqlrules_postgresql/__init__.py +28 -0
  24. sqlrules-0.3.0/packages/sqlrules-postgresql/src/sqlrules_postgresql/pattern.py +16 -0
  25. sqlrules-0.3.0/packages/sqlrules-postgresql/tests/test_postgres_plugin.py +33 -0
  26. sqlrules-0.3.0/packages/sqlrules-sqlite/README.md +19 -0
  27. sqlrules-0.3.0/packages/sqlrules-sqlite/pyproject.toml +28 -0
  28. sqlrules-0.3.0/packages/sqlrules-sqlite/src/sqlrules_sqlite/__init__.py +33 -0
  29. sqlrules-0.3.0/packages/sqlrules-sqlite/src/sqlrules_sqlite/pattern.py +19 -0
  30. sqlrules-0.3.0/packages/sqlrules-sqlite/tests/test_sqlite_plugin.py +33 -0
  31. {sqlrules-0.2.0 → sqlrules-0.3.0}/pyproject.toml +2 -1
  32. {sqlrules-0.2.0 → sqlrules-0.3.0}/src/sqlrules/__init__.py +6 -1
  33. {sqlrules-0.2.0 → sqlrules-0.3.0}/src/sqlrules/compiler.py +67 -2
  34. sqlrules-0.3.0/src/sqlrules/conformance.py +146 -0
  35. {sqlrules-0.2.0 → sqlrules-0.3.0}/src/sqlrules/errors.py +22 -6
  36. {sqlrules-0.2.0 → sqlrules-0.3.0}/src/sqlrules/ir.py +5 -0
  37. sqlrules-0.3.0/src/sqlrules/plugins.py +57 -0
  38. {sqlrules-0.2.0 → sqlrules-0.3.0}/src/sqlrules/translators.py +72 -5
  39. {sqlrules-0.2.0 → sqlrules-0.3.0}/tests/conftest.py +5 -0
  40. {sqlrules-0.2.0 → sqlrules-0.3.0}/tests/test_errors.py +1 -1
  41. sqlrules-0.3.0/tests/test_plugins.py +364 -0
  42. {sqlrules-0.2.0 → sqlrules-0.3.0}/tests/test_v02_features.py +2 -0
  43. sqlrules-0.2.0/CONTRIBUTING.md +0 -57
  44. sqlrules-0.2.0/docs/PLUGIN_SYSTEM.md +0 -252
  45. {sqlrules-0.2.0 → sqlrules-0.3.0}/LICENSE +0 -0
  46. {sqlrules-0.2.0 → sqlrules-0.3.0}/benchmarks/__init__.py +0 -0
  47. {sqlrules-0.2.0 → sqlrules-0.3.0}/benchmarks/bench_compile.py +0 -0
  48. {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/ARCHITECTURE.md +0 -0
  49. {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/CONTRIBUTING.md +0 -0
  50. {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/NON_GOALS.md +0 -0
  51. {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/PERFORMANCE.md +0 -0
  52. {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/PHILOSOPHY.md +0 -0
  53. {sqlrules-0.2.0 → sqlrules-0.3.0}/docs/VISION.md +0 -0
  54. {sqlrules-0.2.0 → sqlrules-0.3.0}/src/sqlrules/cache.py +0 -0
  55. {sqlrules-0.2.0 → sqlrules-0.3.0}/src/sqlrules/columns.py +0 -0
  56. {sqlrules-0.2.0 → sqlrules-0.3.0}/src/sqlrules/constraints.py +0 -0
  57. {sqlrules-0.2.0 → sqlrules-0.3.0}/src/sqlrules/inspectors.py +0 -0
  58. {sqlrules-0.2.0 → sqlrules-0.3.0}/src/sqlrules/py.typed +0 -0
  59. {sqlrules-0.2.0 → sqlrules-0.3.0}/tests/test_correctness.py +0 -0
  60. {sqlrules-0.2.0 → sqlrules-0.3.0}/tests/test_coverage_edges.py +0 -0
  61. {sqlrules-0.2.0 → sqlrules-0.3.0}/tests/test_helpers.py +0 -0
  62. {sqlrules-0.2.0 → sqlrules-0.3.0}/tests/test_literal_enum.py +0 -0
  63. {sqlrules-0.2.0 → sqlrules-0.3.0}/tests/test_numeric_constraints.py +0 -0
  64. {sqlrules-0.2.0 → sqlrules-0.3.0}/tests/test_string_constraints.py +0 -0
  65. {sqlrules-0.2.0 → sqlrules-0.3.0}/tests/test_v01_features.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,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.2.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
- ## Supported constraints (0.2)
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
- [architecture](docs/ARCHITECTURE.md), and [roadmap](docs/ROADMAP.md).
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
- pytest
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
- ## Supported constraints (0.2)
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
- [architecture](docs/ARCHITECTURE.md), and [roadmap](docs/ROADMAP.md).
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
- pytest
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 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,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` (reserved; not raised in 0.2)
132
+ - `InvalidTranslatorError`
97
133
  - `RegistryError`
98
134
  - `ConfigurationError`
99
- - `InternalCompilerError` (reserved; not raised in 0.2)
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
- (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
 
@@ -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 only in 0.2 (no portable core translator) |
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.register("pattern", lambda c, col, ctx: col.op("~")(c.value))
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 (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
 
@@ -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 (dev) Primary local/CI smoke target
40
- PostgreSQL Core (planned) Enhanced features via future plugin
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
- Potential enhancements:
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 (reserved in 0.2)
28
+ ├── InvalidTranslatorError
29
29
  ├── RegistryError
30
30
  ├── ConfigurationError
31
- └── InternalCompilerError (reserved in 0.2)
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
- Reserved for future validation of custom translators. Not raised by the
93
- 0.2 compiler path.
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
- In 0.2 this is raised for an invalid `on_unsupported` mode only.
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
- - dialect overrides
156
- - plugin registration
157
- - conflict detection
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
 
@@ -93,7 +93,7 @@ Improve correctness and extensibility.
93
93
 
94
94
  ------------------------------------------------------------------------
95
95
 
96
- # 0.3.0 --- Plugin System
96
+ # 0.3.0 --- Plugin System
97
97
 
98
98
  ## Goals
99
99