Skip to content

Commit 79072fb

Browse files
authored
Support exporting nodes directly in Neo4j. (#316)
1 parent 9972688 commit 79072fb

File tree

6 files changed

+481
-316
lines changed

6 files changed

+481
-316
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,4 @@ async-stream = "0.3.6"
104104
neo4rs = "0.8.0"
105105
bytes = "1.10.1"
106106
rand = "0.9.0"
107+
indoc = "2.0.6"

examples/docs_to_kg/main.py

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def docs_to_kg_flow(flow_builder: cocoindex.FlowBuilder, data_scope: cocoindex.D
2121

2222
conn_spec = cocoindex.add_auth_entry(
2323
"Neo4jConnection",
24-
cocoindex.storages.Neo4jConnectionSpec(
24+
cocoindex.storages.Neo4jConnection(
2525
uri="bolt://localhost:7687",
2626
user="neo4j",
2727
password="cocoindex",
@@ -70,38 +70,40 @@ def docs_to_kg_flow(flow_builder: cocoindex.FlowBuilder, data_scope: cocoindex.D
7070

7171
relationships.export(
7272
"relationships",
73-
cocoindex.storages.Neo4jRelationship(
73+
cocoindex.storages.Neo4j(
7474
connection=conn_spec,
75-
rel_type="RELATIONSHIP",
76-
source=cocoindex.storages.Neo4jRelationshipEndSpec(
77-
label="Entity",
78-
fields=[
79-
cocoindex.storages.Neo4jFieldMapping(
80-
field_name="subject", node_field_name="value"),
81-
cocoindex.storages.Neo4jFieldMapping(
82-
field_name="subject_embedding", node_field_name="embedding"),
83-
]
84-
),
85-
target=cocoindex.storages.Neo4jRelationshipEndSpec(
86-
label="Entity",
87-
fields=[
88-
cocoindex.storages.Neo4jFieldMapping(
89-
field_name="object", node_field_name="value"),
90-
cocoindex.storages.Neo4jFieldMapping(
91-
field_name="object_embedding", node_field_name="embedding"),
92-
]
93-
),
94-
nodes={
95-
"Entity": cocoindex.storages.Neo4jRelationshipNodeSpec(
96-
primary_key_fields=["value"],
97-
vector_indexes=[
98-
cocoindex.VectorIndexDef(
99-
field_name="embedding",
100-
metric=cocoindex.VectorSimilarityMetric.COSINE_SIMILARITY,
101-
),
102-
],
75+
mapping=cocoindex.storages.Neo4jRelationship(
76+
rel_type="RELATIONSHIP",
77+
source=cocoindex.storages.Neo4jRelationshipEnd(
78+
label="Entity",
79+
fields=[
80+
cocoindex.storages.Neo4jFieldMapping(
81+
field_name="subject", node_field_name="value"),
82+
cocoindex.storages.Neo4jFieldMapping(
83+
field_name="subject_embedding", node_field_name="embedding"),
84+
]
10385
),
104-
},
86+
target=cocoindex.storages.Neo4jRelationshipEnd(
87+
label="Entity",
88+
fields=[
89+
cocoindex.storages.Neo4jFieldMapping(
90+
field_name="object", node_field_name="value"),
91+
cocoindex.storages.Neo4jFieldMapping(
92+
field_name="object_embedding", node_field_name="embedding"),
93+
]
94+
),
95+
nodes={
96+
"Entity": cocoindex.storages.Neo4jRelationshipNode(
97+
primary_key_fields=["value"],
98+
vector_indexes=[
99+
cocoindex.VectorIndexDef(
100+
field_name="embedding",
101+
metric=cocoindex.VectorSimilarityMetric.COSINE_SIMILARITY,
102+
),
103+
],
104+
),
105+
},
106+
),
105107
),
106108
primary_key_fields=["id"],
107109
)

python/cocoindex/convert.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,10 @@ def dump_engine_object(v: Any) -> Any:
117117
nanos = int((total_secs - secs) * 1e9)
118118
return {'secs': secs, 'nanos': nanos}
119119
elif hasattr(v, '__dict__'):
120-
return {k: dump_engine_object(v) for k, v in v.__dict__.items()}
120+
s = {k: dump_engine_object(v) for k, v in v.__dict__.items()}
121+
if hasattr(v, 'kind') and 'kind' not in s:
122+
s['kind'] = v.kind
123+
return s
121124
elif isinstance(v, (list, tuple)):
122125
return [dump_engine_object(item) for item in v]
123126
elif isinstance(v, dict):

python/cocoindex/storages.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class Qdrant(op.StorageSpec):
2121
api_key: str | None = None
2222

2323
@dataclass
24-
class Neo4jConnectionSpec:
24+
class Neo4jConnection:
2525
"""Connection spec for Neo4j."""
2626
uri: str
2727
user: str
@@ -37,22 +37,36 @@ class Neo4jFieldMapping:
3737
node_field_name: str | None = None
3838

3939
@dataclass
40-
class Neo4jRelationshipEndSpec:
40+
class Neo4jRelationshipEnd:
4141
"""Spec for a Neo4j node type."""
4242
label: str
4343
fields: list[Neo4jFieldMapping]
4444

4545
@dataclass
46-
class Neo4jRelationshipNodeSpec:
46+
class Neo4jRelationshipNode:
4747
"""Spec for a Neo4j node type."""
4848
primary_key_fields: Sequence[str]
4949
vector_indexes: Sequence[index.VectorIndexDef] = ()
5050

51-
class Neo4jRelationship(op.StorageSpec):
51+
@dataclass
52+
class Neo4jNode:
53+
"""Spec for a Neo4j node type."""
54+
kind = "Node"
55+
56+
label: str
57+
58+
@dataclass
59+
class Neo4jRelationship:
60+
"""Spec for a Neo4j relationship."""
61+
kind = "Relationship"
62+
63+
rel_type: str
64+
source: Neo4jRelationshipEnd
65+
target: Neo4jRelationshipEnd
66+
nodes: dict[str, Neo4jRelationshipNode]
67+
68+
class Neo4j(op.StorageSpec):
5269
"""Graph storage powered by Neo4j."""
5370

5471
connection: AuthEntryReference
55-
rel_type: str
56-
source: Neo4jRelationshipEndSpec
57-
target: Neo4jRelationshipEndSpec
58-
nodes: dict[str, Neo4jRelationshipNodeSpec]
72+
mapping: Neo4jNode | Neo4jRelationship

src/ops/registration.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ fn register_executor_factories(registry: &mut ExecutorFactoryRegistry) -> Result
1515
Arc::new(storages::postgres::Factory::default()).register(registry)?;
1616
Arc::new(storages::qdrant::Factory::default()).register(registry)?;
1717

18-
let neo4j_pool = Arc::new(storages::neo4j::GraphPool::default());
19-
storages::neo4j::RelationshipFactory::new(neo4j_pool).register(registry)?;
18+
storages::neo4j::Factory::new().register(registry)?;
2019

2120
Ok(())
2221
}

0 commit comments

Comments
 (0)