Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
b6fc008
fix(types): judge a type modifier when it is rendered, not when it is…
vistart Oct 11, 2026
6b3c063
refactor(dialect): read the field, not a guess at it
vistart Oct 11, 2026
9146a0c
feat(expression): typed column clauses, MATCH as a field, and gates o…
vistart Oct 11, 2026
8cd2f9d
fix(dialect): make constraint modifiers opt-in and gate constraint na…
vistart Oct 12, 2026
1033f1d
refactor(sqlite): converge the column mixin onto the core answers
vistart Oct 13, 2026
e003863
docs(expression): codify when a construct is a clause and when it is …
vistart Oct 13, 2026
68ce905
refactor(query-cte): make the WITH clause a node its own formatter re…
vistart Oct 13, 2026
85338f4
refactor(dialect): inline the IN value-list rendering into format_in_…
vistart Oct 13, 2026
683a183
refactor(query-cte)!: collapse the CTE family to two nodes on executa…
vistart Oct 13, 2026
380a5ca
refactor(sqlxml): make the PASSING clause a node with a public formatter
vistart Oct 13, 2026
101f71a
refactor(dialect)!: read declared fields instead of probing for attri…
vistart Oct 13, 2026
34e1fb4
refactor(expression)!: rename statement-level nodes to *Statement
vistart Oct 13, 2026
83c3305
docs: carry the statement renames into README, skills and experiments
vistart Oct 13, 2026
93ad24b
refactor(dialect)!: finish the statement formatter names
vistart Oct 13, 2026
890aaaa
refactor(expression)!: name the predicate, clause and match nodes by …
vistart Oct 13, 2026
a5b593b
test(feature-ddl): record the constraint SQL baseline before the S3 r…
vistart Oct 13, 2026
8448dfb
refactor(constraints)!: split constraint nodes into clause + branch l…
vistart Oct 13, 2026
70ed002
test(constraints): migrate the suite to the two-level constraint nodes
vistart Oct 13, 2026
e5e0689
ci: point testsuite at the matching branch
vistart Oct 13, 2026
5dc64fd
refactor(expressions)!: rename value and function families per the na…
vistart Oct 13, 2026
61a1a45
feat(wildcards)!: split the wildcard node by grammar position per spe…
vistart Oct 13, 2026
ba05ec7
fix(field): resolve ColumnBase at runtime in UseColumnType
vistart Oct 14, 2026
1c98924
test(query): follow the value-family rename into the lattice guard
vistart Oct 14, 2026
70e2ae1
refactor(expression): give every node its own class and its own surface
vistart Oct 17, 2026
6ad14f8
refactor(expression): read each parameter under its own name
vistart Oct 17, 2026
3d3c99f
fix(dialect): ignore a concatenation declaration that is not a name
vistart Oct 17, 2026
ee5f4b5
refactor(backend): give an artifact a kind, not a class path
vistart Oct 17, 2026
f2d8ea6
refactor(sqlite): declare the formatter instead of calling the dialect
vistart Oct 17, 2026
556568e
refactor(expression)!: plain JSON artifacts, vocabulary on the owners
vistart Oct 21, 2026
2e17cee
docs(expression): the module docstring now describes the bare node
vistart Oct 21, 2026
c4647bb
chore(dev): add PowerShell dev tooling and improve .claude docs
vistart Oct 15, 2026
1af9a0f
fix(dialect): escape through the dialect, not the base class
vistart Oct 21, 2026
c35ad91
fix(dialect): a SET DEFAULT value must reach the statement
vistart Oct 21, 2026
b5b7e5b
fix(sqlite): emit options R-Tree and Geopoly will accept
vistart Oct 21, 2026
a2e8481
refactor(sqlite)!: drop the geopoly content table that SQLite has no …
vistart Oct 21, 2026
d47254a
test(sqlite): hand the rendered statement to SQLite and run it
vistart Oct 21, 2026
61e791c
test(sqlite): cover geopoly's statement where the module is present
vistart Oct 21, 2026
56369de
fix(dialect): the dialect surface, and the type positions it decides
vistart Oct 22, 2026
819eef1
fix(sqlite): the TRUNC and LOG branches are whole again
vistart Oct 22, 2026
b7016ba
refactor(expression)!: the last statement-shaped nodes are named *Sta…
vistart Oct 22, 2026
ab4ee57
docs(ddl): the backends' create-table nodes are statements
vistart Oct 22, 2026
61266d2
refactor(expression)!: make class names, kinds and formatters agree
vistart Oct 23, 2026
fc4f246
refactor(backend)!: helpers called outside their module are public
vistart Oct 23, 2026
235b034
refactor(introspection)!: the cache is a component, and the expressio…
vistart Oct 23, 2026
c3e98bd
test(cli): pin that each introspect run is a fresh look
vistart Oct 23, 2026
6e34c75
fix(introspection): the async manager is a manager too
vistart Oct 23, 2026
505303d
fix(introspection): stop discarding what SQLite already answered
vistart Oct 24, 2026
6dd304d
feat(cli): derive which introspection questions a CLI offers
vistart Oct 24, 2026
4d2e076
revert(cli): stop arbitrating introspect types in core
vistart Oct 24, 2026
e9c4894
feat(introspection)!: the dialect owns the SQL, and the contract drop…
vistart Oct 25, 2026
52105a1
fix(expression): an OR under an AND keeps its brackets
vistart Oct 26, 2026
4f3162c
feat(cli)!: the sqlite introspect drops the table aggregate
vistart Oct 26, 2026
c7d3a18
feat(introspection): a materialized view is not a base table
vistart Oct 27, 2026
c59fc9e
feat(dialect): capability predicates answer before the server is asked
vistart Oct 27, 2026
5f00066
fix(transaction): refuse a second task (or thread) driving one connec…
vistart Oct 27, 2026
b38bc75
fix(introspection)!: a shadow table is not an external one, and sqlit…
vistart Oct 27, 2026
73a5ebf
ci: pin the test toolchain below pytest 9.2
vistart Oct 28, 2026
080215e
chore: add changelog fragment for PR #127
vistart Oct 28, 2026
42c174f
ci: pin pytest per python line, not below 9.2 outright
vistart Oct 28, 2026
1c68fbe
fix(pool): a failed rollback must not replace the error that caused it
vistart Oct 30, 2026
a55ebbd
fix(pool): a cancelled acquire must give its permit back
vistart Oct 30, 2026
305aa8c
fix(introspection): ask table_xinfo, so generated columns are columns…
vistart Oct 30, 2026
3aeeee2
feat(cli): the introspect types name their switches, and a NAME means…
vistart Oct 30, 2026
0c485b2
docs(cli): seven types, one NAME rule, and why PRAGMA stays in the li…
vistart Oct 30, 2026
a9df89d
docs(introspection): say what a None generated_expression means
vistart Oct 30, 2026
3d1b277
feat(sqlite-cli): register the console script the docs promise
vistart Oct 30, 2026
b579c31
docs: run every example against the expression API it documents
vistart Oct 30, 2026
096a46c
ci: let pytest float from 9.2, now that the two plugin breaks are shi…
vistart Oct 30, 2026
06dbb6c
docs(changelog): the fragments catch up with the last two days
vistart Oct 30, 2026
dd42c82
docs: declare the named series Python-only
vistart Oct 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
25 changes: 17 additions & 8 deletions .claude/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,14 +316,23 @@ data at construction time. String scanning of SQL is also a classic injection su
mismatch between the scanner's assumptions (quote states, escape rules, placeholder characters)
and the real dialect syntax silently corrupts the statement.

**The single exception — inline-literal resolution.** When a statement
(`requires_inline_literals() == True`, trusted developer-declared DDL) renders its literal
values inline, the dialect's `format_literal` produces **escaped, quoted atoms**; these are
concatenated directly during formatting and never require re-scanning the assembled SQL.
Because this exception deals with splicing raw values into SQL text, it is a **SQL-injection
hazard by nature**: developers must be highly alert that inline content is trusted-only and
never reachable by end users. `Literal.requires_inline_literals` / the statement-level switch
must default to `False` (bind parameters) for exactly this reason.
**The single exception — inline literals.** A value can be spliced into the SQL text
instead of bound, and the call site asks for that per value:
`Literal(dialect, value, inline=True)`. The dialect then produces **escaped, quoted atoms**
(`inline_sql_literal`), concatenated directly during formatting; nothing re-scans the
assembled SQL.

Whether to inline is **the caller's decision, not the framework's**. Some engines reject a
statement that carries a bind parameter in a position with no room for one, and a statement
built for such an engine has to inline the values in those positions. That is why the switch
lives on the literal the caller constructs: the developer knows which engine the statement
is for, and the framework cannot infer it.

The default is bind parameters (`inline=False`), because inlining is a **SQL-injection hazard
by nature**: it splices raw values into SQL text, so inline content must be trusted-only and
never reachable by end users. A formatter must not turn a bound value into text on its own
initiative, and must not refuse a statement merely because it binds where an engine would
prefer text — the caller's choice is the contract.

**What happens if you break it (harm):** a scanner (e.g. walking the assembled SQL to replace
`?` placeholders) mis-fires on placeholders that appear *inside* string literals, breaks the
Expand Down
6 changes: 3 additions & 3 deletions .claude/code_style.md
Original file line number Diff line number Diff line change
Expand Up @@ -655,9 +655,9 @@ def transaction(backend: StorageBackend):
- [ ] Expression-dialect separation maintained
- [ ] Expression system module organization followed

### RawSQLPredicate / RawSQLExpression
### RawSQLPredicate / RawSQL

`RawSQLExpression` and `RawSQLPredicate` (`backend/expression/operators.py`) embed a raw SQL
`RawSQL` and `RawSQLPredicate` (`backend/expression/operators.py`) embed a raw SQL
fragment directly into the query, bypassing the typed expression building mechanism. They are
**used internally by the core query builder** and therefore are not categorically forbidden.

Expand All @@ -677,7 +677,7 @@ SQL fragment strings relies on `RawSQLPredicate` under the hood.

#### Security analysis

- **Values are still parameterized.** `RawSQLPredicate`/`RawSQLExpression` bind parameters via
- **Values are still parameterized.** `RawSQLPredicate`/`RawSQL` bind parameters via
a separate params tuple, so values are always sent through placeholders and never
naively concatenated. The SQL string itself, however, is embedded verbatim.
- **The SQL fragment string is trusted input.** The security boundary is: the SQL structure
Expand Down
19 changes: 17 additions & 2 deletions .claude/commands/lint.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,22 @@ Run code linting with ruff
Run code linting and formatting checks for the project.

Execute the following commands:
1. ruff check src/ - Check code style
2. ruff format --check src/ - Check code formatting
1. `ruff check src/` — lint only. `[tool.ruff]` selects E/F/B at line-length 120.
2. Do NOT run `ruff format`. The project has no `[tool.ruff.format]` section, and
formatting is owned by black (`[tool.black]`, line-length 100) plus isort
(`profile = "black"`). `ruff format` would reformat at 120 and fight black.

On Windows, use the wrapper, which resolves the virtualenv and locates ruff:

```powershell
./scripts/dev.ps1 lint # ruff check src
./scripts/dev.ps1 lint src tests # explicit targets
./scripts/dev.ps1 fmt # ruff check --fix
./scripts/dev.ps1 fmt --with-black --with-isort
./scripts/dev.ps1 type # mypy src ([tool.mypy] pins python_version = 3.8)
```

`ruff`, `mypy`, `black` and `isort` are not installed by default; install them with
`uv tool install ruff mypy black isort` (or add to the virtualenv).

Show any violations and suggest fixes.
20 changes: 13 additions & 7 deletions .claude/commands/test.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
Run the full test suite for rhosocial-activerecord.

Before running tests, ensure PYTHONPATH is set:
```bash
export PYTHONPATH=src # Linux/macOS
# OR
$env:PYTHONPATH="src" # PowerShell
Before running tests, ensure PYTHONPATH is set (note the `tests` half — it makes
`providers/registry.py` importable; see .claude/testing.md §2):

```powershell
$env:PYTHONPATH="src;tests" # Windows PowerShell 7
```

Then run: pytest tests/ -v
On Windows, prefer the wrapper, which resolves the virtualenv and sets PYTHONPATH for you:

```powershell
./scripts/dev.ps1 test # serial (AGENTS.md iron rule 1)
./scripts/dev.ps1 test -Parallel # opt-in, mirrors CI: -n auto --dist=loadgroup + plugin
./scripts/dev.ps1 collect # import check only, no tests executed
```

Show test results and any failures. Focus on failing tests and suggest fixes.
Show the complete unfiltered output. Do not filter with grep or truncate with head/tail.
4 changes: 4 additions & 0 deletions .claude/rules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ stands alone; read the one that concerns the change in front of you.
| Rule | Covers |
|---|---|
| [expression-direction.md](expression-direction.md) | `expr -> format` and why the reverse direction is where injection and unreviewable inference live. What a factory signature may accept. |
| [expression-node-design.md](expression-node-design.md) | When a grammar construct is a clause node and when it is a parameter. One expression one formatter; counting paths rather than keywords; the four parameter shapes; why a constraint clause is two levels, not a class hierarchy. |
| [column-and-result-types.md](column-and-result-types.md) | Designing column classes; the core / backend / user layering; stating a result type the source cannot settle, and why it is a mixin rather than a keyword. |
| [operation-contracts.md](operation-contracts.md) | What each operation promises once the ten-backend survey measured it: the string boundary refusals, the rendering-name contracts, the temporal result types, the value-layer echo, and where version gates refuse. |
| [static-analysis.md](static-analysis.md) | Making a checker actually check: `py.typed`, a configuration that loads, and the defects that opt out of checking silently. |
| [anti-patterns.md](anti-patterns.md) | The catalogue, with what each one cost. Read this before adding a runtime lookup, a coercion helper, or a `__getattr__`. |
| [python-hazards.md](python-hazards.md) | Python spelling and measured surface for the organisation's language-neutral clauses on override order, reflection and enums (expression-dialect.md §11). |
| [introspection.md](introspection.md) | The four introspection rules: nobody spells SQL; core keeps the smallest set and does not arbitrate capability; each backend completes its own semantics; each CLI introspects one connection. |

Two rules decide most of the rest:

Expand All @@ -25,3 +28,4 @@ A third applies to the checks themselves, and it is the one most often skipped:
afternoon — four version gates that could not say no, an argument inserted into
the wrong call — were all in files that had been edited and not tested. Run the
directory.

Loading
Loading