@@ -185,21 +185,8 @@ let processNodeRecord (exportState: ExportState) (recordCtx: RecordContext<Batch
185185
186186 exportState.NodeIdMapping.TryAdd( elementId, stableId) |> ignore
187187
188- writeNode writer { new INode with
189- member _.ElementId = elementId
190- member _.Labels = labels :> IReadOnlyList < string >
191- member _.Properties = properties
192- member _.Item with get( key : string ) = properties.[ key]
193- member _.Get < 'T >( key : string ) : 'T = properties.[ key] :?> 'T
194- member _.TryGet < 'T >( key : string , [<System.Runtime.InteropServices.Out>] value : byref < 'T >) =
195- let success , v = properties.TryGetValue( key)
196- if success && v : ? 'T then
197- value <- unbox< 'T> v
198- true
199- else
200- false
201- member _.Id = 0 L // Not used, but required by interface
202- member _.Equals ( other : INode ) = false } elementId stableId ctx
188+ // Use direct serialization to avoid object allocation in hot path
189+ writeNodeDirect writer elementId stableId ( labels :> IReadOnlyList< string>) properties ctx
203190 with ex ->
204191 let elementId = " "
205192
@@ -310,25 +297,8 @@ let processRelationshipRecord
310297 EndElementId = endNodeId
311298 EndStableId = endStableId } // End node content hash
312299
313- writeRelationship writer { new IRelationship with
314- member _.ElementId = elementId
315- member _.Type = relType
316- member _.StartNodeElementId = startNodeId
317- member _.EndNodeElementId = endNodeId
318- member _.Properties = properties
319- member _.Item with get( key : string ) = properties.[ key]
320- member _.Get < 'T >( key : string ) : 'T = properties.[ key] :?> 'T
321- member _.TryGet < 'T >( key : string , [<System.Runtime.InteropServices.Out>] value : byref < 'T >) =
322- let success , v = properties.TryGetValue( key)
323- if success && v : ? 'T then
324- value <- unbox< 'T> v
325- true
326- else
327- false
328- member _.Id = 0 L // Not used, but required by interface
329- member _.StartNodeId = 0 L // Not used, but required by interface
330- member _.EndNodeId = 0 L // Not used, but required by interface
331- member _.Equals ( other : IRelationship ) = false } ids ctx
300+ // Use direct serialization to avoid object allocation in hot path
301+ writeRelationshipDirect writer relType properties ids ctx
332302 with ex ->
333303 // Failed to parse relationship from record
334304 trackSerializationErrorDedup recordCtx.ErrorAccumulator ex " " " relationship" " RecordAccessError"
@@ -383,19 +353,19 @@ module private KeysetPagination =
383353 /// Extract ID from record for pagination
384354 let extractId ( version : Neo4jVersion ) ( entityType : string ) ( record : IRecord ) : KeysetId option =
385355 try
386- let fieldName =
387- match entityType with
388- | " Nodes" -> " nodeId"
389- | " Relationships" -> " relId"
390- | _ -> failwithf " Unknown entity type: %s " entityType
391-
392356 match version with
393357 | V4x ->
358+ // Neo4j 4.x uses different field names for nodes and relationships
359+ let fieldName =
360+ match entityType with
361+ | " Nodes" -> " nodeId"
362+ | " Relationships" -> " relId"
363+ | _ -> failwithf " Unknown entity type: %s " entityType
394364 let id = record.[ fieldName]. As< int64>()
395365 Some( NumericId id)
396- | V5x
397- | V6x ->
398- let id = record.[ fieldName ]. As< string>()
366+ | V5x | V6x ->
367+ // Neo4j 5.x+ uses elementId for both nodes and relationships
368+ let id = record.[ " elementId " ]. As< string>()
399369 Some( ElementId id)
400370 | Unknown -> None
401371 with ex ->
@@ -629,7 +599,16 @@ let processBatchedQuery<'state>
629599
630600 // Continue if we got a full batch
631601 if recordCount = batchSize then
632- return ! processBatch batchStats newLastProgress nextPaginationState currentHandlerState
602+ // Critical check: Prevent infinite loop when using keyset pagination
603+ match paginationState, nextPaginationState with
604+ | Keyset( prevId, _), Keyset( nextId, _) when prevId = nextId ->
605+ // We processed a full batch but couldn't advance the pagination cursor
606+ // This means ID extraction failed for all records in the batch
607+ return Error( PaginationError(
608+ batchCtx.Processor.EntityName,
609+ sprintf " Unable to advance pagination after processing %d records. This typically occurs when the ID field (nodeId/relId) cannot be extracted from query results. Check that your Neo4j query returns the expected fields." recordCount))
610+ | _ ->
611+ return ! processBatch batchStats newLastProgress nextPaginationState currentHandlerState
633612 else
634613 return Ok( batchStats, currentHandlerState)
635614 }
0 commit comments