@@ -170,7 +170,7 @@ def get_mermaid_config_str(self) -> str:
170170 "Get the Mermaid configuration string for the node."
171171 props = [f"<br/>{ self .key_property .name } : { self .key_property .type } | KEY" ]
172172 props .extend ([f"<br/>{ p .name } : { p .type } " for p in self .properties ])
173- return f'{ self .label } ["{ self .label } { '' .join (props )} "]'
173+ return f'{ self .label } ["{ self .label } { "" .join (props )} "]'
174174
175175 @classmethod
176176 def from_arrows (cls , arrows_node_dict : dict [str , Any ]) -> "Node" :
@@ -214,6 +214,25 @@ def to_arrows(
214214 "caption" : self .metadata .get ("caption" , "" ),
215215 }
216216
217+ def get_cypher_ingest_query_for_many_records (self ) -> str :
218+ """
219+ Generate a Cypher query to ingest a list of Node records into a Neo4j database.
220+ This query takes a parameter $records that is a list of dictionaries, each representing a Node record.
221+ """
222+ formatted_props = ", " .join (
223+ [f"{ p .name } : record.{ p .name } " for p in self .properties ]
224+ )
225+ return f"""UNWIND $records as record
226+ MERGE (n: { self .label } {{{ self .key_property .name } : record.{ self .key_property .name } }})
227+ SET n += {{{ formatted_props } }}"""
228+
229+ def get_cypher_constraint_query (self ) -> str :
230+ """
231+ Generate a Cypher query to create a NODE KEY constraint on the node.
232+ This creates a range index on the key property of the node and enforces uniqueness and existence of the key property.
233+ """
234+ return f"CREATE CONSTRAINT { self .label } _constraint IF NOT EXISTS FOR (n:{ self .label } ) REQUIRE (n.{ self .key_property .name } ) IS NODE KEY"
235+
217236
218237class Relationship (BaseModel ):
219238 "A Neo4j Relationship."
@@ -347,6 +366,41 @@ def to_arrows(self) -> dict[str, Any]:
347366 "style" : self .metadata .get ("style" , {}),
348367 }
349368
369+ def get_cypher_ingest_query_for_many_records (
370+ self , start_node_key_property_name : str , end_node_key_property_name : str
371+ ) -> str :
372+ """
373+ Generate a Cypher query to ingest a list of Relationship records into a Neo4j database.
374+ The sourceId and targetId properties are used to match the start and end nodes.
375+ This query takes a parameter $records that is a list of dictionaries, each representing a Relationship record.
376+ """
377+ formatted_props = ", " .join (
378+ [f"{ p .name } : record.{ p .name } " for p in self .properties ]
379+ )
380+ key_prop = (
381+ f" {{{ self .key_property .name } : record.{ self .key_property .name } }}"
382+ if self .key_property
383+ else ""
384+ )
385+ query = f"""UNWIND $records as record
386+ MATCH (start: { self .start_node_label } {{{ start_node_key_property_name } : record.sourceId}})
387+ MATCH (end: { self .end_node_label } {{{ end_node_key_property_name } : record.targetId}})
388+ MERGE (start)-[:{ self .type } { key_prop } ]->(end)"""
389+ if formatted_props :
390+ query += f"""
391+ SET end += {{{ formatted_props } }}"""
392+ return query
393+
394+ def get_cypher_constraint_query (self ) -> str | None :
395+ """
396+ Generate a Cypher query to create a RELATIONSHIP KEY constraint on the relationship.
397+ This creates a range index on the key property of the relationship and enforces uniqueness and existence of the key property.
398+ """
399+ if self .key_property :
400+ return f"CREATE CONSTRAINT { self .type } _constraint IF NOT EXISTS FOR ()-[r:{ self .type } ]->() REQUIRE (r.{ self .key_property .name } ) IS RELATIONSHIP KEY"
401+ else :
402+ return None
403+
350404
351405class DataModel (BaseModel ):
352406 "A Neo4j Graph Data Model."
@@ -403,6 +457,16 @@ def validate_relationships(
403457
404458 return relationships
405459
460+ @property
461+ def nodes_dict (self ) -> dict [str , Node ]:
462+ "Return a dictionary of the nodes of the data model. {node_label: node_dict}"
463+ return {n .label : n for n in self .nodes }
464+
465+ @property
466+ def relationships_dict (self ) -> dict [str , Relationship ]:
467+ "Return a dictionary of the relationships of the data model. {relationship_pattern: relationship_dict}"
468+ return {r .pattern : r for r in self .relationships }
469+
406470 def add_node (self , node : Node ) -> None :
407471 "Add a new node to the data model."
408472 if node .label in [n .label for n in self .nodes ]:
@@ -520,3 +584,40 @@ def to_arrows_dict(self) -> dict[str, Any]:
520584 def to_arrows_json_str (self ) -> str :
521585 "Convert the data model to an Arrows Data Model JSON string."
522586 return json .dumps (self .to_arrows_dict (), indent = 2 )
587+
588+ def get_node_cypher_ingest_query_for_many_records (self , node_label : str ) -> str :
589+ "Generate a Cypher query to ingest a list of Node records into a Neo4j database."
590+ node = self .nodes_dict [node_label ]
591+ return node .get_cypher_ingest_query_for_many_records ()
592+
593+ def get_relationship_cypher_ingest_query_for_many_records (
594+ self ,
595+ relationship_type : str ,
596+ relationship_start_node_label : str ,
597+ relationship_end_node_label : str ,
598+ ) -> str :
599+ "Generate a Cypher query to ingest a list of Relationship records into a Neo4j database."
600+ pattern = _generate_relationship_pattern (
601+ relationship_start_node_label ,
602+ relationship_type ,
603+ relationship_end_node_label ,
604+ )
605+ relationship = self .relationships_dict [pattern ]
606+ start_node = self .nodes_dict [relationship .start_node_label ]
607+ end_node = self .nodes_dict [relationship .end_node_label ]
608+ return relationship .get_cypher_ingest_query_for_many_records (
609+ start_node .key_property .name , end_node .key_property .name
610+ )
611+
612+ def get_cypher_constraints_query (self ) -> list [str ]:
613+ """
614+ Generate a list of Cypher queries to create constraints on the data model.
615+ This creates range indexes on the key properties of the nodes and relationships and enforces uniqueness and existence of the key properties.
616+ """
617+ node_queries = [n .get_cypher_constraint_query () + ";" for n in self .nodes ]
618+ relationship_queries = [
619+ r .get_cypher_constraint_query () + ";"
620+ for r in self .relationships
621+ if r .key_property is not None
622+ ]
623+ return node_queries + relationship_queries
0 commit comments