0xSiO / bolt-rs

Communicate with Bolt-compatible graph databases in Rust! ⚡🔩

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Iteration over a result set in bolt_client

meiamsome opened this issue · comments

Currently the PullAll and Pull messages return a Vec of results instead of some form of iterator. This means that it is not possible to write streaming algorithms against older versions of Neo4j. Additionally, as the low-level read_message and send_message are only pub(crate) it isn't possible to implement this behaviour as a consumer.

In the case of Pull, this is probably fine as the user can specify a low n value to stream over results, but it might be worth changing both APIs to match anyway.

I like the idea of making the pull-type methods return a Stream of records. I'll have to mull over the design a bit more, since it may require a decent amount of change to the message parsing logic. Might rewrite it using nom and these parsers.

Some pseudo-code for me to reference later:

use futures_util::stream::TryStreamExt;
...

struct RecordStream { ... }

impl RecordStream {
    async fn summary(&mut self) -> Option<Result<Message>> { ... }
}

impl Stream for RecordStream {
    type Item = Result<Record>;

    ...
}

let mut stream: RecordStream = client.pull(...).await?;
while let Some(record) = stream.try_next().await? {
    ...
}
let summary_msg = stream.summary().await.unwrap()?;

Yeah, I came to that sort of conclusion too, but there is a bit of a catch - if you drop RecordStream before pulling the summary the client ends up in a weird state where it hasn't read some of the responses yet. When I was working on it locally this was the best I got: meiamsome@632269d

The basic idea is that in order to construct a RecordStream (I called it Pull in my code) you have to relinquish the Client entirely, so that the only way you can get the Client back is by finishing the RecordStream.

That's obviously problematic for use cases where the Client is not directly owned, but I couldn't think of a good way to ensure that the messages are exhausted except embedding a bunch of logic in to every other message handler to handle the out of sync case.

I hadn't implemented it as a Stream yet which is why I hadn't mentioned it here yet, but it should be fairly trivial to do that instead.

Off the top of my head, it's probably fine if the user doesn't consume all the records in the stream - there are messages still left to read, but I think those can be discarded with a RESET.

Writing a Stream impl without having the necessary poll-type methods available doesn't seem possible to me without some funny business, like polling stored futures or relinquishing ownership of the Client. I'm considering this feature blocked by the message parsing logic unless I think of another solution.

Even the way RESET is currently implemented wouldn't work, though - the issue is that all of the methods currently assume the incoming queue of messages will be in a clean slate, so if we leave messages in the queue everything is out of sync. In the case of RESET it just reads the next message, which would probably be a RECORD or the SUCCESS/FAILURE result of the dropped ResultStream.

Essentially, the way everything is written currently assumes that everything will read the correct amount of messages - which is totally fine - but means that you can't drop ResultStream (Or more specifically the borrow on the client) until the incoming message queue is back in to a valid (empty) state.

Additionally, it's likely there are plenty of times you wouldn't want to RESET (Inside of a TX that you don't want to cancel, for example).

Ah, yep - my mistake, and changing the parsing wouldn't help much in that case. This sort of cleanup sounds like something an async Drop would help with, but that concept comes with its own set of issues.

I've been avoiding tracking server state since it wasn't specified in the docs I used to write this library, but with the newer protocol docs now clearly specifying the Bolt server states and transitions, this is probably the biggest thing to address next.