cypherast 0.1.0__py3-none-any.whl
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.
- cypherast/__init__.py +214 -0
- cypherast/ast.py +604 -0
- cypherast/cli.py +62 -0
- cypherast/dialects/__init__.py +23 -0
- cypherast/dialects/capabilities.py +35 -0
- cypherast/dialects/constraints.py +425 -0
- cypherast/dialects/dialect.py +142 -0
- cypherast/dialects/memgraph.py +34 -0
- cypherast/dialects/neo4j.py +30 -0
- cypherast/dialects/opencypher.py +25 -0
- cypherast/dialects/puppygraph.py +54 -0
- cypherast/errors.py +158 -0
- cypherast/executor/__init__.py +6 -0
- cypherast/executor/engine.py +680 -0
- cypherast/executor/env.py +308 -0
- cypherast/executor/graph.py +112 -0
- cypherast/lexer.py +467 -0
- cypherast/lineage.py +186 -0
- cypherast/optimizer/__init__.py +67 -0
- cypherast/optimizer/catalog.py +96 -0
- cypherast/optimizer/engine.py +126 -0
- cypherast/parser.py +880 -0
- cypherast/planner/__init__.py +30 -0
- cypherast/planner/cache.py +32 -0
- cypherast/planner/cost.py +36 -0
- cypherast/planner/enumeration.py +122 -0
- cypherast/planner/physical_ops.py +162 -0
- cypherast/planner/plan.py +162 -0
- cypherast/py.typed +0 -0
- cypherast/renderer.py +431 -0
- cypherast/rewriter/__init__.py +62 -0
- cypherast/rewriter/annotate_types.py +39 -0
- cypherast/rewriter/canonicalize_patterns.py +21 -0
- cypherast/rewriter/merge_match_chains.py +63 -0
- cypherast/rewriter/pushdown_predicates.py +77 -0
- cypherast/rewriter/qualify.py +40 -0
- cypherast/rewriter/simplify.py +84 -0
- cypherast/schema.py +105 -0
- cypherast/scope.py +113 -0
- cypherast-0.1.0.dist-info/METADATA +230 -0
- cypherast-0.1.0.dist-info/RECORD +44 -0
- cypherast-0.1.0.dist-info/WHEEL +4 -0
- cypherast-0.1.0.dist-info/entry_points.txt +2 -0
- cypherast-0.1.0.dist-info/licenses/LICENSE +21 -0
cypherast/__init__.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""cypherast — Cypher/GQL transpiler, rewriter, planner, executor."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from cypherast import ast
|
|
6
|
+
from cypherast.dialects import dialect_names, get_dialect
|
|
7
|
+
from cypherast.errors import (
|
|
8
|
+
CompatibilityError,
|
|
9
|
+
CypherastError,
|
|
10
|
+
ExecuteError,
|
|
11
|
+
OptimizeError,
|
|
12
|
+
ParseError,
|
|
13
|
+
PlanError,
|
|
14
|
+
SchemaError,
|
|
15
|
+
TokenizeError,
|
|
16
|
+
ValidationError,
|
|
17
|
+
)
|
|
18
|
+
from cypherast.lexer import Lexer, Token, TokenKind
|
|
19
|
+
from cypherast.parser import Parser
|
|
20
|
+
|
|
21
|
+
__version__ = "0.1.0"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def parse(cypher: str, read: str | None = None) -> list[ast.AstNode]:
|
|
25
|
+
"""Parse one or more Cypher statements. Returns list of top-level AstNodes."""
|
|
26
|
+
dialect = get_dialect(read)
|
|
27
|
+
tree = dialect.parser(cypher).parse()
|
|
28
|
+
return [tree]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def parse_one(cypher: str, read: str | None = None) -> ast.AstNode:
|
|
32
|
+
"""Parse a single Cypher statement into an AstNode (usually ``Cypher``)."""
|
|
33
|
+
dialect = get_dialect(read)
|
|
34
|
+
return dialect.parser(cypher).parse()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def optimize(
|
|
38
|
+
cypher: str | ast.AstNode,
|
|
39
|
+
schema: object | None = None,
|
|
40
|
+
read: str | None = None,
|
|
41
|
+
write: str | None = None,
|
|
42
|
+
*,
|
|
43
|
+
strict: bool = False,
|
|
44
|
+
only: list[str] | tuple[str, ...] | None = None,
|
|
45
|
+
disable: list[str] | tuple[str, ...] | None = None,
|
|
46
|
+
constraint_only: list[str] | tuple[str, ...] | None = None,
|
|
47
|
+
constraint_disable: list[str] | tuple[str, ...] | None = None,
|
|
48
|
+
) -> ast.AstNode:
|
|
49
|
+
"""Canonicalize + apply target-dialect capability constraints.
|
|
50
|
+
|
|
51
|
+
``read`` selects the parser dialect; ``write`` (default ``read``) selects
|
|
52
|
+
capability rewrites (e.g. puppygraph LIMIT / no Cartesian MATCH).
|
|
53
|
+
|
|
54
|
+
Rules are named. Filter with ``only`` / ``disable`` for canonicalizer rules,
|
|
55
|
+
and ``constraint_only`` / ``constraint_disable`` for dialect constraints
|
|
56
|
+
(e.g. ``ensure_row_limit``, ``split_multi_path_match``).
|
|
57
|
+
"""
|
|
58
|
+
from cypherast.dialects.dialect import get_dialect_cls
|
|
59
|
+
|
|
60
|
+
tree = cypher if isinstance(cypher, ast.AstNode) else parse_one(cypher, read=read)
|
|
61
|
+
target = get_dialect_cls(write or read)
|
|
62
|
+
return target.optimize(
|
|
63
|
+
tree,
|
|
64
|
+
schema=schema,
|
|
65
|
+
strict=strict,
|
|
66
|
+
only=only,
|
|
67
|
+
disable=disable,
|
|
68
|
+
constraint_only=constraint_only,
|
|
69
|
+
constraint_disable=constraint_disable,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def translate(
|
|
74
|
+
cypher: str,
|
|
75
|
+
*,
|
|
76
|
+
from_: str | None = None,
|
|
77
|
+
to_: str | None = None,
|
|
78
|
+
pretty: bool = False,
|
|
79
|
+
optimize: bool = False,
|
|
80
|
+
strict: bool = False,
|
|
81
|
+
only: list[str] | tuple[str, ...] | None = None,
|
|
82
|
+
disable: list[str] | tuple[str, ...] | None = None,
|
|
83
|
+
constraint_only: list[str] | tuple[str, ...] | None = None,
|
|
84
|
+
constraint_disable: list[str] | tuple[str, ...] | None = None,
|
|
85
|
+
) -> str:
|
|
86
|
+
"""Parse with ``from_`` dialect and render with ``to_`` dialect (transpile).
|
|
87
|
+
|
|
88
|
+
When ``optimize=True``, runs shared rewriter + target capability constraints
|
|
89
|
+
before render (useful for puppygraph emit). Rule filters pass through.
|
|
90
|
+
"""
|
|
91
|
+
from cypherast.dialects.constraints import raise_if_invalid
|
|
92
|
+
from cypherast.dialects.dialect import get_dialect_cls
|
|
93
|
+
|
|
94
|
+
tree = parse_one(cypher, read=from_)
|
|
95
|
+
target_cls = get_dialect_cls(to_ or from_)
|
|
96
|
+
if optimize:
|
|
97
|
+
tree = target_cls.optimize(
|
|
98
|
+
tree,
|
|
99
|
+
strict=strict,
|
|
100
|
+
only=only,
|
|
101
|
+
disable=disable,
|
|
102
|
+
constraint_only=constraint_only,
|
|
103
|
+
constraint_disable=constraint_disable,
|
|
104
|
+
)
|
|
105
|
+
else:
|
|
106
|
+
tree = target_cls.apply_constraints(
|
|
107
|
+
tree, only=constraint_only, disable=constraint_disable
|
|
108
|
+
)
|
|
109
|
+
if strict:
|
|
110
|
+
raise_if_invalid(tree, target_cls.capabilities)
|
|
111
|
+
return target_cls.renderer().generate(tree, pretty=pretty)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# Alias for translate
|
|
115
|
+
transpile = translate
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def validate(
|
|
119
|
+
cypher: str | ast.AstNode,
|
|
120
|
+
*,
|
|
121
|
+
read: str | None = None,
|
|
122
|
+
dialect: str | None = None,
|
|
123
|
+
) -> list[object]:
|
|
124
|
+
"""Return capability ``ConstraintIssue`` list for ``dialect`` (default ``read``)."""
|
|
125
|
+
from cypherast.dialects.dialect import get_dialect_cls
|
|
126
|
+
|
|
127
|
+
tree = cypher if isinstance(cypher, ast.AstNode) else parse_one(cypher, read=read)
|
|
128
|
+
return list(get_dialect_cls(dialect or read).validate(tree))
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def explain(cypher: str, schema: object | None = None, read: str | None = None) -> str:
|
|
133
|
+
"""Return textual EXPLAIN plan."""
|
|
134
|
+
from cypherast.planner import explain as _explain
|
|
135
|
+
|
|
136
|
+
tree = parse_one(cypher, read=read)
|
|
137
|
+
return _explain(tree, schema=schema)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def profile(
|
|
141
|
+
cypher: str,
|
|
142
|
+
*,
|
|
143
|
+
schema: object | None = None,
|
|
144
|
+
graph: object | None = None,
|
|
145
|
+
read: str | None = None,
|
|
146
|
+
) -> str:
|
|
147
|
+
"""Run with profiling and return plan + row counts."""
|
|
148
|
+
from cypherast.planner import profile as _profile
|
|
149
|
+
|
|
150
|
+
tree = parse_one(cypher, read=read)
|
|
151
|
+
return _profile(tree, schema=schema, graph=graph)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def run(
|
|
155
|
+
cypher: str,
|
|
156
|
+
*,
|
|
157
|
+
graph: object | None = None,
|
|
158
|
+
schema: object | None = None,
|
|
159
|
+
read: str | None = None,
|
|
160
|
+
) -> object:
|
|
161
|
+
"""Execute Cypher against an in-memory Graph."""
|
|
162
|
+
from cypherast.executor import execute
|
|
163
|
+
from cypherast.executor.graph import Graph
|
|
164
|
+
|
|
165
|
+
tree = parse_one(cypher, read=read)
|
|
166
|
+
g = graph if isinstance(graph, Graph) or graph is None else None
|
|
167
|
+
if graph is not None and not isinstance(graph, Graph):
|
|
168
|
+
raise TypeError("graph must be a cypherast.executor.Graph")
|
|
169
|
+
return execute(tree, graph=g, schema=schema)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def lineage(
|
|
173
|
+
cypher: str | ast.AstNode,
|
|
174
|
+
binding: str | None = None,
|
|
175
|
+
*,
|
|
176
|
+
schema: object | None = None,
|
|
177
|
+
from_: str | None = None,
|
|
178
|
+
) -> object:
|
|
179
|
+
"""Binding-level provenance graph for a Cypher query."""
|
|
180
|
+
from cypherast.lineage import lineage as _lineage
|
|
181
|
+
|
|
182
|
+
tree = cypher if isinstance(cypher, ast.AstNode) else parse_one(cypher, read=from_)
|
|
183
|
+
return _lineage(tree, binding=binding, schema=schema)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
__all__ = [
|
|
187
|
+
"CompatibilityError",
|
|
188
|
+
"CypherastError",
|
|
189
|
+
"ExecuteError",
|
|
190
|
+
"Lexer",
|
|
191
|
+
"OptimizeError",
|
|
192
|
+
"ParseError",
|
|
193
|
+
"Parser",
|
|
194
|
+
"PlanError",
|
|
195
|
+
"SchemaError",
|
|
196
|
+
"Token",
|
|
197
|
+
"TokenKind",
|
|
198
|
+
"TokenizeError",
|
|
199
|
+
"ValidationError",
|
|
200
|
+
"ast",
|
|
201
|
+
"dialect_names",
|
|
202
|
+
"explain",
|
|
203
|
+
"get_dialect",
|
|
204
|
+
"lineage",
|
|
205
|
+
"optimize",
|
|
206
|
+
"parse",
|
|
207
|
+
"parse_one",
|
|
208
|
+
"profile",
|
|
209
|
+
"run",
|
|
210
|
+
"transpile",
|
|
211
|
+
"translate",
|
|
212
|
+
"validate",
|
|
213
|
+
"__version__",
|
|
214
|
+
]
|