gestaltdb 0.3.0a0__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.
- gestaltdb-0.3.0a0/PKG-INFO +136 -0
- gestaltdb-0.3.0a0/README.md +74 -0
- gestaltdb-0.3.0a0/pyproject.toml +44 -0
- gestaltdb-0.3.0a0/setup.cfg +4 -0
- gestaltdb-0.3.0a0/src/gestaltdb/__init__.py +7 -0
- gestaltdb-0.3.0a0/src/gestaltdb/cypher.py +104 -0
- gestaltdb-0.3.0a0/src/gestaltdb/cypher_ast.py +180 -0
- gestaltdb-0.3.0a0/src/gestaltdb/cypher_parser.py +584 -0
- gestaltdb-0.3.0a0/src/gestaltdb/cypher_plan.py +216 -0
- gestaltdb-0.3.0a0/src/gestaltdb/cypher_runtime.py +590 -0
- gestaltdb-0.3.0a0/src/gestaltdb/graphdb.py +2240 -0
- gestaltdb-0.3.0a0/src/gestaltdb/ingestion.py +233 -0
- gestaltdb-0.3.0a0/src/gestaltdb/kvstores.py +1414 -0
- gestaltdb-0.3.0a0/src/gestaltdb/sampling.py +198 -0
- gestaltdb-0.3.0a0/src/gestaltdb/serializers.py +230 -0
- gestaltdb-0.3.0a0/src/gestaltdb.egg-info/PKG-INFO +136 -0
- gestaltdb-0.3.0a0/src/gestaltdb.egg-info/SOURCES.txt +26 -0
- gestaltdb-0.3.0a0/src/gestaltdb.egg-info/dependency_links.txt +1 -0
- gestaltdb-0.3.0a0/src/gestaltdb.egg-info/requires.txt +68 -0
- gestaltdb-0.3.0a0/src/gestaltdb.egg-info/top_level.txt +1 -0
- gestaltdb-0.3.0a0/tests/test_cypher.py +1195 -0
- gestaltdb-0.3.0a0/tests/test_graphdb_interfaces.py +320 -0
- gestaltdb-0.3.0a0/tests/test_ingestion.py +426 -0
- gestaltdb-0.3.0a0/tests/test_kvstores.py +92 -0
- gestaltdb-0.3.0a0/tests/test_optional_dependencies.py +43 -0
- gestaltdb-0.3.0a0/tests/test_sampling.py +155 -0
- gestaltdb-0.3.0a0/tests/test_serializers.py +87 -0
- gestaltdb-0.3.0a0/tests/test_typed_traversal.py +60 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gestaltdb
|
|
3
|
+
Version: 0.3.0a0
|
|
4
|
+
Summary: A pure Python GraphDB for attributed graphs.
|
|
5
|
+
Requires-Python: <3.14,>=3.9
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: google>=3.0.0
|
|
8
|
+
Requires-Dist: msgpack>=1.1.2
|
|
9
|
+
Requires-Dist: plyvel>=1.5.1
|
|
10
|
+
Requires-Dist: protobuf>=6.33.6
|
|
11
|
+
Provides-Extra: lmdb
|
|
12
|
+
Requires-Dist: lmdb; extra == "lmdb"
|
|
13
|
+
Provides-Extra: leveldb
|
|
14
|
+
Requires-Dist: plyvel; extra == "leveldb"
|
|
15
|
+
Provides-Extra: msgpack
|
|
16
|
+
Requires-Dist: msgpack; extra == "msgpack"
|
|
17
|
+
Provides-Extra: protobuf
|
|
18
|
+
Requires-Dist: protobuf; extra == "protobuf"
|
|
19
|
+
Provides-Extra: bloom
|
|
20
|
+
Requires-Dist: pybloom-live; extra == "bloom"
|
|
21
|
+
Provides-Extra: coverage
|
|
22
|
+
Requires-Dist: coverage; extra == "coverage"
|
|
23
|
+
Requires-Dist: pytest; extra == "coverage"
|
|
24
|
+
Provides-Extra: docs
|
|
25
|
+
Requires-Dist: furo; extra == "docs"
|
|
26
|
+
Requires-Dist: myst-parser; extra == "docs"
|
|
27
|
+
Requires-Dist: sphinx; extra == "docs"
|
|
28
|
+
Provides-Extra: rocksdb
|
|
29
|
+
Requires-Dist: pyrex-rocksdb>=0.3.0a0; extra == "rocksdb"
|
|
30
|
+
Provides-Extra: arrow
|
|
31
|
+
Requires-Dist: pyarrow; extra == "arrow"
|
|
32
|
+
Provides-Extra: polars
|
|
33
|
+
Requires-Dist: polars; extra == "polars"
|
|
34
|
+
Requires-Dist: pyarrow; extra == "polars"
|
|
35
|
+
Provides-Extra: fast-ingest
|
|
36
|
+
Requires-Dist: pyarrow; extra == "fast-ingest"
|
|
37
|
+
Requires-Dist: polars; extra == "fast-ingest"
|
|
38
|
+
Requires-Dist: pyrex-rocksdb>=0.3.0a0; extra == "fast-ingest"
|
|
39
|
+
Provides-Extra: all
|
|
40
|
+
Requires-Dist: lmdb; extra == "all"
|
|
41
|
+
Requires-Dist: msgpack; extra == "all"
|
|
42
|
+
Requires-Dist: plyvel; extra == "all"
|
|
43
|
+
Requires-Dist: protobuf; extra == "all"
|
|
44
|
+
Requires-Dist: pybloom-live; extra == "all"
|
|
45
|
+
Requires-Dist: pyrex-rocksdb>=0.3.0a0; extra == "all"
|
|
46
|
+
Requires-Dist: pyarrow; extra == "all"
|
|
47
|
+
Requires-Dist: polars; extra == "all"
|
|
48
|
+
Provides-Extra: dev
|
|
49
|
+
Requires-Dist: coverage; extra == "dev"
|
|
50
|
+
Requires-Dist: furo; extra == "dev"
|
|
51
|
+
Requires-Dist: lmdb; extra == "dev"
|
|
52
|
+
Requires-Dist: msgpack; extra == "dev"
|
|
53
|
+
Requires-Dist: myst-parser; extra == "dev"
|
|
54
|
+
Requires-Dist: plyvel; extra == "dev"
|
|
55
|
+
Requires-Dist: protobuf; extra == "dev"
|
|
56
|
+
Requires-Dist: pybloom-live; extra == "dev"
|
|
57
|
+
Requires-Dist: pyrex-rocksdb>=0.3.0a0; extra == "dev"
|
|
58
|
+
Requires-Dist: pyarrow; extra == "dev"
|
|
59
|
+
Requires-Dist: polars; extra == "dev"
|
|
60
|
+
Requires-Dist: pytest; extra == "dev"
|
|
61
|
+
Requires-Dist: sphinx; extra == "dev"
|
|
62
|
+
|
|
63
|
+
# GestaltDB
|
|
64
|
+
|
|
65
|
+

|
|
66
|
+
[](https://mylonasc.github.io/gestaltdb/)
|
|
67
|
+
|
|
68
|
+
GestaltDB is a pure Python graph database toolkit for attributed graphs. It stores nodes, edges, labels, typed adjacency records, and property indexes on embedded key-value backends.
|
|
69
|
+
|
|
70
|
+
Documentation: https://mylonasc.github.io/gestaltdb/
|
|
71
|
+
|
|
72
|
+
## Install
|
|
73
|
+
|
|
74
|
+
From a local checkout:
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
uv sync
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Install into another project:
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
uv add /path/to/gestaltdb
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
With pip:
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
python -m pip install /path/to/gestaltdb
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Optional extras include `lmdb`, `leveldb`, `rocksdb`, `arrow`, `polars`, `fast-ingest`, `msgpack`, `protobuf`, `bloom`, `docs`, `dev`, and `all`.
|
|
93
|
+
|
|
94
|
+
## Quick Example
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from gestaltdb.graphdb import Edge, GraphDB, Node
|
|
98
|
+
from gestaltdb.kvstores import LMDBStore
|
|
99
|
+
from gestaltdb.serializers import PickleSerializer
|
|
100
|
+
|
|
101
|
+
graph = GraphDB(LMDBStore(path="example_lmdb"), PickleSerializer())
|
|
102
|
+
|
|
103
|
+
graph.put_node(Node(node_id="alice", labels=["Person"], properties={"name": "Alice"}))
|
|
104
|
+
graph.put_node(Node(node_id="bob", labels=["Person"], properties={"name": "Bob"}))
|
|
105
|
+
graph.put_edge(Edge(
|
|
106
|
+
edge_id="alice-knows-bob",
|
|
107
|
+
source="alice",
|
|
108
|
+
target="bob",
|
|
109
|
+
properties={"type": "knows", "since": 2024},
|
|
110
|
+
))
|
|
111
|
+
|
|
112
|
+
result = graph.query('MATCH (a:Person {name: "Alice"}) MATCH (a)-[:knows]->(b) RETURN a.id, b.name')
|
|
113
|
+
print(result.records)
|
|
114
|
+
|
|
115
|
+
graph.close()
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Features
|
|
119
|
+
|
|
120
|
+
- Attributed `Node` and `Edge` objects with stable IDs.
|
|
121
|
+
- Native node labels and typed edge traversal through `edge.properties["type"]`.
|
|
122
|
+
- LMDB, LevelDB, and RocksDB/PyRex storage backends.
|
|
123
|
+
- Pickle, JSON, MessagePack, and Protobuf serializers.
|
|
124
|
+
- Label, relationship type, property, composite, and range indexes.
|
|
125
|
+
- Read-only Cypher subset for indexed scans, typed traversal, filtering, ordering, limits, and chained `MATCH` clauses.
|
|
126
|
+
- Bulk and columnar ingestion helpers.
|
|
127
|
+
- Typed path and subgraph sampling.
|
|
128
|
+
|
|
129
|
+
See the full documentation for backend selection, indexing, Cypher syntax, ingestion, sampling, and benchmarks.
|
|
130
|
+
|
|
131
|
+
<details>
|
|
132
|
+
<summary>Name origin</summary>
|
|
133
|
+
|
|
134
|
+
The name GestaltDB is inspired by Gestalt psychology and the idea that the whole is something more than its parts.
|
|
135
|
+
|
|
136
|
+
</details>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# GestaltDB
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
[](https://mylonasc.github.io/gestaltdb/)
|
|
5
|
+
|
|
6
|
+
GestaltDB is a pure Python graph database toolkit for attributed graphs. It stores nodes, edges, labels, typed adjacency records, and property indexes on embedded key-value backends.
|
|
7
|
+
|
|
8
|
+
Documentation: https://mylonasc.github.io/gestaltdb/
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
From a local checkout:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
uv sync
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Install into another project:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
uv add /path/to/gestaltdb
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
With pip:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
python -m pip install /path/to/gestaltdb
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Optional extras include `lmdb`, `leveldb`, `rocksdb`, `arrow`, `polars`, `fast-ingest`, `msgpack`, `protobuf`, `bloom`, `docs`, `dev`, and `all`.
|
|
31
|
+
|
|
32
|
+
## Quick Example
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from gestaltdb.graphdb import Edge, GraphDB, Node
|
|
36
|
+
from gestaltdb.kvstores import LMDBStore
|
|
37
|
+
from gestaltdb.serializers import PickleSerializer
|
|
38
|
+
|
|
39
|
+
graph = GraphDB(LMDBStore(path="example_lmdb"), PickleSerializer())
|
|
40
|
+
|
|
41
|
+
graph.put_node(Node(node_id="alice", labels=["Person"], properties={"name": "Alice"}))
|
|
42
|
+
graph.put_node(Node(node_id="bob", labels=["Person"], properties={"name": "Bob"}))
|
|
43
|
+
graph.put_edge(Edge(
|
|
44
|
+
edge_id="alice-knows-bob",
|
|
45
|
+
source="alice",
|
|
46
|
+
target="bob",
|
|
47
|
+
properties={"type": "knows", "since": 2024},
|
|
48
|
+
))
|
|
49
|
+
|
|
50
|
+
result = graph.query('MATCH (a:Person {name: "Alice"}) MATCH (a)-[:knows]->(b) RETURN a.id, b.name')
|
|
51
|
+
print(result.records)
|
|
52
|
+
|
|
53
|
+
graph.close()
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Features
|
|
57
|
+
|
|
58
|
+
- Attributed `Node` and `Edge` objects with stable IDs.
|
|
59
|
+
- Native node labels and typed edge traversal through `edge.properties["type"]`.
|
|
60
|
+
- LMDB, LevelDB, and RocksDB/PyRex storage backends.
|
|
61
|
+
- Pickle, JSON, MessagePack, and Protobuf serializers.
|
|
62
|
+
- Label, relationship type, property, composite, and range indexes.
|
|
63
|
+
- Read-only Cypher subset for indexed scans, typed traversal, filtering, ordering, limits, and chained `MATCH` clauses.
|
|
64
|
+
- Bulk and columnar ingestion helpers.
|
|
65
|
+
- Typed path and subgraph sampling.
|
|
66
|
+
|
|
67
|
+
See the full documentation for backend selection, indexing, Cypher syntax, ingestion, sampling, and benchmarks.
|
|
68
|
+
|
|
69
|
+
<details>
|
|
70
|
+
<summary>Name origin</summary>
|
|
71
|
+
|
|
72
|
+
The name GestaltDB is inspired by Gestalt psychology and the idea that the whole is something more than its parts.
|
|
73
|
+
|
|
74
|
+
</details>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "gestaltdb"
|
|
7
|
+
version = "0.3.0a0"
|
|
8
|
+
description = "A pure Python GraphDB for attributed graphs."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9,<3.14"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"google>=3.0.0",
|
|
13
|
+
"msgpack>=1.1.2",
|
|
14
|
+
"plyvel>=1.5.1",
|
|
15
|
+
"protobuf>=6.33.6",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.optional-dependencies]
|
|
19
|
+
lmdb = ["lmdb"]
|
|
20
|
+
leveldb = ["plyvel"]
|
|
21
|
+
msgpack = ["msgpack"]
|
|
22
|
+
protobuf = ["protobuf"]
|
|
23
|
+
bloom = ["pybloom-live"]
|
|
24
|
+
coverage = ["coverage", "pytest"]
|
|
25
|
+
docs = ["furo", "myst-parser", "sphinx"]
|
|
26
|
+
rocksdb = ["pyrex-rocksdb>=0.3.0a0"]
|
|
27
|
+
arrow = ["pyarrow"]
|
|
28
|
+
polars = ["polars", "pyarrow"]
|
|
29
|
+
fast-ingest = ["pyarrow", "polars", "pyrex-rocksdb>=0.3.0a0"]
|
|
30
|
+
all = ["lmdb", "msgpack", "plyvel", "protobuf", "pybloom-live", "pyrex-rocksdb>=0.3.0a0", "pyarrow", "polars"]
|
|
31
|
+
dev = ["coverage", "furo", "lmdb", "msgpack", "myst-parser", "plyvel", "protobuf", "pybloom-live", "pyrex-rocksdb>=0.3.0a0", "pyarrow", "polars", "pytest", "sphinx"]
|
|
32
|
+
|
|
33
|
+
[tool.setuptools.packages.find]
|
|
34
|
+
where = ["src"]
|
|
35
|
+
|
|
36
|
+
[tool.pytest.ini_options]
|
|
37
|
+
pythonpath = ["src"]
|
|
38
|
+
testpaths = ["tests"]
|
|
39
|
+
|
|
40
|
+
[dependency-groups]
|
|
41
|
+
dev = [
|
|
42
|
+
"coverage>=7.10.7",
|
|
43
|
+
"pytest>=8.0",
|
|
44
|
+
]
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Minimal read-only Cypher support for GestaltDB.
|
|
2
|
+
|
|
3
|
+
The supported subset maps directly to existing typed adjacency and sampling APIs:
|
|
4
|
+
|
|
5
|
+
MATCH (a {id: "node-id"})-[:TYPE1]->(b)<-[:TYPE2]-(c) RETURN a.name, b LIMIT 10
|
|
6
|
+
CALL pg.sample_typed_paths(["node-id"], [{"edge_type": "TYPE", "sample_size": 2}]) YIELD path RETURN path
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
|
|
13
|
+
from .cypher_ast import MatchQuery, MultiMatchQuery, NodeScanQuery, RelationshipScanQuery, SampleTypedPathsCall
|
|
14
|
+
from .cypher_plan import LogicalPlan, plan_query
|
|
15
|
+
from .cypher_parser import parse as _parse_query, split_top_level_args as _split_top_level_args
|
|
16
|
+
from .cypher_runtime import QueryContext, execute_match, execute_multi_match, execute_node_scan, execute_relationship_scan
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class QueryResult:
|
|
21
|
+
"""Tabular query result returned by ``GraphDB.query``.
|
|
22
|
+
|
|
23
|
+
``columns`` contains projected column names in return order. ``records`` is
|
|
24
|
+
a list of dictionaries keyed by column name.
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
>>> result = QueryResult(columns=("n",), records=[{"n": "node"}])
|
|
28
|
+
>>> len(result)
|
|
29
|
+
1
|
|
30
|
+
>>> list(result)[0]["n"]
|
|
31
|
+
'node'
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
columns: tuple[str, ...]
|
|
35
|
+
records: list[dict[str, object]]
|
|
36
|
+
|
|
37
|
+
def __iter__(self):
|
|
38
|
+
"""Iterate over result records."""
|
|
39
|
+
return iter(self.records)
|
|
40
|
+
|
|
41
|
+
def __len__(self):
|
|
42
|
+
"""Return the number of result records."""
|
|
43
|
+
return len(self.records)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def parse(query: str) -> MatchQuery | SampleTypedPathsCall | NodeScanQuery | RelationshipScanQuery | MultiMatchQuery:
|
|
47
|
+
"""Parse the supported Cypher subset.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
query: Cypher query text.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Parsed query object.
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
ValueError: If the query is outside the supported subset.
|
|
57
|
+
|
|
58
|
+
Examples:
|
|
59
|
+
>>> parse('MATCH (n:Drug) RETURN n').label
|
|
60
|
+
'Drug'
|
|
61
|
+
"""
|
|
62
|
+
return _parse_query(query)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def plan(query: str) -> LogicalPlan:
|
|
66
|
+
"""Return the logical plan for a supported Cypher query."""
|
|
67
|
+
return plan_query(parse(query))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def execute(graph, query: str, parameters: dict[str, object] | None = None) -> QueryResult:
|
|
71
|
+
"""Execute a supported Cypher query against a ``GraphDB`` instance.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
graph: ``GraphDB`` instance used for indexed lookups and traversal.
|
|
75
|
+
query: Cypher query text.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
``QueryResult`` with projected records.
|
|
79
|
+
|
|
80
|
+
Examples:
|
|
81
|
+
>>> execute(graph_db, 'MATCH (n:Drug) RETURN n') # doctest: +SKIP
|
|
82
|
+
"""
|
|
83
|
+
parsed = parse(query)
|
|
84
|
+
plan_query(parsed)
|
|
85
|
+
parameters = parameters or {}
|
|
86
|
+
if isinstance(parsed, SampleTypedPathsCall):
|
|
87
|
+
paths = graph.sample_typed_paths(parsed.seed_ids, parsed.pattern)
|
|
88
|
+
if parsed.limit is not None:
|
|
89
|
+
paths = paths[:parsed.limit]
|
|
90
|
+
return QueryResult(
|
|
91
|
+
columns=parsed.returns,
|
|
92
|
+
records=[{"path": path} for path in paths],
|
|
93
|
+
)
|
|
94
|
+
if isinstance(parsed, NodeScanQuery):
|
|
95
|
+
records = execute_node_scan(parsed, QueryContext(graph=graph, parameters=parameters))
|
|
96
|
+
return QueryResult(columns=parsed.returns, records=records)
|
|
97
|
+
if isinstance(parsed, RelationshipScanQuery):
|
|
98
|
+
records = execute_relationship_scan(parsed, QueryContext(graph=graph, parameters=parameters))
|
|
99
|
+
return QueryResult(columns=parsed.returns, records=records)
|
|
100
|
+
if isinstance(parsed, MultiMatchQuery):
|
|
101
|
+
records = execute_multi_match(parsed, QueryContext(graph=graph, parameters=parameters))
|
|
102
|
+
return QueryResult(columns=parsed.returns, records=records)
|
|
103
|
+
records = execute_match(parsed, QueryContext(graph=graph, parameters=parameters))
|
|
104
|
+
return QueryResult(columns=parsed.returns, records=records)
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""AST objects for the GestaltDB Cypher subset."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class Parameter:
|
|
10
|
+
"""Cypher query parameter reference, such as ``$name``."""
|
|
11
|
+
|
|
12
|
+
name: str
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class PropertyRef:
|
|
17
|
+
"""Reference to a variable property, such as ``n.name``."""
|
|
18
|
+
|
|
19
|
+
variable: str
|
|
20
|
+
property_name: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class ComparisonExpression:
|
|
25
|
+
"""Binary comparison expression for the current Cypher subset."""
|
|
26
|
+
|
|
27
|
+
left: PropertyRef
|
|
28
|
+
operator: str
|
|
29
|
+
right: object
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class InExpression:
|
|
34
|
+
"""Membership predicate, such as ``n.kind IN ["drug"]``."""
|
|
35
|
+
|
|
36
|
+
left: PropertyRef
|
|
37
|
+
values: object
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class NullPredicate:
|
|
42
|
+
"""Null check predicate."""
|
|
43
|
+
|
|
44
|
+
expression: PropertyRef
|
|
45
|
+
negated: bool = False
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class AndExpression:
|
|
50
|
+
"""Conjunction of boolean expressions."""
|
|
51
|
+
|
|
52
|
+
expressions: tuple[object, ...]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass(frozen=True)
|
|
56
|
+
class OrderItem:
|
|
57
|
+
"""One ORDER BY item."""
|
|
58
|
+
|
|
59
|
+
expression: str
|
|
60
|
+
descending: bool = False
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass(frozen=True)
|
|
64
|
+
class TraversalHop:
|
|
65
|
+
"""One typed relationship expansion in a parsed ``MATCH`` pattern."""
|
|
66
|
+
|
|
67
|
+
rel_var: str | None
|
|
68
|
+
edge_type: str
|
|
69
|
+
target_var: str
|
|
70
|
+
direction: str = "out"
|
|
71
|
+
edge_types: tuple[str, ...] = ()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass(frozen=True)
|
|
75
|
+
class MatchQuery:
|
|
76
|
+
"""Parsed anchored typed path query."""
|
|
77
|
+
|
|
78
|
+
source_var: str
|
|
79
|
+
source_id: str
|
|
80
|
+
hops: tuple[TraversalHop, ...]
|
|
81
|
+
returns: tuple[str, ...]
|
|
82
|
+
limit: int | None = None
|
|
83
|
+
where: object | None = None
|
|
84
|
+
projections: tuple[str, ...] = ()
|
|
85
|
+
order_by: tuple[OrderItem, ...] = ()
|
|
86
|
+
skip: int | None = None
|
|
87
|
+
distinct: bool = False
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass(frozen=True)
|
|
91
|
+
class NodePatternClause:
|
|
92
|
+
"""One node pattern in a multi-clause ``MATCH`` query."""
|
|
93
|
+
|
|
94
|
+
variable: str
|
|
95
|
+
label: str | None = None
|
|
96
|
+
property_name: str | None = None
|
|
97
|
+
property_value: object = None
|
|
98
|
+
labels: tuple[str, ...] = ()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass(frozen=True)
|
|
102
|
+
class RelationshipPatternClause:
|
|
103
|
+
"""One relationship pattern in a multi-clause ``MATCH`` query."""
|
|
104
|
+
|
|
105
|
+
source_var: str
|
|
106
|
+
rel_var: str | None
|
|
107
|
+
edge_type: str
|
|
108
|
+
target_var: str
|
|
109
|
+
direction: str = "out"
|
|
110
|
+
edge_types: tuple[str, ...] = ()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@dataclass(frozen=True)
|
|
114
|
+
class AnchoredPatternClause:
|
|
115
|
+
"""One anchored traversal pattern in a multi-clause ``MATCH`` query."""
|
|
116
|
+
|
|
117
|
+
source_var: str
|
|
118
|
+
source_id: str
|
|
119
|
+
hops: tuple[TraversalHop, ...]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@dataclass(frozen=True)
|
|
123
|
+
class MultiMatchQuery:
|
|
124
|
+
"""Parsed query containing multiple ``MATCH`` clauses."""
|
|
125
|
+
|
|
126
|
+
clauses: tuple[object, ...]
|
|
127
|
+
returns: tuple[str, ...]
|
|
128
|
+
where: object | None = None
|
|
129
|
+
projections: tuple[str, ...] = ()
|
|
130
|
+
order_by: tuple[OrderItem, ...] = ()
|
|
131
|
+
skip: int | None = None
|
|
132
|
+
limit: int | None = None
|
|
133
|
+
distinct: bool = False
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@dataclass(frozen=True)
|
|
137
|
+
class SampleTypedPathsCall:
|
|
138
|
+
"""Parsed ``pg.sample_typed_paths`` procedure call."""
|
|
139
|
+
|
|
140
|
+
seed_ids: list[str]
|
|
141
|
+
pattern: list[dict[str, object]]
|
|
142
|
+
returns: tuple[str, ...] = ("path",)
|
|
143
|
+
limit: int | None = None
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@dataclass(frozen=True)
|
|
147
|
+
class NodeScanQuery:
|
|
148
|
+
"""Parsed indexed node label scan query."""
|
|
149
|
+
|
|
150
|
+
variable: str
|
|
151
|
+
label: str | None
|
|
152
|
+
property_name: str | None
|
|
153
|
+
property_value: object
|
|
154
|
+
returns: tuple[str, ...]
|
|
155
|
+
limit: int | None = None
|
|
156
|
+
where: object | None = None
|
|
157
|
+
labels: tuple[str, ...] = ()
|
|
158
|
+
projections: tuple[str, ...] = ()
|
|
159
|
+
order_by: tuple[OrderItem, ...] = ()
|
|
160
|
+
skip: int | None = None
|
|
161
|
+
distinct: bool = False
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@dataclass(frozen=True)
|
|
165
|
+
class RelationshipScanQuery:
|
|
166
|
+
"""Parsed unanchored typed relationship scan query."""
|
|
167
|
+
|
|
168
|
+
source_var: str
|
|
169
|
+
rel_var: str | None
|
|
170
|
+
edge_type: str
|
|
171
|
+
target_var: str
|
|
172
|
+
returns: tuple[str, ...]
|
|
173
|
+
direction: str = "out"
|
|
174
|
+
edge_types: tuple[str, ...] = ()
|
|
175
|
+
where: object | None = None
|
|
176
|
+
projections: tuple[str, ...] = ()
|
|
177
|
+
order_by: tuple[OrderItem, ...] = ()
|
|
178
|
+
skip: int | None = None
|
|
179
|
+
limit: int | None = None
|
|
180
|
+
distinct: bool = False
|