j-perm-sql 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.
- {j_perm_sql-0.2.0 → j_perm_sql-0.3.0}/PKG-INFO +25 -4
- {j_perm_sql-0.2.0 → j_perm_sql-0.3.0}/README.md +23 -2
- {j_perm_sql-0.2.0 → j_perm_sql-0.3.0}/pyproject.toml +2 -2
- {j_perm_sql-0.2.0 → j_perm_sql-0.3.0}/src/j_perm_sql/handler.py +74 -13
- {j_perm_sql-0.2.0 → j_perm_sql-0.3.0}/src/j_perm_sql/render.py +23 -1
- {j_perm_sql-0.2.0 → j_perm_sql-0.3.0}/src/j_perm_sql.egg-info/PKG-INFO +25 -4
- j_perm_sql-0.3.0/src/j_perm_sql.egg-info/requires.txt +1 -0
- j_perm_sql-0.2.0/src/j_perm_sql.egg-info/requires.txt +0 -1
- {j_perm_sql-0.2.0 → j_perm_sql-0.3.0}/setup.cfg +0 -0
- {j_perm_sql-0.2.0 → j_perm_sql-0.3.0}/src/j_perm_sql/__init__.py +0 -0
- {j_perm_sql-0.2.0 → j_perm_sql-0.3.0}/src/j_perm_sql/constructs.py +0 -0
- {j_perm_sql-0.2.0 → j_perm_sql-0.3.0}/src/j_perm_sql/constructs_write.py +0 -0
- {j_perm_sql-0.2.0 → j_perm_sql-0.3.0}/src/j_perm_sql/dialect.py +0 -0
- {j_perm_sql-0.2.0 → j_perm_sql-0.3.0}/src/j_perm_sql/install.py +0 -0
- {j_perm_sql-0.2.0 → j_perm_sql-0.3.0}/src/j_perm_sql/pipeline.py +0 -0
- {j_perm_sql-0.2.0 → j_perm_sql-0.3.0}/src/j_perm_sql.egg-info/SOURCES.txt +0 -0
- {j_perm_sql-0.2.0 → j_perm_sql-0.3.0}/src/j_perm_sql.egg-info/dependency_links.txt +0 -0
- {j_perm_sql-0.2.0 → j_perm_sql-0.3.0}/src/j_perm_sql.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: j-perm-sql
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: j-perm plugin for SQL
|
|
5
5
|
Author-email: Roman <kuschanow@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -10,7 +10,7 @@ Project-URL: Tracker, https://github.com/kuschanow/j-perm/issues
|
|
|
10
10
|
Project-URL: Documentation, https://github.com/kuschanow/j-perm
|
|
11
11
|
Requires-Python: >=3.10
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
|
-
Requires-Dist: j-perm>=1.
|
|
13
|
+
Requires-Dist: j-perm>=1.10.0
|
|
14
14
|
|
|
15
15
|
# j-perm-sql
|
|
16
16
|
|
|
@@ -39,8 +39,10 @@ A [j-perm](https://github.com/kuschanow/j-perm) plugin that builds and executes
|
|
|
39
39
|
pip install j-perm-sql
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
Requires `j-perm >= 1.9.0
|
|
43
|
-
|
|
42
|
+
Requires `j-perm >= 1.10.0`: 1.9.0 made `run_pipeline` a passthrough invoker
|
|
43
|
+
(which this plugin relies on), and 1.10.0 added the `nested_spec_pipeline`
|
|
44
|
+
compile hook and per-pipeline `CompiledSpec` execution that let `op: sql` be
|
|
45
|
+
compiled end-to-end (see [Compilation](#compilation)).
|
|
44
46
|
|
|
45
47
|
## Quick start
|
|
46
48
|
|
|
@@ -202,6 +204,25 @@ install_sql(engine, run_sql) # async
|
|
|
202
204
|
await engine.apply_async(spec, source=…, dest=…)
|
|
203
205
|
```
|
|
204
206
|
|
|
207
|
+
## Compilation
|
|
208
|
+
|
|
209
|
+
`op: sql` / `op: sql_write` are compilable. `engine.compile(spec)` compiles the
|
|
210
|
+
`query` subtree against the isolated SQL pipeline (the engine never needs to
|
|
211
|
+
understand the SQL constructs — it routes the nested spec through the registered
|
|
212
|
+
pipeline by name), and the rendered tree is dispatched through the compiled path
|
|
213
|
+
with per-node memoisation. Re-applying the same `CompiledSpec` keeps every node
|
|
214
|
+
compiled; only `$val` data is re-bound from the live context on each run.
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
compiled = engine.compile([{"op": "sql", "to": "/rows", "query": query}])
|
|
218
|
+
compiled.apply(source={"wanted": 1}, dest={}) # renders + executes, fully compiled
|
|
219
|
+
compiled.apply(source={"wanted": 2}, dest={}) # reuses compiled nodes, re-binds values
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
This requires the `nested_spec_pipeline` compile hook and per-pipeline
|
|
223
|
+
`CompiledSpec` execution added in the core engine (see the "What gets compiled"
|
|
224
|
+
section of the main j-perm README).
|
|
225
|
+
|
|
205
226
|
## Construct reference
|
|
206
227
|
|
|
207
228
|
**Query**
|
|
@@ -25,8 +25,10 @@ A [j-perm](https://github.com/kuschanow/j-perm) plugin that builds and executes
|
|
|
25
25
|
pip install j-perm-sql
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
Requires `j-perm >= 1.9.0
|
|
29
|
-
|
|
28
|
+
Requires `j-perm >= 1.10.0`: 1.9.0 made `run_pipeline` a passthrough invoker
|
|
29
|
+
(which this plugin relies on), and 1.10.0 added the `nested_spec_pipeline`
|
|
30
|
+
compile hook and per-pipeline `CompiledSpec` execution that let `op: sql` be
|
|
31
|
+
compiled end-to-end (see [Compilation](#compilation)).
|
|
30
32
|
|
|
31
33
|
## Quick start
|
|
32
34
|
|
|
@@ -188,6 +190,25 @@ install_sql(engine, run_sql) # async
|
|
|
188
190
|
await engine.apply_async(spec, source=…, dest=…)
|
|
189
191
|
```
|
|
190
192
|
|
|
193
|
+
## Compilation
|
|
194
|
+
|
|
195
|
+
`op: sql` / `op: sql_write` are compilable. `engine.compile(spec)` compiles the
|
|
196
|
+
`query` subtree against the isolated SQL pipeline (the engine never needs to
|
|
197
|
+
understand the SQL constructs — it routes the nested spec through the registered
|
|
198
|
+
pipeline by name), and the rendered tree is dispatched through the compiled path
|
|
199
|
+
with per-node memoisation. Re-applying the same `CompiledSpec` keeps every node
|
|
200
|
+
compiled; only `$val` data is re-bound from the live context on each run.
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
compiled = engine.compile([{"op": "sql", "to": "/rows", "query": query}])
|
|
204
|
+
compiled.apply(source={"wanted": 1}, dest={}) # renders + executes, fully compiled
|
|
205
|
+
compiled.apply(source={"wanted": 2}, dest={}) # reuses compiled nodes, re-binds values
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
This requires the `nested_spec_pipeline` compile hook and per-pipeline
|
|
209
|
+
`CompiledSpec` execution added in the core engine (see the "What gets compiled"
|
|
210
|
+
section of the main j-perm README).
|
|
211
|
+
|
|
191
212
|
## Construct reference
|
|
192
213
|
|
|
193
214
|
**Query**
|
|
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "j-perm-sql"
|
|
9
|
-
version = "0.
|
|
9
|
+
version = "0.3.0"
|
|
10
10
|
description = "j-perm plugin for SQL"
|
|
11
11
|
authors = [
|
|
12
12
|
{ name = "Roman", email = "kuschanow@gmail.com" },
|
|
@@ -18,7 +18,7 @@ requires-python = ">=3.10"
|
|
|
18
18
|
license = { text = "MIT" }
|
|
19
19
|
|
|
20
20
|
dependencies = [
|
|
21
|
-
"j-perm>=1.
|
|
21
|
+
"j-perm>=1.10.0",
|
|
22
22
|
]
|
|
23
23
|
|
|
24
24
|
[tool.pytest.ini_options]
|
|
@@ -17,9 +17,20 @@ shared :class:`SqlRenderer`.
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
19
|
from j_perm import ActionHandler, AsyncActionHandler
|
|
20
|
+
from j_perm.core import Compound
|
|
20
21
|
|
|
21
22
|
from .dialect import RenderOptions
|
|
22
|
-
from .render import
|
|
23
|
+
from .render import (
|
|
24
|
+
ACTIVE_PIPELINE_KEY,
|
|
25
|
+
COMPILE_CACHE_KEY,
|
|
26
|
+
SQL_PIPELINE_NAME,
|
|
27
|
+
is_fragment,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
#: Attribute under which a compiled query spec carries its per-node compilation
|
|
31
|
+
#: cache (see :func:`j_perm_sql.render.render`). Stored on the spec — not in the
|
|
32
|
+
#: context — so it survives across runs of the same compiled query.
|
|
33
|
+
_NODE_CACHE_ATTR = "_sql_node_cache"
|
|
23
34
|
|
|
24
35
|
|
|
25
36
|
class SqlRenderer:
|
|
@@ -34,24 +45,64 @@ class SqlRenderer:
|
|
|
34
45
|
self.opts = opts
|
|
35
46
|
self.pipeline_name = pipeline_name
|
|
36
47
|
|
|
37
|
-
def
|
|
48
|
+
def _render_ctx(self, ctx, extra_metadata: dict | None = None):
|
|
38
49
|
# Render against a scratch dest so the real document is never clobbered,
|
|
39
50
|
# but expose the real dest under _real_dest so @: pointers inside $val
|
|
40
51
|
# can still read the document being built. The active pipeline name is
|
|
41
52
|
# threaded through metadata so recursion stays in this pipeline.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
)
|
|
50
|
-
|
|
53
|
+
metadata = {
|
|
54
|
+
**ctx.metadata,
|
|
55
|
+
"_real_dest": ctx.dest,
|
|
56
|
+
ACTIVE_PIPELINE_KEY: self.pipeline_name,
|
|
57
|
+
}
|
|
58
|
+
if extra_metadata:
|
|
59
|
+
metadata.update(extra_metadata)
|
|
60
|
+
return ctx.copy(new_dest={}, new_metadata=metadata)
|
|
61
|
+
|
|
62
|
+
def _finalize(self, frag) -> tuple:
|
|
51
63
|
if not is_fragment(frag):
|
|
52
64
|
raise ValueError("top-level SQL query must be a SQL construct")
|
|
53
65
|
return self.opts.finalize(frag["sql"], frag["params"])
|
|
54
66
|
|
|
67
|
+
def render(self, query, ctx) -> tuple:
|
|
68
|
+
"""Render *query* through the interpreted SQL pipeline."""
|
|
69
|
+
render_ctx = self._render_ctx(ctx)
|
|
70
|
+
frag = ctx.engine.run_pipeline(self.pipeline_name, query, render_ctx).dest
|
|
71
|
+
return self._finalize(frag)
|
|
72
|
+
|
|
73
|
+
def render_compiled(self, compiled_query, ctx) -> tuple:
|
|
74
|
+
"""Render a pre-compiled query spec, reusing per-node compilation.
|
|
75
|
+
|
|
76
|
+
*compiled_query* is the :class:`~j_perm.core.CompiledSpec` produced for
|
|
77
|
+
the ``query`` key at compile time (its top node is already resolved).
|
|
78
|
+
Nested nodes are compiled lazily on first run and memoised on the spec,
|
|
79
|
+
so repeated executions of the same compiled query stay fully compiled.
|
|
80
|
+
"""
|
|
81
|
+
cache = getattr(compiled_query, _NODE_CACHE_ATTR, None)
|
|
82
|
+
if cache is None:
|
|
83
|
+
cache = {}
|
|
84
|
+
setattr(compiled_query, _NODE_CACHE_ATTR, cache)
|
|
85
|
+
render_ctx = self._render_ctx(ctx, {COMPILE_CACHE_KEY: cache})
|
|
86
|
+
ctx.engine.get_pipeline(self.pipeline_name).run_compiled(compiled_query, render_ctx)
|
|
87
|
+
return self._finalize(render_ctx.dest)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class _SqlCompound(Compound):
|
|
91
|
+
"""Mixin marking the ``op: sql`` handlers as compilable.
|
|
92
|
+
|
|
93
|
+
The ``query`` subtree is compiled against the handler's isolated SQL
|
|
94
|
+
pipeline (read or write) rather than the main pipeline, so the engine never
|
|
95
|
+
needs to know the SQL constructs.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
_renderer: SqlRenderer
|
|
99
|
+
|
|
100
|
+
def nested_spec_keys(self, step) -> list[str]:
|
|
101
|
+
return ["query"] if "query" in step else []
|
|
102
|
+
|
|
103
|
+
def nested_spec_pipeline(self, step, key) -> str:
|
|
104
|
+
return self._renderer.pipeline_name
|
|
105
|
+
|
|
55
106
|
|
|
56
107
|
def _write_result(step, ctx, result):
|
|
57
108
|
if "to" in step:
|
|
@@ -60,7 +111,7 @@ def _write_result(step, ctx, result):
|
|
|
60
111
|
return ctx.dest
|
|
61
112
|
|
|
62
113
|
|
|
63
|
-
class SqlHandler(ActionHandler):
|
|
114
|
+
class SqlHandler(ActionHandler, _SqlCompound):
|
|
64
115
|
"""``op: sql`` with a synchronous executor ``executor(sql, params) -> result``."""
|
|
65
116
|
|
|
66
117
|
def __init__(self, executor, renderer: SqlRenderer) -> None:
|
|
@@ -72,8 +123,13 @@ class SqlHandler(ActionHandler):
|
|
|
72
123
|
result = self._executor(sql, params)
|
|
73
124
|
return _write_result(step, ctx, result)
|
|
74
125
|
|
|
126
|
+
def execute_compiled(self, step, ctx, nested):
|
|
127
|
+
sql, params = self._renderer.render_compiled(nested["query"], ctx)
|
|
128
|
+
result = self._executor(sql, params)
|
|
129
|
+
return _write_result(step, ctx, result)
|
|
130
|
+
|
|
75
131
|
|
|
76
|
-
class AsyncSqlHandler(AsyncActionHandler):
|
|
132
|
+
class AsyncSqlHandler(AsyncActionHandler, _SqlCompound):
|
|
77
133
|
"""``op: sql`` with an async executor ``await executor(sql, params) -> result``."""
|
|
78
134
|
|
|
79
135
|
def __init__(self, executor, renderer: SqlRenderer) -> None:
|
|
@@ -84,3 +140,8 @@ class AsyncSqlHandler(AsyncActionHandler):
|
|
|
84
140
|
sql, params = self._renderer.render(step["query"], ctx)
|
|
85
141
|
result = await self._executor(sql, params)
|
|
86
142
|
return _write_result(step, ctx, result)
|
|
143
|
+
|
|
144
|
+
async def execute_compiled(self, step, ctx, nested):
|
|
145
|
+
sql, params = self._renderer.render_compiled(nested["query"], ctx)
|
|
146
|
+
result = await self._executor(sql, params)
|
|
147
|
+
return _write_result(step, ctx, result)
|
|
@@ -28,6 +28,12 @@ SQL_PIPELINE_NAME = "sql"
|
|
|
28
28
|
#: pipeline when absent.
|
|
29
29
|
ACTIVE_PIPELINE_KEY = "_sql_pipeline"
|
|
30
30
|
|
|
31
|
+
#: Metadata key carrying the per-node compilation cache (``{id(node): CompiledSpec}``)
|
|
32
|
+
#: for the *compiled* render path. Present only when the top-level ``op: sql``
|
|
33
|
+
#: was reached through a compiled pipeline; absent for the plain interpreted path
|
|
34
|
+
#: (then :func:`render` dispatches through ``run_pipeline`` as before).
|
|
35
|
+
COMPILE_CACHE_KEY = "_sql_compile_cache"
|
|
36
|
+
|
|
31
37
|
#: Keys whose presence marks a node as a *query* (must be parenthesised when
|
|
32
38
|
#: used as an operand, subquery, or derived table).
|
|
33
39
|
_QUERY_KEYS = frozenset(
|
|
@@ -56,9 +62,25 @@ def render(node: Any, ctx) -> Any:
|
|
|
56
62
|
The active pipeline name is read from ``ctx.metadata`` (set by the renderer)
|
|
57
63
|
so recursion stays within the same pipeline the top-level operation chose;
|
|
58
64
|
it defaults to the read-only :data:`SQL_PIPELINE_NAME`.
|
|
65
|
+
|
|
66
|
+
When a compilation cache is active (the top-level ``op: sql`` was compiled),
|
|
67
|
+
each node is compiled once against the SQL pipeline and the resulting
|
|
68
|
+
:class:`~j_perm.core.CompiledSpec` is memoised on the cache, so subsequent
|
|
69
|
+
runs of the same compiled query skip stage processing and handler resolution
|
|
70
|
+
for every node. The cache lives on the top-level compiled query spec, so it
|
|
71
|
+
is bounded by that query's nodes and freed together with it.
|
|
59
72
|
"""
|
|
60
73
|
name = ctx.metadata.get(ACTIVE_PIPELINE_KEY, SQL_PIPELINE_NAME)
|
|
61
|
-
|
|
74
|
+
cache = ctx.metadata.get(COMPILE_CACHE_KEY)
|
|
75
|
+
if cache is None:
|
|
76
|
+
return ctx.engine.run_pipeline(name, node, ctx).dest
|
|
77
|
+
pipeline = ctx.engine.get_pipeline(name)
|
|
78
|
+
compiled = cache.get(id(node))
|
|
79
|
+
if compiled is None:
|
|
80
|
+
compiled = pipeline.compile(node, ctx)
|
|
81
|
+
cache[id(node)] = compiled
|
|
82
|
+
pipeline.run_compiled(compiled, ctx)
|
|
83
|
+
return ctx.dest
|
|
62
84
|
|
|
63
85
|
|
|
64
86
|
def render_construct(node: Any, ctx) -> dict:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: j-perm-sql
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: j-perm plugin for SQL
|
|
5
5
|
Author-email: Roman <kuschanow@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -10,7 +10,7 @@ Project-URL: Tracker, https://github.com/kuschanow/j-perm/issues
|
|
|
10
10
|
Project-URL: Documentation, https://github.com/kuschanow/j-perm
|
|
11
11
|
Requires-Python: >=3.10
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
|
-
Requires-Dist: j-perm>=1.
|
|
13
|
+
Requires-Dist: j-perm>=1.10.0
|
|
14
14
|
|
|
15
15
|
# j-perm-sql
|
|
16
16
|
|
|
@@ -39,8 +39,10 @@ A [j-perm](https://github.com/kuschanow/j-perm) plugin that builds and executes
|
|
|
39
39
|
pip install j-perm-sql
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
Requires `j-perm >= 1.9.0
|
|
43
|
-
|
|
42
|
+
Requires `j-perm >= 1.10.0`: 1.9.0 made `run_pipeline` a passthrough invoker
|
|
43
|
+
(which this plugin relies on), and 1.10.0 added the `nested_spec_pipeline`
|
|
44
|
+
compile hook and per-pipeline `CompiledSpec` execution that let `op: sql` be
|
|
45
|
+
compiled end-to-end (see [Compilation](#compilation)).
|
|
44
46
|
|
|
45
47
|
## Quick start
|
|
46
48
|
|
|
@@ -202,6 +204,25 @@ install_sql(engine, run_sql) # async
|
|
|
202
204
|
await engine.apply_async(spec, source=…, dest=…)
|
|
203
205
|
```
|
|
204
206
|
|
|
207
|
+
## Compilation
|
|
208
|
+
|
|
209
|
+
`op: sql` / `op: sql_write` are compilable. `engine.compile(spec)` compiles the
|
|
210
|
+
`query` subtree against the isolated SQL pipeline (the engine never needs to
|
|
211
|
+
understand the SQL constructs — it routes the nested spec through the registered
|
|
212
|
+
pipeline by name), and the rendered tree is dispatched through the compiled path
|
|
213
|
+
with per-node memoisation. Re-applying the same `CompiledSpec` keeps every node
|
|
214
|
+
compiled; only `$val` data is re-bound from the live context on each run.
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
compiled = engine.compile([{"op": "sql", "to": "/rows", "query": query}])
|
|
218
|
+
compiled.apply(source={"wanted": 1}, dest={}) # renders + executes, fully compiled
|
|
219
|
+
compiled.apply(source={"wanted": 2}, dest={}) # reuses compiled nodes, re-binds values
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
This requires the `nested_spec_pipeline` compile hook and per-pipeline
|
|
223
|
+
`CompiledSpec` execution added in the core engine (see the "What gets compiled"
|
|
224
|
+
section of the main j-perm README).
|
|
225
|
+
|
|
205
226
|
## Construct reference
|
|
206
227
|
|
|
207
228
|
**Query**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
j-perm>=1.10.0
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
j-perm>=1.9.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|