j-perm-sql 0.1.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.
- j_perm_sql-0.1.0/PKG-INFO +191 -0
- j_perm_sql-0.1.0/README.md +177 -0
- j_perm_sql-0.1.0/pyproject.toml +50 -0
- j_perm_sql-0.1.0/setup.cfg +4 -0
- j_perm_sql-0.1.0/src/j_perm_sql/__init__.py +48 -0
- j_perm_sql-0.1.0/src/j_perm_sql/constructs.py +591 -0
- j_perm_sql-0.1.0/src/j_perm_sql/dialect.py +120 -0
- j_perm_sql-0.1.0/src/j_perm_sql/handler.py +75 -0
- j_perm_sql-0.1.0/src/j_perm_sql/install.py +47 -0
- j_perm_sql-0.1.0/src/j_perm_sql/pipeline.py +47 -0
- j_perm_sql-0.1.0/src/j_perm_sql/render.py +97 -0
- j_perm_sql-0.1.0/src/j_perm_sql.egg-info/PKG-INFO +191 -0
- j_perm_sql-0.1.0/src/j_perm_sql.egg-info/SOURCES.txt +14 -0
- j_perm_sql-0.1.0/src/j_perm_sql.egg-info/dependency_links.txt +1 -0
- j_perm_sql-0.1.0/src/j_perm_sql.egg-info/requires.txt +1 -0
- j_perm_sql-0.1.0/src/j_perm_sql.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: j-perm-sql
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: j-perm plugin for SQL
|
|
5
|
+
Author-email: Roman <kuschanow@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/kuschanow/j-perm
|
|
8
|
+
Project-URL: Source, https://github.com/kuschanow/j-perm
|
|
9
|
+
Project-URL: Tracker, https://github.com/kuschanow/j-perm/issues
|
|
10
|
+
Project-URL: Documentation, https://github.com/kuschanow/j-perm
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: j-perm>=1.9.0
|
|
14
|
+
|
|
15
|
+
# j-perm-sql
|
|
16
|
+
|
|
17
|
+
A [j-perm](https://github.com/kuschanow/j-perm) plugin that builds and executes
|
|
18
|
+
**SQL `SELECT` queries** from j-perm constructs.
|
|
19
|
+
|
|
20
|
+
* SQL is described with a tree of `$`-constructs (`$select`, `$col`, `$val`,
|
|
21
|
+
predicates, joins, …).
|
|
22
|
+
* A single new top-level operation — `op: sql` — renders that tree to a
|
|
23
|
+
**parameterized** `(sql, params)` pair and hands it to a configurable
|
|
24
|
+
executor (any ORM's raw-execute function).
|
|
25
|
+
* The SQL constructs live in an **isolated** named pipeline: they mean nothing
|
|
26
|
+
outside `op: sql`. `{"$select": …}` used as an ordinary value is just a dict.
|
|
27
|
+
|
|
28
|
+
> v1 scope: the full standard **`SELECT`** surface (read-only). DDL/DML
|
|
29
|
+
> (`CREATE`/`ALTER`/`INSERT`/`UPDATE`/`DELETE`) is intentionally out of scope.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install j-perm-sql
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Requires `j-perm >= 1.9.0` (the version that made `run_pipeline` a passthrough
|
|
38
|
+
invoker, which this plugin relies on).
|
|
39
|
+
|
|
40
|
+
## Quick start
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from j_perm import build_default_engine
|
|
44
|
+
from j_perm_sql import install_sql
|
|
45
|
+
|
|
46
|
+
def run_sql(sql, params):
|
|
47
|
+
# any ORM's raw execute: cursor.execute(sql, params); return rows
|
|
48
|
+
...
|
|
49
|
+
|
|
50
|
+
engine = build_default_engine()
|
|
51
|
+
install_sql(engine, run_sql, paramstyle="qmark")
|
|
52
|
+
|
|
53
|
+
engine.apply(
|
|
54
|
+
{"op": "sql", "to": "/rows", "query": {"$select": {
|
|
55
|
+
"columns": [{"$col": {"name": "id"}}, {"$col": {"name": "name"}}],
|
|
56
|
+
"from": {"table": "users"},
|
|
57
|
+
"where": {"$gte": [{"$col": {"name": "age"}}, {"$val": 18}]},
|
|
58
|
+
"order_by": [{"expr": {"$col": {"name": "name"}}}],
|
|
59
|
+
"limit": 50,
|
|
60
|
+
}}},
|
|
61
|
+
source={}, dest={},
|
|
62
|
+
)
|
|
63
|
+
# run_sql receives: ('SELECT "id", "name" FROM "users" WHERE "age" >= ? ORDER BY "name" LIMIT 50', [18])
|
|
64
|
+
# result is written to dest at /rows
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`install_sql` **patches an existing engine** — it registers the isolated SQL
|
|
68
|
+
pipeline and the `op: sql` operation. It composes with any engine and any other
|
|
69
|
+
plugins.
|
|
70
|
+
|
|
71
|
+
## The `op: sql` operation
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
{"op": "sql", "query": <SQL construct tree>, "to": "/dest/path"}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
* `query` — the SQL construct tree.
|
|
78
|
+
* `to` — optional destination pointer (template-expanded); the executor's
|
|
79
|
+
result is written there. If omitted, the result is discarded.
|
|
80
|
+
|
|
81
|
+
## Parameterization & injection safety
|
|
82
|
+
|
|
83
|
+
Data values are **always bound as parameters**, never interpolated:
|
|
84
|
+
|
|
85
|
+
* `$val` (and the data sides of `$in`, `$between`, `$values`) emit a placeholder
|
|
86
|
+
and add the value to `params`.
|
|
87
|
+
* Identifiers (table/column/alias names) are validated against a conservative
|
|
88
|
+
charset and quoted.
|
|
89
|
+
* Function names, CAST types, join types, sort directions, etc. are validated
|
|
90
|
+
against whitelists.
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
{"$eq": [{"$col": {"name": "name"}}, {"$val": {"$ref": "/user_input"}}]}
|
|
94
|
+
# → '"name" = ?' with the (possibly malicious) value safely in params
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Inside `$val`, the value expression is resolved with j-perm's normal value
|
|
98
|
+
pipeline, so `$ref`, `${…}` templates, and `@:` dest-pointers all work.
|
|
99
|
+
|
|
100
|
+
## Dialect / `RenderOptions`
|
|
101
|
+
|
|
102
|
+
Everything that genuinely differs between databases is configurable:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from j_perm_sql import RenderOptions
|
|
106
|
+
|
|
107
|
+
install_sql(engine, run_sql, dialect=RenderOptions(
|
|
108
|
+
paramstyle="numeric", # qmark (?) | format (%s) | numeric ($1) | named (:p1)
|
|
109
|
+
identifier_quote='"', # e.g. "`" for MySQL
|
|
110
|
+
pagination="fetch", # "limit" (LIMIT n OFFSET m) | "fetch" (OFFSET m ROWS FETCH FIRST n ROWS ONLY)
|
|
111
|
+
concat_operator="||", # "||" or "+"
|
|
112
|
+
))
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Sync & async
|
|
116
|
+
|
|
117
|
+
`install_sql` inspects the executor: a coroutine function registers the async
|
|
118
|
+
handler (use with `engine.apply_async`); a regular function registers the sync
|
|
119
|
+
handler (use with `engine.apply`).
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
async def run_sql(sql, params): ...
|
|
123
|
+
install_sql(engine, run_sql) # async
|
|
124
|
+
await engine.apply_async(spec, source=…, dest=…)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Construct reference
|
|
128
|
+
|
|
129
|
+
**Query**
|
|
130
|
+
|
|
131
|
+
| Construct | Form |
|
|
132
|
+
|---|---|
|
|
133
|
+
| `$select` | `{with?, distinct?, columns?, from?, joins?, where?, group_by?, having?, order_by?, limit?/offset? \| fetch?}` |
|
|
134
|
+
| `$union` / `$union_all` / `$intersect` / `$except` | `{"$union": [q1, q2, …], order_by?, limit?…}` |
|
|
135
|
+
| `$values` | `{"$values": [[…row…], …]}` (table source or `IN`) |
|
|
136
|
+
|
|
137
|
+
**Projection / expressions**
|
|
138
|
+
|
|
139
|
+
| Construct | Renders |
|
|
140
|
+
|---|---|
|
|
141
|
+
| `$col` | `"t"."name" [AS "alias"]`; `"id"`; `*`; `"t".*` |
|
|
142
|
+
| `$val` | a bound parameter |
|
|
143
|
+
| `$func` / `$call` | `NAME([DISTINCT ]args)[ OVER (…)][ AS "alias"]` (use `"*"` for `COUNT(*)`) |
|
|
144
|
+
| `$cast` | `CAST(expr AS TYPE)` |
|
|
145
|
+
| `$case` | searched `CASE WHEN … THEN … [ELSE …] END` |
|
|
146
|
+
| `$concat` | `(a || b …)` |
|
|
147
|
+
| `$add` `$sub` `$mul` `$div` `$mod` | `(a op b …)` |
|
|
148
|
+
|
|
149
|
+
Projection items may also be `{"expr": <operand>, "as": "alias"}`.
|
|
150
|
+
|
|
151
|
+
**Predicates** (WHERE / HAVING / ON)
|
|
152
|
+
|
|
153
|
+
`$and` `$or` `$not` · `$eq` `$ne` `$gt` `$gte` `$lt` `$lte` ·
|
|
154
|
+
`$in`/`$not_in` (list or subquery) · `$between`/`$not_between` ·
|
|
155
|
+
`$like`/`$not_like` (+ `escape`) · `$is_null`/`$is_not_null` ·
|
|
156
|
+
`$exists`/`$not_exists` · `$any`/`$all`/`$some` (quantified).
|
|
157
|
+
|
|
158
|
+
**FROM / JOIN** — *table source* = a table name (string), a
|
|
159
|
+
`{table, as?, schema?}` dict, a nested `$select`/`$values` (derived table, needs
|
|
160
|
+
`as`), or `lateral: true`. `$join`: `{type, table, as?, on? | using?, natural?}`
|
|
161
|
+
with type `inner`/`left`/`right`/`full`/`cross`.
|
|
162
|
+
|
|
163
|
+
**Windows** — `over` on `$func`: `{partition_by?, order_by?, frame?}` where
|
|
164
|
+
`frame` is `{type: "rows"|"range", start, end?}` and a bound is
|
|
165
|
+
`"unbounded preceding" | "unbounded following" | "current row" |
|
|
166
|
+
{preceding: n} | {following: n}`.
|
|
167
|
+
|
|
168
|
+
**GROUP BY** — a list of expressions, or `{"$rollup": […]}` /
|
|
169
|
+
`{"$cube": […]}` / `{"$grouping_sets": [[…], …]}`.
|
|
170
|
+
|
|
171
|
+
**CTE** — `with`: `[{name, columns?, recursive?, query: $select}]`.
|
|
172
|
+
|
|
173
|
+
See `tests/` for end-to-end examples.
|
|
174
|
+
|
|
175
|
+
## Portability caveats
|
|
176
|
+
|
|
177
|
+
The DSL renders standard SQL and does **not** validate that a target database
|
|
178
|
+
supports every feature — portability is the query author's responsibility:
|
|
179
|
+
|
|
180
|
+
* `LIMIT/OFFSET` vs `OFFSET/FETCH`, `RIGHT/FULL JOIN`, `INTERSECT/EXCEPT`,
|
|
181
|
+
`NATURAL JOIN`, `NULLS FIRST/LAST`, `LATERAL`, `GROUPING SETS/ROLLUP/CUBE`,
|
|
182
|
+
and the concatenation operator (`||` vs `+`) are not universal.
|
|
183
|
+
* CTEs and window functions are standard but require recent versions
|
|
184
|
+
(e.g. MySQL ≥ 8, SQLite ≥ 3.25 for windows / ≥ 3.8.3 for CTEs).
|
|
185
|
+
|
|
186
|
+
Use `RenderOptions` to match the target dialect's placeholder style, identifier
|
|
187
|
+
quoting, pagination form, and concatenation operator.
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
MIT
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# j-perm-sql
|
|
2
|
+
|
|
3
|
+
A [j-perm](https://github.com/kuschanow/j-perm) plugin that builds and executes
|
|
4
|
+
**SQL `SELECT` queries** from j-perm constructs.
|
|
5
|
+
|
|
6
|
+
* SQL is described with a tree of `$`-constructs (`$select`, `$col`, `$val`,
|
|
7
|
+
predicates, joins, …).
|
|
8
|
+
* A single new top-level operation — `op: sql` — renders that tree to a
|
|
9
|
+
**parameterized** `(sql, params)` pair and hands it to a configurable
|
|
10
|
+
executor (any ORM's raw-execute function).
|
|
11
|
+
* The SQL constructs live in an **isolated** named pipeline: they mean nothing
|
|
12
|
+
outside `op: sql`. `{"$select": …}` used as an ordinary value is just a dict.
|
|
13
|
+
|
|
14
|
+
> v1 scope: the full standard **`SELECT`** surface (read-only). DDL/DML
|
|
15
|
+
> (`CREATE`/`ALTER`/`INSERT`/`UPDATE`/`DELETE`) is intentionally out of scope.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install j-perm-sql
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Requires `j-perm >= 1.9.0` (the version that made `run_pipeline` a passthrough
|
|
24
|
+
invoker, which this plugin relies on).
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from j_perm import build_default_engine
|
|
30
|
+
from j_perm_sql import install_sql
|
|
31
|
+
|
|
32
|
+
def run_sql(sql, params):
|
|
33
|
+
# any ORM's raw execute: cursor.execute(sql, params); return rows
|
|
34
|
+
...
|
|
35
|
+
|
|
36
|
+
engine = build_default_engine()
|
|
37
|
+
install_sql(engine, run_sql, paramstyle="qmark")
|
|
38
|
+
|
|
39
|
+
engine.apply(
|
|
40
|
+
{"op": "sql", "to": "/rows", "query": {"$select": {
|
|
41
|
+
"columns": [{"$col": {"name": "id"}}, {"$col": {"name": "name"}}],
|
|
42
|
+
"from": {"table": "users"},
|
|
43
|
+
"where": {"$gte": [{"$col": {"name": "age"}}, {"$val": 18}]},
|
|
44
|
+
"order_by": [{"expr": {"$col": {"name": "name"}}}],
|
|
45
|
+
"limit": 50,
|
|
46
|
+
}}},
|
|
47
|
+
source={}, dest={},
|
|
48
|
+
)
|
|
49
|
+
# run_sql receives: ('SELECT "id", "name" FROM "users" WHERE "age" >= ? ORDER BY "name" LIMIT 50', [18])
|
|
50
|
+
# result is written to dest at /rows
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`install_sql` **patches an existing engine** — it registers the isolated SQL
|
|
54
|
+
pipeline and the `op: sql` operation. It composes with any engine and any other
|
|
55
|
+
plugins.
|
|
56
|
+
|
|
57
|
+
## The `op: sql` operation
|
|
58
|
+
|
|
59
|
+
```js
|
|
60
|
+
{"op": "sql", "query": <SQL construct tree>, "to": "/dest/path"}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
* `query` — the SQL construct tree.
|
|
64
|
+
* `to` — optional destination pointer (template-expanded); the executor's
|
|
65
|
+
result is written there. If omitted, the result is discarded.
|
|
66
|
+
|
|
67
|
+
## Parameterization & injection safety
|
|
68
|
+
|
|
69
|
+
Data values are **always bound as parameters**, never interpolated:
|
|
70
|
+
|
|
71
|
+
* `$val` (and the data sides of `$in`, `$between`, `$values`) emit a placeholder
|
|
72
|
+
and add the value to `params`.
|
|
73
|
+
* Identifiers (table/column/alias names) are validated against a conservative
|
|
74
|
+
charset and quoted.
|
|
75
|
+
* Function names, CAST types, join types, sort directions, etc. are validated
|
|
76
|
+
against whitelists.
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
{"$eq": [{"$col": {"name": "name"}}, {"$val": {"$ref": "/user_input"}}]}
|
|
80
|
+
# → '"name" = ?' with the (possibly malicious) value safely in params
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Inside `$val`, the value expression is resolved with j-perm's normal value
|
|
84
|
+
pipeline, so `$ref`, `${…}` templates, and `@:` dest-pointers all work.
|
|
85
|
+
|
|
86
|
+
## Dialect / `RenderOptions`
|
|
87
|
+
|
|
88
|
+
Everything that genuinely differs between databases is configurable:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from j_perm_sql import RenderOptions
|
|
92
|
+
|
|
93
|
+
install_sql(engine, run_sql, dialect=RenderOptions(
|
|
94
|
+
paramstyle="numeric", # qmark (?) | format (%s) | numeric ($1) | named (:p1)
|
|
95
|
+
identifier_quote='"', # e.g. "`" for MySQL
|
|
96
|
+
pagination="fetch", # "limit" (LIMIT n OFFSET m) | "fetch" (OFFSET m ROWS FETCH FIRST n ROWS ONLY)
|
|
97
|
+
concat_operator="||", # "||" or "+"
|
|
98
|
+
))
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Sync & async
|
|
102
|
+
|
|
103
|
+
`install_sql` inspects the executor: a coroutine function registers the async
|
|
104
|
+
handler (use with `engine.apply_async`); a regular function registers the sync
|
|
105
|
+
handler (use with `engine.apply`).
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
async def run_sql(sql, params): ...
|
|
109
|
+
install_sql(engine, run_sql) # async
|
|
110
|
+
await engine.apply_async(spec, source=…, dest=…)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Construct reference
|
|
114
|
+
|
|
115
|
+
**Query**
|
|
116
|
+
|
|
117
|
+
| Construct | Form |
|
|
118
|
+
|---|---|
|
|
119
|
+
| `$select` | `{with?, distinct?, columns?, from?, joins?, where?, group_by?, having?, order_by?, limit?/offset? \| fetch?}` |
|
|
120
|
+
| `$union` / `$union_all` / `$intersect` / `$except` | `{"$union": [q1, q2, …], order_by?, limit?…}` |
|
|
121
|
+
| `$values` | `{"$values": [[…row…], …]}` (table source or `IN`) |
|
|
122
|
+
|
|
123
|
+
**Projection / expressions**
|
|
124
|
+
|
|
125
|
+
| Construct | Renders |
|
|
126
|
+
|---|---|
|
|
127
|
+
| `$col` | `"t"."name" [AS "alias"]`; `"id"`; `*`; `"t".*` |
|
|
128
|
+
| `$val` | a bound parameter |
|
|
129
|
+
| `$func` / `$call` | `NAME([DISTINCT ]args)[ OVER (…)][ AS "alias"]` (use `"*"` for `COUNT(*)`) |
|
|
130
|
+
| `$cast` | `CAST(expr AS TYPE)` |
|
|
131
|
+
| `$case` | searched `CASE WHEN … THEN … [ELSE …] END` |
|
|
132
|
+
| `$concat` | `(a || b …)` |
|
|
133
|
+
| `$add` `$sub` `$mul` `$div` `$mod` | `(a op b …)` |
|
|
134
|
+
|
|
135
|
+
Projection items may also be `{"expr": <operand>, "as": "alias"}`.
|
|
136
|
+
|
|
137
|
+
**Predicates** (WHERE / HAVING / ON)
|
|
138
|
+
|
|
139
|
+
`$and` `$or` `$not` · `$eq` `$ne` `$gt` `$gte` `$lt` `$lte` ·
|
|
140
|
+
`$in`/`$not_in` (list or subquery) · `$between`/`$not_between` ·
|
|
141
|
+
`$like`/`$not_like` (+ `escape`) · `$is_null`/`$is_not_null` ·
|
|
142
|
+
`$exists`/`$not_exists` · `$any`/`$all`/`$some` (quantified).
|
|
143
|
+
|
|
144
|
+
**FROM / JOIN** — *table source* = a table name (string), a
|
|
145
|
+
`{table, as?, schema?}` dict, a nested `$select`/`$values` (derived table, needs
|
|
146
|
+
`as`), or `lateral: true`. `$join`: `{type, table, as?, on? | using?, natural?}`
|
|
147
|
+
with type `inner`/`left`/`right`/`full`/`cross`.
|
|
148
|
+
|
|
149
|
+
**Windows** — `over` on `$func`: `{partition_by?, order_by?, frame?}` where
|
|
150
|
+
`frame` is `{type: "rows"|"range", start, end?}` and a bound is
|
|
151
|
+
`"unbounded preceding" | "unbounded following" | "current row" |
|
|
152
|
+
{preceding: n} | {following: n}`.
|
|
153
|
+
|
|
154
|
+
**GROUP BY** — a list of expressions, or `{"$rollup": […]}` /
|
|
155
|
+
`{"$cube": […]}` / `{"$grouping_sets": [[…], …]}`.
|
|
156
|
+
|
|
157
|
+
**CTE** — `with`: `[{name, columns?, recursive?, query: $select}]`.
|
|
158
|
+
|
|
159
|
+
See `tests/` for end-to-end examples.
|
|
160
|
+
|
|
161
|
+
## Portability caveats
|
|
162
|
+
|
|
163
|
+
The DSL renders standard SQL and does **not** validate that a target database
|
|
164
|
+
supports every feature — portability is the query author's responsibility:
|
|
165
|
+
|
|
166
|
+
* `LIMIT/OFFSET` vs `OFFSET/FETCH`, `RIGHT/FULL JOIN`, `INTERSECT/EXCEPT`,
|
|
167
|
+
`NATURAL JOIN`, `NULLS FIRST/LAST`, `LATERAL`, `GROUPING SETS/ROLLUP/CUBE`,
|
|
168
|
+
and the concatenation operator (`||` vs `+`) are not universal.
|
|
169
|
+
* CTEs and window functions are standard but require recent versions
|
|
170
|
+
(e.g. MySQL ≥ 8, SQLite ≥ 3.25 for windows / ≥ 3.8.3 for CTEs).
|
|
171
|
+
|
|
172
|
+
Use `RenderOptions` to match the target dialect's placeholder style, identifier
|
|
173
|
+
quoting, pagination form, and concatenation operator.
|
|
174
|
+
|
|
175
|
+
## License
|
|
176
|
+
|
|
177
|
+
MIT
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = [
|
|
3
|
+
"setuptools>=68"
|
|
4
|
+
]
|
|
5
|
+
build-backend = "setuptools.build_meta"
|
|
6
|
+
|
|
7
|
+
[project]
|
|
8
|
+
name = "j-perm-sql"
|
|
9
|
+
version = "0.1.0"
|
|
10
|
+
description = "j-perm plugin for SQL"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Roman", email = "kuschanow@gmail.com" },
|
|
13
|
+
]
|
|
14
|
+
readme = "README.md"
|
|
15
|
+
|
|
16
|
+
requires-python = ">=3.10"
|
|
17
|
+
|
|
18
|
+
license = { text = "MIT" }
|
|
19
|
+
|
|
20
|
+
dependencies = [
|
|
21
|
+
"j-perm>=1.9.0",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[tool.pytest.ini_options]
|
|
25
|
+
testpaths = ["tests"]
|
|
26
|
+
pythonpath = ["src"]
|
|
27
|
+
addopts = "--cov=src/j_perm_sql --cov-report=term-missing"
|
|
28
|
+
asyncio_mode = "auto"
|
|
29
|
+
|
|
30
|
+
[tool.coverage.run]
|
|
31
|
+
source = ["src/j_perm_sql"]
|
|
32
|
+
omit = []
|
|
33
|
+
|
|
34
|
+
[tool.coverage.report]
|
|
35
|
+
fail_under = 100
|
|
36
|
+
exclude_lines = [
|
|
37
|
+
"pragma: no cover",
|
|
38
|
+
"if TYPE_CHECKING:",
|
|
39
|
+
"@abstractmethod",
|
|
40
|
+
"^\\s*\\.\\.\\.\\s*$",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.urls]
|
|
44
|
+
Homepage = "https://github.com/kuschanow/j-perm"
|
|
45
|
+
Source = "https://github.com/kuschanow/j-perm"
|
|
46
|
+
Tracker = "https://github.com/kuschanow/j-perm/issues"
|
|
47
|
+
Documentation = "https://github.com/kuschanow/j-perm"
|
|
48
|
+
|
|
49
|
+
[tool.setuptools.packages.find]
|
|
50
|
+
where = ["src"]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""j_perm_sql — build and execute SQL queries from j-perm constructs.
|
|
2
|
+
|
|
3
|
+
SQL is described with a tree of ``$``-constructs (``$select``, ``$col``,
|
|
4
|
+
``$val``, predicates, joins, …) and rendered by an **isolated** named pipeline.
|
|
5
|
+
A single top-level operation, ``op: sql``, renders that tree to a parameterized
|
|
6
|
+
``(sql, params)`` pair and hands it to a configurable executor (any ORM's raw
|
|
7
|
+
execute). The SQL constructs are never visible to the engine's normal value
|
|
8
|
+
pipeline — they only mean anything inside ``op: sql``.
|
|
9
|
+
|
|
10
|
+
Quick start::
|
|
11
|
+
|
|
12
|
+
from j_perm import build_default_engine
|
|
13
|
+
from j_perm_sql import install_sql
|
|
14
|
+
|
|
15
|
+
engine = build_default_engine()
|
|
16
|
+
install_sql(engine, my_orm_raw_execute, paramstyle="qmark")
|
|
17
|
+
|
|
18
|
+
engine.apply(
|
|
19
|
+
{"op": "sql", "to": "/rows", "query": {"$select": {
|
|
20
|
+
"columns": [{"$col": {"name": "id"}}],
|
|
21
|
+
"from": {"table": "users"},
|
|
22
|
+
"where": {"$gte": [{"$col": {"name": "age"}}, {"$val": 18}]},
|
|
23
|
+
}}},
|
|
24
|
+
source={}, dest={},
|
|
25
|
+
)
|
|
26
|
+
"""
|
|
27
|
+
from .constructs import build_sql_specials
|
|
28
|
+
from .dialect import PLACEHOLDER, RenderOptions
|
|
29
|
+
from .handler import AsyncSqlHandler, SqlHandler, SqlRenderer
|
|
30
|
+
from .install import install_sql
|
|
31
|
+
from .pipeline import SQL_PIPELINE_NAME, build_sql_pipeline
|
|
32
|
+
from .render import fragment, is_fragment, is_query, render
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"install_sql",
|
|
36
|
+
"RenderOptions",
|
|
37
|
+
"PLACEHOLDER",
|
|
38
|
+
"SqlHandler",
|
|
39
|
+
"AsyncSqlHandler",
|
|
40
|
+
"SqlRenderer",
|
|
41
|
+
"build_sql_pipeline",
|
|
42
|
+
"build_sql_specials",
|
|
43
|
+
"SQL_PIPELINE_NAME",
|
|
44
|
+
"fragment",
|
|
45
|
+
"is_fragment",
|
|
46
|
+
"is_query",
|
|
47
|
+
"render",
|
|
48
|
+
]
|