
    i                    p   U d Z ddlmZ ddlZddlmZ ddlmZ ddlm	Z	m
Z
 ddlmZ ddlmZ dd	lmZ  ee          Zh d
Zh dZ ej        d          Zd4dZd5dZd6dZd6dZ e            aded<    e            aded<   	 	 d7d8dZ G d d          Z d9d!Z!d:d&Z"d;d+Z#d<d,Z$d=d.Z%d>d/Z&d?d@d3Z'dS )Au;  Neo4j async client wrapper for knowledge-graph operations.

Neo4j 异步客户端封装模块。
负责知识图谱的 Schema 初始化、文档子图的 MERGE 写入、
实体/关系的 CRUD 操作以及文档邻域子图查询。
支持基于 graph_schema.yaml 的动态类型校验和占位文档节点合并。
    )annotationsN)defaultdict)Any)AsyncDriverAsyncGraphDatabase)settings)
get_schema)
get_logger>      失效   有效	   已废止	   待确认   部分失效>      其他   市级   未知   省级	   区县级	   国家级   乡镇街道级z^[A-Za-z_]\w{0,49}$namestrreturnc                x    t                               |           st          d| dt           j                   | S )u   校验 Cypher 标签名或关系类型名是否合法，不合法则抛出 ValueError。
    Validate a Cypher identifier (node label or relationship type).

    Prevents Cypher injection when identifiers are interpolated into f-strings.
    zInvalid Cypher identifier: u    — must match )_IDENTIFIER_REmatch
ValueErrorpattern)r   s    9D:\work\zm-rag\backend\app\infrastructure\neo4j_client.py_validate_identifierr    !   sT     %% 
3$ 3 3(03 3
 
 	
 K    propsdict[str, Any]c                j   |                      dd          }|rA|t          vr8t                              d|t                     |                     dd           |                      dd          }|rA|t
          vr8t                              d|t
                     |                     dd           | S )	zValidate and sanitise Document enum fields before Neo4j write.

    - status: must be in VALID_DOC_STATUS, otherwise cleared with a warning.
    - admin_level: must be in VALID_DOC_ADMIN_LEVEL, otherwise cleared.
    status invalid_doc_status)r%   allowedNadmin_levelinvalid_doc_admin_level)r)   r(   )getVALID_DOC_STATUSloggerwarningpopVALID_DOC_ADMIN_LEVEL)r"   r%   r)   s      r   _validate_doc_enumr1   /   s     YYx$$F "& 000+FDTUUU		(D!!!))M2..K '{*???%#) 	 	
 	
 	

 			-&&&Lr!   set[str]c                 B    t                                                      S )z0Load default node labels from graph_schema.yaml.)r	   all_node_labels r!   r   _default_node_labelsr6   F   s    <<'')))r!   c                 B    t                                                      S )z7Load default relationship types from graph_schema.yaml.)r	   all_rel_typesr5   r!   r   _default_rel_typesr9   K   s    <<%%'''r!   _VALID_NODE_LABELS_VALID_REL_TYPESnode_labelsset[str] | None	rel_typesNonec                \    | | dhz  a nt                      a ||adS t                      adS )a  Replace the module-level valid label / type sets.

    Called by GraphAdminService after loading or mutating type definitions.
    Always includes "Document" as a built-in label.
    If called with ``None``, defaults are loaded from ``graph_schema.yaml``.
    NDocument)r:   r6   r;   r9   )r<   r>   s     r   reload_valid_typesrB   [   sJ     (J<7133$-//r!   c                      e Zd ZdZd*dZed+d            Zed,d	            Zd-d
Z	d-dZ
d.dZd/dZdddd0dZd1dZd2dZd3d Zd4d$Zd5d%Zd6d(Zd7d)ZdS )8Neo4jClientu   Async Neo4j driver wrapper with schema initialisation helpers.

    Neo4j 异步驱动封装，提供 Schema 初始化、文档子图合并、
    实体 CRUD 和邻域查询等核心图操作接口。
    driverr   r   r?   c                    || _         d S N_driver)selfrE   s     r   __init__zNeo4jClient.__init__w   s    r!   'Neo4jClient'c                    t          j        t          j        t          j        t          j        f          } | |          S )z5Create a client from the global application settings.)auth)r   rE   r   	neo4j_uri
neo4j_userneo4j_password)clsrE   s     r   from_settingszNeo4jClient.from_settingsz   s?     $*%x'>?
 
 
 s6{{r!   c                    | j         S rG   rH   rJ   s    r   rE   zNeo4jClient.driver   s
    |r!   c                H   K   | j                                          d {V  d S rG   )rI   closerU   s    r   rW   zNeo4jClient.close   s2      l  """""""""""r!   c           	       K   t                      }| j                            t          j                  4 d{V }|                    d           d{V  |j        D ]}|d         }|                    dd          }t          |           t          |           |	                                 d| d}d| d	| d
| d}|                    |           d{V  |                    d           d{V  |                    d           d{V  |                    d           d{V  |                    d           d{V  |                    d           d{V  |                    d           d{V  |                    d           d{V  |                    d           d{V  ddd          d{V  n# 1 d{V swxY w Y   t                              d           dS )u^  初始化图谱 Schema：创建唯一性约束、属性索引和全文索引。

        Create uniqueness constraints, property indexes and fulltext
        indexes required by the knowledge graph.

        Entity types are loaded from ``graph_schema.yaml``; a uniqueness
        constraint on the ``key_property`` is created for each type.
        databaseNzYCREATE CONSTRAINT doc_id_unique IF NOT EXISTS FOR (d:Document) REQUIRE d.doc_id IS UNIQUEr   key_property__uniquezCREATE CONSTRAINT z IF NOT EXISTS FOR (n:z) REQUIRE n.z
 IS UNIQUEzrCREATE FULLTEXT INDEX doc_fulltext IF NOT EXISTS FOR (d:Document) ON EACH [d.title, d.subject_summary, d.doc_code]zVCREATE FULLTEXT INDEX org_fulltext IF NOT EXISTS FOR (o:Organization) ON EACH [o.name]zbCREATE FULLTEXT INDEX matter_fulltext IF NOT EXISTS FOR (m:Matter) ON EACH [m.name, m.description]zPCREATE INDEX doc_publish_date IF NOT EXISTS FOR (d:Document) ON (d.publish_date)zHCREATE INDEX doc_doc_code IF NOT EXISTS FOR (d:Document) ON (d.doc_code)zDCREATE INDEX doc_status IF NOT EXISTS FOR (d:Document) ON (d.status)zaCREATE INDEX doc_knowledge_category IF NOT EXISTS FOR (d:Document) ON (d.knowledge_category_code)zXCREATE INDEX doc_normalized_title IF NOT EXISTS FOR (d:Document) ON (d.normalized_title)neo4j_schema_initialized)r	   rI   sessionr   neo4j_databaserunentity_typesr+   r    lowerr-   info)rJ   schemar_   etlabelkey_propconstraint_namecyphers           r   init_schemazNeo4jClient.init_schema   s      <''1H'II 8	 8	 8	 8	 8	 8	 8	W++>         ) * *6
66.&99 %U+++$X...%*[[]]"F"FX"F"F"FF F F#F F19F F F  kk&)))))))))) ++T         ++8         ++A         ++7         ++3         ++1         ++B         ++;        k8	 8	 8	 8	 8	 8	 8	 8	 8	 8	 8	 8	 8	 8	 8	 8	 8	 8	 8	 8	 8	 8	 8	 8	 8	 8	 8	t 	./////s   E;G		
GGdoc_idr   metadatar#   entitieslist[dict[str, Any]]relationshipsc           
     	  K   t           pt                      }t          pt                      }t	                      }|                    dd          }|                    dd          }	|r|	s|                    |          }	| j                            t          j
                  4 d{V }
i d|d|                    dd          d|                    dd          d	|                    d	          p|                    d
d          d|                    dd          d|                    dd          d|                    dd          d|                    dd          d|                    dd          d|                    dd          d|                    dd          d|                    d          p|                    d          pg d|d|	d|                    dd          d|                    dd          dt          t                              |                    d          pg                     }d |                                D             }||d<   t          |          }|
                    d||           d{V  |                    d	d          }|rZ|
                    d||           d{V  |
                    d||           d{V  |
                    d||           d{V  t#          t                    }|D ]k}|                    dd          }|                    d           pi }||vrt$                              d!|"           P||                             |           l|                                D ]\  }}t+          |
||           d{V  t#          t                    }|D ]}|                    d#d          }|                    d$d          }|                    d%d          }||vs||vs||vrt$                              d&|||'           o||||f                             |           |                                D ]"\  \  }}}}t-          |
||||           d{V  #	 ddd          d{V  n# 1 d{V swxY w Y   t$                              d(|t1          |          t1          |          )           dS )*u  将完整的文档子图合并写入 Neo4j（MERGE 幂等操作），包括文档节点、实体节点和关系。

        Merge a complete document subgraph into Neo4j (MERGE, idempotent).

        Parameters
        ----------
        doc_id:
            Unique document ID used as the Document node key.
        metadata:
            Document-level metadata written onto the Document node.
        entities:
            Each dict: ``{"label": "<NodeLabel>", "properties": {...}}``.
            Document nodes are keyed by ``doc_id``.
            All other nodes are keyed by ``name`` (or ``key_property``
            defined in ``graph_schema.yaml``).
        relationships:
            Each dict: ``{"from_label", "from_key", "to_label", "to_key",
            "type", "properties"}``.  ``from_key`` / ``to_key`` are the
            matching key values (``doc_id`` for Document nodes, ``name``
            for all others).
        knowledge_categoryr&   knowledge_category_coderY   Nrl   titlenormalized_titledoc_code
doc_numberdoc_typer%   publish_dateeffective_dateexpiry_dater)   subject_summarykeywordssubject_wordssource
is_currentTacl_idsc                >    i | ]\  }}|d k    |g k    s|dk    ||S )r&   r   r5   ).0kvs      r   
<dictcomp>z4Neo4jClient.merge_document_graph.<locals>.<dictcomp>  s?       Aq77R1	>> 1+9>>r!   z4MERGE (d:Document {doc_id: $doc_id}) SET d += $props)rl   r"   a  
                    MATCH (placeholder:Document)
                    WHERE placeholder.doc_code = $doc_code
                      AND placeholder.doc_id STARTS WITH 'ref:'
                      AND placeholder.doc_id <> $doc_id
                      AND coalesce(placeholder.is_placeholder, false) = true
                    WITH placeholder
                    MATCH (src)-[r]->(placeholder)
                    WITH placeholder, src, r, type(r) AS rtype, properties(r) AS rprops
                    MATCH (real:Document {doc_id: $doc_id})
                    CALL apoc.create.relationship(src, rtype, rprops, real) YIELD rel
                    DELETE r
                    )rv   rl   a  
                    MATCH (placeholder:Document)
                    WHERE placeholder.doc_code = $doc_code
                      AND placeholder.doc_id STARTS WITH 'ref:'
                      AND placeholder.doc_id <> $doc_id
                      AND coalesce(placeholder.is_placeholder, false) = true
                    WITH placeholder
                    MATCH (placeholder)-[r]->(tgt)
                    WITH placeholder, tgt, r, type(r) AS rtype, properties(r) AS rprops
                    MATCH (real:Document {doc_id: $doc_id})
                    CALL apoc.create.relationship(real, rtype, rprops, tgt) YIELD rel
                    DELETE r
                    am  
                    MATCH (placeholder:Document)
                    WHERE placeholder.doc_code = $doc_code
                      AND placeholder.doc_id STARTS WITH 'ref:'
                      AND placeholder.doc_id <> $doc_id
                      AND coalesce(placeholder.is_placeholder, false) = true
                    DELETE placeholder
                    rg   
propertiesunknown_node_label)rg   
from_labelto_labeltypeinvalid_relationship)r   r   rel_typedocument_graph_merged)rl   rn   rp   )r:   r6   r;   r9   r	   r+   rs   rI   r_   r   r`   listdictfromkeysitemsr1   ra   r   r-   r.   append_batch_merge_nodes_batch_merge_relsrd   len)rJ   rl   rm   rn   rp   valid_labels
valid_relsre   kckc_coder_   	doc_propsrv   node_groupsentityrg   r"   
props_list
rel_groupsrelr   r   r   rel_lists                           r   merge_document_graphz Neo4jClient.merge_document_graph   s     : *C-A-C-C%=);)=)=
 \\.33,,8"== 	9g 	944R88G<''1H'II C	[ C	[ C	[ C	[ C	[ C	[ C	[W)&)gr22) #HLL1CR$H$H) HLL44V\SU8V8V	)
 HLLR88) (,,x44) ^R @ @) !(,,/?"D"D) x||M2>>) x||M2>>) "8<<0A2#F#F) HLL44[_8U8U[Y[) %b) *7) (,,x44)  hll<>>!)" 4hll9.E.E.K L LMM#)I( %OO--  I
 #)Ih*955I++F           !}}Z44H 1kk &! "         $ kk &! "         $ kk &! "          <Gt;L;LK" 1 1

7B//

<006B,,NN#7uNEEEE"))%0000%0%6%6%8%8 E E!z(%DDDDDDDDDD LWW[K\K\J$ I I WW\266
77:r227762..l22|33z11NN.#-!)!)	 #    J(;<CCCHHHH>H>N>N>P>P [ [:0Xx('XxQYZZZZZZZZZZ[EC	[ C	[ C	[ C	[ C	[ C	[ C	[ C	[ C	[ C	[ C	[ C	[ C	[ C	[ C	[ C	[ C	[ C	[ C	[ C	[ C	[ C	[ C	[ C	[ C	[ C	[ C	[J 	#]]m,,	 	 	
 	
 	
 	
 	
s   +O/R--
R7:R7c                  K   | j                             t          j                  4 d{V }|                    d|           d{V  |D ]F}|d         }|d         }d| dt          ||           d	}|                    ||
           d{V  G|D ]}	|	d         }
|	d         }|	d         }|	d         }|	d         }|	                    di           }d|
 dt          |
|d           d| dt          ||d           d| d}|                    ||||           d{V  	 ddd          d{V  n# 1 d{V swxY w Y   t          	                    d|t          |                     dS )a  Merge extracted entities and relationships into the knowledge graph.

        Parameters
        ----------
        doc_id:
            The source document identifier.
        entities:
            Each dict must have ``label`` (e.g. Organization) and ``properties``
            containing at least the uniqueness key (e.g. ``name``).
        relationships:
            Each dict must have ``from_label``, ``from_key``, ``to_label``,
            ``to_key``, ``type`` (relationship type) and optionally ``properties``.
        rY   Nz$MERGE (d:Document {doc_id: $doc_id})rl   rg   r   	MERGE (n: {z})
SET n += $props)r"   r   from_keyr   to_keyr   	MATCH (a:fromz})
MATCH (b:toz})
MERGE (a)-[r:]->(b)
SET r += $rel_propsr   r   	rel_propsneo4j_entities_merged)rl   count)rI   r_   r   r`   ra   _key_clauser+   _match_clauser-   rd   r   )rJ   rl   rn   rp   r_   r   rg   r"   rj   r   r   r   r   r   r   r   s                   r   merge_entitieszNeo4jClient.merge_entities  s     & <''1H'II #	 #	 #	 #	 #	 #	 #	W++6          
 # 7 7w|,' ' '+eU*C*C ' ' '  kk&k6666666666$   .
z?z?Xv;GGL"55	+
 + +}ZSY/Z/Z + + (+ +-:8VT-R-R+ +$,+ + +  kk%!'	 "          !#	 #	 #	 #	 #	 #	 #	 #	 #	 #	 #	 #	 #	 #	 #	 #	 #	 #	 #	 #	 #	 #	 #	 #	 #	 #	 #	J 	+F#h--PPPPPs   C9D::
EE   N)	max_depth
acl_tokensr   intr   list[str] | Nonec          
       K   t          dt          t          |          d                    }d}|d}d| d| d}| j                            t
          j        	          4 d{V }d
|i}|||d<    |j        |fi | d{V }	|	                                 d{V }
ddd          d{V  n# 1 d{V swxY w Y   |
g g dS t                      }g }|
d         pg D ]f}|d         }|rZ||vrV|
                    |           |                    |t          |d                   t          |d         pi           d           gt                      }g }|
d         pg D ]s}|d         |d         |d         f}||vrV|
                    |           |                    |d         |d         |d         t          |d         pi           d           t||dS )ur  查询文档的邻域子图，返回指定深度内的所有节点和边。

        Return nodes and edges for a document's neighbourhood sub-graph.

        Returns a dict with ``nodes`` (list of dicts) and ``edges`` (list of
        dicts with ``source``, ``target``, ``type``).

        NOTE: Neo4j does not allow parameters in variable-length path ranges
        ``[*1..$max_depth]``.  The depth is embedded as a literal.  All node
        and relationship data is returned as scalar values (elementId, labels,
        properties) so the async driver never needs to serialise Node /
        Relationship objects.
              z,coalesce(node.is_placeholder, false) = falseNz(coalesce(node.is_placeholder, false) = false AND node.acl_ids IS NOT NULL AND (size(coalesce(node.acl_ids, [])) = 0 OR any(token IN coalesce(node.acl_ids, []) WHERE token IN $acl_tokens)))z1MATCH path = (d:Document {doc_id: $doc_id})-[*1..zF]-(neighbor) WHERE all(node IN nodes(path) WHERE NOT node:Document OR a  ) UNWIND relationships(path) AS rel WITH DISTINCT rel WITH   collect(DISTINCT {eid: elementId(startNode(rel)), lbs: labels(startNode(rel)), props: properties(startNode(rel))})   + collect(DISTINCT {eid: elementId(endNode(rel)),   lbs: labels(endNode(rel)),   props: properties(endNode(rel))})   AS all_nodes,   collect(DISTINCT {src: elementId(startNode(rel)), tgt: elementId(endNode(rel)),                     typ: type(rel), props: properties(rel)}) AS all_rels RETURN all_nodes AS nodes, all_rels AS relsrY   rl   r   )nodesedgesr   eidlbsr"   idlabelsr   relssrctgttyp)r   targetr   r   )maxminr   rI   r_   r   r`   ra   singlesetaddr   r   r   )rJ   rl   r   r   
depth_safedoc_visibilityrj   r_   paramsresultrecord
seen_nodesr   nr   
seen_edgesr   rkeys                      r   query_document_graphz Neo4jClient.query_document_graph  s     ( CI2233
G![ 
:* 
: 
:HV
: 
: 
: 	 <''1H'II 	+ 	+ 	+ 	+ 	+ 	+ 	+W&.%7F%'1|$&7;v8888888888F!==??******F	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ >"---  #uu
&(/'R 	 	AE(C s*,,s###"1U8nn"&qz'7R"8"8     "%
&(.&B 		 		AU8QuXqx0C*$$s###hheH"&qz'7R"8"8	     ///s   (:B44
B>B>c                *  K   | j                             t          j                  4 d{V }|                    d           d{V }|                                 d{V }|r|d         ndcddd          d{V  S # 1 d{V swxY w Y   dS )z8Return the total number of nodes in the knowledge graph.rY   Nz MATCH (n) RETURN count(n) AS cntcntr   rI   r_   r   r`   ra   r   )rJ   r_   r   r   s       r   get_node_countzNeo4jClient.get_node_count  s0     <''1H'II 	2 	2 	2 	2 	2 	2 	2W";;'IJJJJJJJJF!==??******F$*16%==	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2 	2s   AB
BBdict[str, int]c                |  K   d}| j                             t          j                  4 d{V }|                    ||           d{V }|                                 d{V }|r|d         nd}ddd          d{V  n# 1 d{V swxY w Y   d|z   }t                              d|||	           d
|iS )u  删除文档节点、关系，以及因此变为孤立的实体节点。

        Steps:
        1. 收集 Document 直连的所有实体节点 ID
        2. DETACH DELETE Document 节点
        3. 对原直连实体，若不再有任何 Document 引用则删除（清理孤立节点）

        Returns
        -------
        dict
            ``{"deleted_nodes": int}`` — 删除的节点总数（含 Document + 孤立实体）。
        a  
        MATCH (d:Document {doc_id: $doc_id})
        OPTIONAL MATCH (d)--(entity)
        WHERE NOT entity:Document
        WITH d, collect(DISTINCT elementId(entity)) AS entity_ids
        DETACH DELETE d
        WITH entity_ids
        UNWIND entity_ids AS eid
        MATCH (e) WHERE elementId(e) = eid
        WHERE NOT EXISTS { MATCH (e)--(other:Document) }
        DETACH DELETE e
        RETURN count(e) AS orphans_deleted
        rY   Nr   orphans_deletedr   r   neo4j_document_deleted)rl   deleted_nodesr   r   rI   r_   r   r`   ra   r   r-   rd   )rJ   rl   rj   r_   r   r   orphanstotals           r   delete_document_graphz!Neo4jClient.delete_document_graph  s      <''1H'II 	A 	A 	A 	A 	A 	A 	AW";;vf;========F!==??******F39@f.//qG	A 	A 	A 	A 	A 	A 	A 	A 	A 	A 	A 	A 	A 	A 	A 	A 	A 	A 	A 	A 	A 	A 	A 	A 	A 	A 	A
 G$#	 	 	
 	
 	
  ''s   AB
BBc                  K   d}d}| j                             t          j                  4 d{V }	 |                    |           d{V }|                                 d{V }|r|d         nd}||z  }|dk     rnN	 ddd          d{V  n# 1 d{V swxY w Y   t                              d|	           d
|iS )u0   删除图数据库中的所有节点和关系。zq
        MATCH (n)
        WITH n LIMIT 10000
        DETACH DELETE n
        RETURN count(n) AS deleted
        r   rY   NTdeletedi'  neo4j_all_deleted)r   r   r   )rJ   rj   total_deletedr_   r   r   batchs          r   delete_all_graphzNeo4jClient.delete_all_graphE  s{      <''1H'II 	 	 	 	 	 	 	W&{{622222222%}}......-3:y))&5== 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	'}EEE//s   AB
B"B	entity_idr"   dict[str, Any] | Nonec                  K   d}| j                             t          j                  4 d{V }|                    |||           d{V }|                                 d{V }ddd          d{V  n# 1 d{V swxY w Y   |dS |d         t          |d                   t          |d                   dS )	z4Update properties on a node identified by elementId.zzMATCH (n) WHERE elementId(n) = $eid SET n += $props RETURN elementId(n) AS id, labels(n) AS labels, properties(n) AS propsrY   N)r   r"   r   r   r"   r   )rI   r_   r   r`   ra   r   r   r   )rJ   r   r"   rj   r_   r   r   s          r   update_nodezNeo4jClient.update_nodeZ  sd     U 	
 <''1H'II 	+ 	+ 	+ 	+ 	+ 	+ 	+W";;v9E;JJJJJJJJF!==??******F	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ >4,6(+,,vg//
 
 	
s   9A;;
BBc                2  K   d}| j                             t          j                  4 d{V }|                    ||           d{V }|                                 d{V }ddd          d{V  n# 1 d{V swxY w Y   d|r|d         ndiS )zQDETACH DELETE a node by elementId, returning the number of deleted relationships.zpMATCH (n) WHERE elementId(n) = $eid WITH n, size([(n)-[r]-() | r]) AS rel_count DETACH DELETE n RETURN rel_countrY   Nr   deleted_relationships	rel_countr   r   )rJ   r   rj   r_   r   r   s         r   delete_nodezNeo4jClient.delete_nodel  s<      	 <''1H'II 	+ 	+ 	+ 	+ 	+ 	+ 	+W";;v9;========F!==??******F	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ ()M)<)<ANN   8A::
BBrel_idboolc                L  K   d}| j                             t          j                  4 d{V }|                    ||           d{V }|                                 d{V }ddd          d{V  n# 1 d{V swxY w Y   t          |o|d         dk              S )zCDelete a single relationship by elementId. Returns True if deleted.zJMATCH ()-[r]->() WHERE elementId(r) = $rid DELETE r RETURN count(r) AS cntrY   N)ridr   r   )rI   r_   r   r`   ra   r   r   )rJ   r   rj   r_   r   r   s         r   delete_relationship_by_idz%Neo4jClient.delete_relationship_by_idy  s>     . 	 <''1H'II 	+ 	+ 	+ 	+ 	+ 	+ 	+W";;v6;::::::::F!==??******F	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ F0ve}q0111r   c           
       K   d}| j                             t          j                  4 d{V }|                    ||           d{V }|                                 d{V }ddd          d{V  n# 1 d{V swxY w Y   |dS |d         }g }|d         D ]y}|d         o|                    |d         j        |d         j        t          |d         j
                  t          |d                                       d	d
          d           z|j        t          |j
                  t          |          |dS )z0Retrieve a single entity node by its element id.zMATCH (n) WHERE elementId(n) = $eid OPTIONAL MATCH (n)-[r]-(m) RETURN n, collect(DISTINCT {rel: r, neighbor: m}) AS connectionsrY   Nr   r   connectionsr   neighborr   r&   )r   neighbor_idneighbor_labelsneighbor_name)r   r   r   r   )rI   r_   r   r`   ra   r   r   r   
element_idr   r   r   r+   )	rJ   r   rj   r_   r   r   noder   conns	            r   
get_entityzNeo4jClient.get_entity  s     O 	
 <''1H'II 	+ 	+ 	+ 	+ 	+ 	+ 	+W";;v9;========F!==??******F	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ >4c{=) 	 	DE{&"" K,#'
#3#>'+D,<,C'D'D%)$z*:%;%;%?%?%K%K	$ $    /4;''t**&	
 
 	
r   )rE   r   r   r?   )r   rL   )r   r   )r   r?   )
rl   r   rm   r#   rn   ro   rp   ro   r   r?   )rl   r   rn   ro   rp   ro   r   r?   )rl   r   r   r   r   r   r   r#   )r   r   )rl   r   r   r   )r   r   )r   r   r"   r#   r   r   )r   r   r   r   )r   r   r   r   )r   r   r   r   )__name__
__module____qualname____doc__rK   classmethodrS   propertyrE   rW   rk   r   r   r   r   r   r   r   r   r   r  r5   r!   r   rD   rD   p   s               [    X# # # #
E0 E0 E0 E0Rq
 q
 q
 q
j8Q 8Q 8Q 8Q@ '+N0 N0 N0 N0 N0 N0`2 2 2 2&( &( &( &(P0 0 0 0*
 
 
 
$O O O O	2 	2 	2 	2
 
 
 
 
 
r!   rD   rg   c                    | dk    rdS 	 t                      }|                                }| |v r||                              dd          S n,# t          $ r t                              dd           Y nw xY wdS )zReturn the primary key property name for a given node label.

    Uses ``key_property`` from ``graph_schema.yaml`` if available,
    otherwise defaults to ``doc_id`` for Document and ``name`` for others.
    rA   rl   r[   r   z8Failed to determine key property, falling back to 'name'T)exc_info)r	   entity_type_mapr+   	Exceptionr-   debug)rg   re   et_maps      r   	_key_propr    s     
x`''))F??%=$$^V<<<  ` ` `OZ^_____` 6s   AA &A65A6r_   r   r   ro   c           
       K   |sdS t          |           t          |          }t          |           g }|D ]p}|                    |d          }|s>t                              d||t          |                                                     X|                    ||d           q|sdS d| d| d}	 |                     ||	           d{V  dS # t          $ re}t                              d
|t          |          t          |                     |D ]}	t          | ||	d                    d{V   Y d}~dS d}~ww xY w)u  批量 MERGE 同一 label 的节点，使用 UNWIND 减少网络往返。

    Batch merge nodes of the same label using UNWIND to minimise round-trips.
    Filters out items with empty/None key values and falls back to per-item
    execution if the entire batch fails.
    Nr&   batch_node_skip_empty_key)rg   rh   
props_keyskey_valr"   zUNWIND $batch AS item MERGE (n:r   z$: item.key_val}) SET n += item.propsr    batch_node_merge_failed_fallback)rg   
batch_sizeerrorr"   )r    r  r+   r-   r.   r   keysr   ra   r  r   r   _merge_node)
r_   rg   r   rh   r   r"   r  rj   excitems
             r   r   r     s        H""" #%E 
; 
;))Hb)) 	NN+!

--	     599:::: 		 	&	 	 	 =kk&k........... 
= 
= 
= 	.5zzc((	 	 	
 	
 	
  	= 	=Dgud7m<<<<<<<<<<	= 	= 	= 	= 	= 	=
=s   7C 
E AE  Er   r   r   r   c                2  K   |sdS t          |           t          |           t          |           t          |          }t          |          }t          |           t          |           g }|D ]}|                    dd          }	|                    dd          }
|	r|
s!t                              d||||	|
           S|                    |	|
|                    d          pi d           |sdS d	| d
| d| d
| d| d}	 |                     ||           d{V  dS # t          $ r`}t                              d|||t          |          t          |                     |D ]}t          | |           d{V  Y d}~dS d}~ww xY w)u  批量 MERGE 同一类型的关系，使用 UNWIND 减少网络往返。

    Batch merge relationships of the same (from_label, to_label, rel_type)
    using UNWIND to minimise round-trips.
    Falls back to per-item execution if the entire batch fails.
    Nr   r&   r   batch_rel_skip_empty_key)r   r   r   r   r   r   )r   r   r"   zUNWIND $batch AS item MATCH (a:r   z: item.from_key}) MATCH (b:z: item.to_key}) MERGE (a)-[r:z]->(b) SET r += item.propsr  batch_rel_merge_failed_fallback)r   r   r   r  r  )r    r  r+   r-   r.   r   ra   r  r   r   
_merge_rel)r_   r   r   r   r   from_key_propto_key_propr   r   r   r   rj   r  item_rels                 r   r   r     su        $$$""""""j))MH%%K'''%%% #%E  77:r**2&& 		v 		NN*%!!!      WW\**0b
 
 	 	 	 	  		 	#0	 		 	!,	 	 !	 	 	 0kk&k........... 0 0 0 	-!5zzc(( 	 	
 	
 	
 ! 	0 	0HWh//////////	0 	0 	0 	0 	0 	00s   D, ,
F6AFFc                J  K   	 t          |           t          |          }|                    |d          }|sdS |                     d| d| d||           d{V  dS # t          $ r5}t
                              d|t          |                     Y d}~dS d}~ww xY w)	z9Merge a single entity node.  Label must be pre-validated.r&   Nr   r   z: $key_val}) SET n += $propsr  node_merge_failed)rg   r  )r    r  r+   ra   r  r-   r.   r   )r_   rg   r"   r   r  r  s         r   r  r  =  s      I 	U###))C$$ 	FkkDDD#DDD  
 
 	
 	
 	
 	
 	
 	
 	
 	
 	

  I I I*%s3xxHHHHHHHHHIs   6A# %A# #
B"-*BB"r   c                  K   |d         }|d         }|d         }|d         }|d         }|                     d          pi }|r|sdS t          |           t          |           t          |           t          |          }t          |          }	d| d	| d
| d	|	 d| d}
	 |                     |
|||           d{V  dS # t          $ r7}t
                              d|||t          |                     Y d}~dS d}~ww xY w)zDMerge a single relationship.  Labels and type must be pre-validated.r   r   r   r   r   r   Nr   r   z: $from_key})
MATCH (b:z: $to_key})
MERGE (a)-[r:r   r   rel_merge_failed)r   r   r   r  )r+   r    r  ra   r  r-   r.   r   )r_   r   r   r   r   r   r   r   	from_propto_proprj   r  s               r   r!  r!  Q  s     \"J:H:H]F6{H%%+I 6  $$$""""""*%%I!!G	J 	 	9 	 		 	!(	 	 	 	 	 
kk	  
 
 	
 	
 	
 	
 	
 	
 	
 	
 	
  
 
 
!c(( 	 	
 	
 	
 	
 	
 	
 	
 	
 	

s   &C 
D,DDc                .    t          |           }| d| S )zDBuild the MERGE key clause based on the node label's uniqueness key.z	: $props.r  )rg   r"   r   s      r   r   r     s#    
E

C!!C!!!r!   r   	key_valueparam_prefixc                0    t          |           }| d| dS )u   Build a MATCH clause fragment for a node look-up by its primary key.

    根据 param_prefix 生成正确的参数引用（from_key 或 to_key），
    修复之前始终返回 $from_key 而忽略 $to_key 的逻辑缺陷。
    z: $_keyr,  )rg   r-  r.  r   s       r   r   r     s(     E

C((l((((r!   )r   r   r   r   )r"   r#   r   r#   )r   r2   )NN)r<   r=   r>   r=   r   r?   )rg   r   r   r   )r_   r   rg   r   r   ro   r   r?   )r_   r   r   r   r   r   r   r   r   ro   r   r?   )r_   r   rg   r   r"   r#   r   r?   )r_   r   r   r#   r   r?   )rg   r   r"   r#   r   r   )r   )rg   r   r-  r   r.  r   r   r   )(r  
__future__r   recollectionsr   typingr   neo4jr   r   
app.configr   app.core.graph_schema_loaderr	   app.utils.loggerr
   r  r-   r,   r0   compiler   r    r1   r6   r9   r   r:   __annotations__r;   rB   rD   r  r   r   r  r!  r   r   r5   r!   r   <module>r;     s[     # " " " " " 				 # # # # # #       1 1 1 1 1 1 1 1       3 3 3 3 3 3 ' ' ' ' ' '	H		 RQQ mmm  233      .* * * *
( ( ( (  #suu  $ $ $ $ SUU  " " " " $(!%0 0 0 0 0*r
 r
 r
 r
 r
 r
 r
 r
p   &3= 3= 3= 3=lI0 I0 I0 I0XI I I I(*
 *
 *
 *
^" " " ") ) ) ) ) ) )r!   