Skip to content

WIP: Implement COPY … FROM STDIN #566

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft

Conversation

ahoppen
Copy link

@ahoppen ahoppen commented Jun 24, 2025

This is still WIP. To do items include:

  • Test the various error cases, I have mostly focused on the success case so far
  • Test the backpressure support
  • Change the public API to accept the table + columns to copy into as well as options so that we can build the COPY query instead of letting the user write it
  • Check if we should buffer data sent through the PostgresCopyFromWriter to reduce the number of CopyData messages we need to send (and thus the protocol overhead). Alternatively, we can leave that kind of optimization to the client.
  • Generally take performance measurements

Deferred to follow-up PRs:

  • Add an API that allows binary transfer of data
  • Implement remaining options that can be passed to COPY FROM.

Fixes #290

This is still WIP. To do items include:
- [ ] Test the various error cases, I have mostly focused on the success case so far
- [ ] Test the backpressure support
- [ ] Change the public API to accept the table + columns to copy into as well as options so that we can build the `COPY` query instead of letting the user write it
- [ ] Add an API that allows binary transfer of data
/// A handle to send
public struct PostgresCopyFromWriter: Sendable {
private let channelHandler: NIOLoopBound<PostgresChannelHandler>
private let context: NIOLoopBound<ChannelHandlerContext>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use the handlerContext: ChannelHandlerContext? with ! in PostgresChannelHandler instead.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you prefer to make PostgresChannelHandler.handlerContext internal? Not sure whether it’s supposed to be private.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to pass the context to sendCopyDone or copyData. Instead use the context that already exists in the PostgresChannelHandler.handlerContext.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, not sure how I missed that.

@@ -88,7 +88,23 @@ struct ConnectionStateMachine {
case sendParseDescribeBindExecuteSync(PostgresQuery)
case sendBindExecuteSync(PSQLExecuteStatement)
case failQuery(EventLoopPromise<PSQLRowStream>, with: PSQLError, cleanupContext: CleanUpContext?)
/// Fail a query's execution by throwing an error on the given continuation.
case failQueryContinuation(any AnyErrorContinuation, with: PSQLError, cleanupContext: CleanUpContext?)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

existential, where Existential isn't necessary.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alternative is to have two cases, one for CheckedContinuation<PostgresCopyFromWriter, any Error> and one for CheckedContinuation<Void, any Error>. Since this is the failure case, it’s not performance sensitive and I’d argue that the improved readability is worth the extra allocation in the failure case. Happy to adjust if you disagree though.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this an enum instead? Best of both worlds?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can even implement the fail on it directly.

Comment on lines 183 to 185
context.flush()
}
context.write(self.wrapOutboundOut(self.encoder.flushBuffer()), promise: nil)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look right. It would mean we only flush once the channel has become not writable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support Copy In Mode
2 participants