Ë
    kQêjà+  ã                  ó  — d Z ddlmZ ddlmZmZmZmZmZm	Z	m
Z
 ddlZddlmZ erddlmZ  e
d«      Zd	Zd
Zh d£Zdd„ZdZdZdd„Zdd„Z	 	 	 	 	 	 dd„Zdd„Z G d„ d«      Zdd„Z	 	 	 	 	 	 	 	 dd„Z	 	 	 	 	 	 	 	 dd„Zy) a¶  WAL-PK-FUSION: hybrid search's primary-key fallback for RTE tables.

Hybrid search joins its vector and full-text legs on `_rowid`, which an RTE
table cannot supply: the fresh tier has no stable row id, so the server rejects
`with_row_id` before it plans. When that happens the legs are joined on a
surrogate `_rowid` built from the table's primary key instead.

This is a stopgap until RTE supports `_rowid`, and everything it needs lives
here so it can be removed in one pass:

1. Delete this file and `tests/test_wal_hybrid.py`.
2. Run `grep -rn WAL-PK-FUSION python/ rust/` and follow the note at each
   marker; the Rust side has its own copy in
   `rust/lancedb/src/query/wal_fusion.rs`.
é    )Úannotations)ÚTYPE_CHECKINGÚ	AwaitableÚCallableÚListÚOptionalÚTupleÚTypeVarNé   )Úblob_v2_projection_sources)ÚQueryProjectionÚTs#   lance-schema:unenforced-primary-keys,   lance-schema:unenforced-primary-key:position>   Ú1ÚyÚonÚyesÚtruec                óÀ  — g }t        | «      D ]š  \  }}|j                  xs i }|j                  t        «      }|�	 t	        |«      }|€>|j                  t        «      }|�$|j                  «       j                  «       t        vrŒwd}|j                  |dk(  |||j                  f«       Œœ t        |«      D ��cg c]  �^ }}|‘Œ	 c}}S # t
        $ r d}Y Œ�w xY wc c}}w )z÷The unenforced primary key columns of `schema`, in key order.

    Mirrors Lance's ordering: fields with an explicit 1-based position first, in
    position order, then fields carrying only the legacy boolean marker, in
    declaration order.
    Nr   )Ú	enumerateÚmetadataÚgetÚ_PK_POSITION_MARKERÚintÚ
ValueErrorÚ
_PK_MARKERÚdecodeÚlowerÚ_TRUTHYÚappendÚnameÚsorted)	ÚschemaÚkeyedÚindexÚfieldr   ÚpositionÚlegacyÚ_r    s	            úX/home/oem/CyberBrain26-unified/.venv/lib/python3.12/site-packages/lancedb/_wal_hybrid.pyÚ_pk_columnsr*   *   sß   € ð €EÜ! &Ó)ò C‰ˆˆuØ—>‘>Ò' RˆØ—<‘<Ô 3Ó4ˆØÐð Ü˜x›=�ð ÐØ—\‘\¤*Ó-ˆFØˆ~ §¡£×!6Ñ!6Ó!8ÄÑ!GØØˆHØ�‰�h !‘m X¨u°e·j±jÐAÕBðCô "(¨£×/‘X�a˜ŠDÓ/Ð/øô ò  Ø’ð üó 0s   ¼C	Â9CÃ	CÃCzdoes not support with_row_idzúhybrid search on an RTE table cannot return _rowid: the fresh tier has no stable row id, and the ids the fusion joins on are synthesized from the primary key. Set use_rte(False) to read the base table only (results will exclude un-compacted RTE data)c                ó$   — t         t        | «      v S )zŠWhether `error` is the server declining `_rowid` because the table is
    RTE-backed, as opposed to any other failure the query might hit.)Ú_WAL_ROW_ID_REFUSALÚstr)Úerrors    r)   Ú_is_wal_row_id_refusalr/   T   s   € ô ¤# e£*Ð,Ð,ó    c                ó,  — | s| g fS t        | t        t        f«      r$t        | d   t        t        f«      rt        | «      } |D �cg c]	  }|| vsŒ|‘Œ }}t        | t        «      ri | ¥|D �ci c]  }||“Œ c}¥|fS t        | «      |z   |fS c c}w c c}w )záAdd the key columns a projection is missing.

    Returns the projection to use and the columns added, which the caller drops
    from the output again so an injected key never reaches a caller who did not
    select it.
    r   )Ú
isinstanceÚlistÚtupleÚdict)ÚcolumnsÚ
pk_columnsÚcÚmissings       r)   Ú_with_pk_columnsr:   Z   s�   € ñ à˜ˆ{Ðä�'œD¤%˜=Ô)¬j¸À¹ÄdÌEÀ]Ô.SÜ�w“-ˆØ$Ö9�Q¨°Ò(8ŠqÐ9€GÐ9Ü�'œ4Ô Ø5�'Ð5¨GÖ4 q˜a ™dÒ4Ð5°wÐ>Ð>Ü�‹=˜7Ñ" GÐ+Ð+ùò :ùâ4s   Á	BÁBÁ,
Bc                ó:  ‡	— |st        d«      ‚i }g }| D ]ù  Š	‰	j                  dk(  r*t        j                  g t        j                  «       ¬«      }n�|D �cg c]  }|‰	j
                  vsŒ|‘Œ }}|rt        d|› d‰	j                  › d�«      ‚t        ˆ	fd„|D «       Ž }t        j                  |D �cg c]  }|j                  |t        |«      «      ‘Œ c}t        j                  «       ¬«      }|j                  ‰	j                  d|«      «       Œû |S c c}w c c}w )	u³  Give each table a `_rowid` column derived from its primary key.

    The fusion only ever compares that column for equality â€” to deduplicate, to
    group by in a reranker, and to restore the original scores â€” so on an RTE
    table, where the server has no stable row id to hand out, any injective
    mapping of the primary key does the same job. Ids are assigned in first-seen
    order across `tables`, so passing them in fusion order (vector, then FTS)
    preserves "first occurrence wins" and its tie break.

    Writing it under the `_rowid` name is what lets every reranker, third-party
    implementations included, run unmodified. The column never reaches the
    caller.
    zmhybrid search on an RTE table needs an unenforced primary key to deduplicate on, and this table declares noner   )Útypez;hybrid search could not deduplicate: primary key column(s) z  missing from a result set with z rowsc              3  ó\   •K  — | ]#  }‰j                  |«      j                  «       –— Œ% y ­w©N)ÚcolumnÚ	to_pylist)Ú.0r8   Útables     €r)   ú	<genexpr>z+_stamp_surrogate_row_ids.<locals>.<genexpr>’   s"   øè ø€ ÒI¸˜Ÿ™ a›×2Ñ2×4ÑIùs   ƒ),Ú_rowid)r   Únum_rowsÚpaÚarrayÚuint64Úcolumn_namesÚzipÚ
setdefaultÚlenr   Úappend_column)
Útablesr7   ÚidsÚstampedÚrow_idsr8   r9   ÚkeysÚkeyrB   s
            @r)   Ú_stamp_surrogate_row_idsrT   m   s  ø€ ñ  Üð;ó
ð 	
ð
 €CØ€GØò ?ˆØ�>‰>˜QÒô —h‘h˜r¬¯	©	«Ô4‰Gà",ÖL˜Q°¸×9KÑ9KÒ0K’qÐLˆGÐLÙÜ ð!Ø!( 	Ð)IØ—~‘~Ð& eð-óð ô
 ÓI¸jÔIÐJˆDÜ—h‘hØ:>Ö?°3�—‘ ¤S¨£XÕ.Ò?ÄbÇiÁiÃkôˆGð 	�‰�u×*Ñ*¨8°WÓ=Õ>ð#?ð$ €Nùò Mùò @s   ÁDÁ)DÂ1"D
c                ó4   — | �|du ry| j                  «       du S )zºWhether a previous hybrid query on `table` was refused `_rowid`, so this
    one should go straight to the primary key.

    Learned from a refusal, never probed, so asking is free.
    FT)Ú_hybrid_pk_fusion_learned)rB   Úuse_rtes     r)   Úpk_fusion_learnedrX   š   s)   € ð €}˜ 5Ñ(Øð ×*Ñ*Ó,°Ð4Ð4r0   c                  ó\   — e Zd ZdZdd„Ze	 	 	 	 	 	 	 	 d	d„«       Zd„ Z	 	 	 	 	 	 d
d„Zdd„Z	y)ÚPkFusionzrHow the legs of a hybrid query on an RTE table are joined: on a
    surrogate `_rowid` built from the primary key.c                ó    — || _         g | _        y r>   )r7   Úinjected)Úselfr7   s     r)   Ú__init__zPkFusion.__init__«   s   € Ø$ˆŒð $&ˆ�r0   c               ót   — |rt        t        «      ‚t        ||«      rt        d«      ‚ | t        |«      «      S )zZResolve the key for a hybrid query, refusing the shapes that need a
        real `_rowid`.záhybrid search cannot project a blob column on an RTE table: fetching blobs needs a real _rowid, and the fresh tier has no stable one. Set use_rte(False) to read the base table only (results will exclude un-compacted RTE data))ÚNotImplementedErrorÚWAL_ROW_ID_UNSUPPORTEDr   r*   )Úclsr"   Ú
projectionÚcaller_requested_row_ids       r)   Ú	for_queryzPkFusion.for_query±   sC   € ñ #Ü%Ô&<Ó=Ð=ô & f¨jÔ9Ü%ð?óð ñ ”;˜vÓ&Ó'Ð'r0   c                óB   — t        || j                  «      \  }| _        |S )zt`columns` with the key columns it is missing, to project in place of
        the `_rowid` an RTE table would refuse.)r:   r7   r\   )r]   r6   s     r)   ÚinjectzPkFusion.injectÈ   s    € ô "2°'¸4¿?¹?Ó!KÑˆ�”Øˆr0   c                ó@   — t        ||g| j                  «      \  }}||fS )z7Give both legs a surrogate `_rowid` built from the key.)rT   r7   )r]   Úvector_resultsÚfts_resultss      r)   ÚstampzPkFusion.stampÎ   s.   € ô '?Ø˜[Ð)¨4¯?©?ó'
Ñ#ˆ˜ð ˜{Ð*Ð*r0   c                óŒ   — dg| j                   ¢D �cg c]  }||j                  v sŒ|‘Œ }}|r|j                  |«      S |S c c}w )zäDrop the surrogate and any injected key columns from the reranked
        results.

        The surrogate is an internal join key, never an answer, and a caller
        who asked for `_rowid` was refused in `for_query`.
        rD   )r\   rI   Údrop)r]   Úresultsr8   rm   s       r)   ÚstripzPkFusion.stripÙ   sL   € ð %Ð5 t§}¡}Ð5ÖS�a¸¸g×>RÑ>RÒ9R’ÐSˆÐSÙ%)ˆw�|‰|˜DÓ!Ð6¨wÐ6ùò Ts
   ’A¦AN©r7   ú	List[str])r"   ú	pa.Schemarc   r   rd   ÚboolÚreturnrZ   )ri   úpa.Tablerj   ru   rt   zTuple[pa.Table, pa.Table])rn   ru   rt   ru   )
Ú__name__Ú
__module__Ú__qualname__Ú__doc__r^   Úclassmethodre   rg   rk   ro   © r0   r)   rZ   rZ   §   sm   „ ñ6ó&ð ð(àð(ð $ð(ð
 "&ð(ð 
ò(ó ð(ò,ð	+Ø&ð	+Ø5=ð	+à	"ó	+ô7r0   rZ   c               óF   — |st        | «      s| ‚|rt        t        «      | ‚y)z\Re-raise `error` unless it is an RTE `_rowid` refusal worth retrying on
    the primary key.N)r/   r`   ra   )r.   Úfusedrd   s      r)   Ú_check_refusalr~   ä   s,   € ñ Ô*¨5Ô1Øˆñ Ü!Ô"8Ó9¸uÐDð r0   c               ó’   — 	  | «       S # t         $ r4}t        | |«       |¬«       |j                  «         | «       cY d}~S d}~ww xY w)aÌ  Run a hybrid query, retrying on the primary key if the table refuses
    `_rowid`.

    `_rowid` is asked for first rather than paying a round trip up front to
    find out: only an RTE table refuses, and the refusal has to be handled
    anyway -- a write spec installed elsewhere can arrive between any two
    queries. It costs nothing server-side: it is raised before the query is
    planned. `fused` reports whether the attempt already used the key.
    ©r}   rd   N©Ú	Exceptionr~   Ú_note_hybrid_pk_fusion©ÚrunrB   r}   rd   Úes        r)   Úwith_pk_fallbackr‡   ð   sE   € ð Ù‹uˆøÜò ÜØ‘U“WÐ6Mõ	
ð 	×$Ñ$Ô&Ù‹u�ûðús   ‚	 ‰	A’)A»AÁAc             ƒ  óÀ   K  — 	  | «       ƒ d{  –—† S 7 Œ# t         $ r=}t        | |«       |¬«       |j                  «         | «       ƒ d{  –—†7  cY d}~S d}~ww xY w­w)z%The async form of `with_pk_fallback`.Nr€   r�   r„   s        r)   Úwith_pk_fallback_asyncr‰     sR   è ø€ ðÙ“U�{Ðˆ{ùÜò ÜØ‘U“WÐ6Mõ	
ð 	×$Ñ$Ô&Ù“U�{ˆ{ÕûðüsH   ‚A„
 Ž� ’A“ •	Až-AÁAÁAÁAÁAÁAÁA)r"   rr   rt   rq   )r.   ÚBaseExceptionrt   rs   rp   )rN   úList[pa.Table]r7   rq   rt   r‹   )rW   zOptional[bool]rt   rs   )r.   r‚   r}   rs   rd   rs   )r…   zCallable[[], T]r}   úCallable[[], bool]rd   rs   rt   r   )r…   zCallable[[], Awaitable[T]]r}   rŒ   rd   rs   rt   r   ) ry   Ú
__future__r   Útypingr   r   r   r   r   r	   r
   ÚpyarrowrF   Ú_blobr   Útypesr   r   r   r   r   r*   r,   ra   r/   r:   rT   rX   rZ   r~   r‡   r‰   r{   r0   r)   ú<module>r’      së   ðñõ  #ç U× UÑ Uã å -áÝ&áˆCƒL€ð 4€
ØEÐ Ú
)€ó0ð@ 5Ð ð*ð ó-ó,ð&*Øð*Ø(1ð*àó*óZ
5÷:7ñ :7óz	EðØ	ðð ð	ð
 "ðð óð6Ø	#ðð ð	ð
 "ðð ôr0   