Skip to content
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

simulate server side cursors #228

Merged
merged 56 commits into from
Apr 4, 2023
Merged

simulate server side cursors #228

merged 56 commits into from
Apr 4, 2023

Conversation

jycor
Copy link

@jycor jycor commented Mar 21, 2023

To be able to use the newest MySQL foreign data wrapper for Postgres, we have to support server side cursors.
This PR emulates them, and hopefully gets us far enough...

Additionally, this includes a fix for Windows clients (and other clients that don't have the DeprecatedEOF client capability flag set) where we send an extra EOF packet when we shouldn't.

Fix for: dolthub/dolt#5441
And this: dolthub/dolt#3029
This one too (kinda): dolthub/dolt#4840

@jycor jycor requested a review from zachmu as a code owner March 21, 2023 21:37
Copy link

@reltuk reltuk left a comment

Choose a reason for hiding this comment

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

I think this is a great start. Adding some comments...

go/mysql/conn.go Outdated Show resolved Hide resolved
go/mysql/conn.go Outdated Show resolved Hide resolved
go/mysql/conn.go Outdated
@@ -1030,6 +1050,7 @@ func (c *Conn) handleNextCommand(handler Handler) error {
}
}
case ComPrepare:
log.Info("Received COM_PREPARE")
Copy link

Choose a reason for hiding this comment

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

The server connection has become quite a bit more stateful now, because it can be in this cursor-exists state with the query running in the background. I think almost all of these cases need to check for conn.cs != nil and do the right thing...I'm not exactly sure what the right thing is for each of them. Maybe connection out of sync error and close the connection?

Seems like ComStmtReset should definitely cancel the query, ensure the query is canceled by selecting on its done channel, and only return success after that's done.

conn.StatusFlags will need to be handled carefully across anywhere the conn.cs can go from != nil to == nil, since we need to make sure CursorExists is getting cleared.

go/mysql/conn.go Outdated Show resolved Hide resolved
go/mysql/conn.go Outdated Show resolved Hide resolved
go/mysql/conn.go Outdated Show resolved Hide resolved
go/mysql/conn.go Outdated Show resolved Hide resolved
go/mysql/conn.go Outdated
return err
}
// indicate that there is no more data, and prevent blocking from reading c.cs.done for errors again
c.cs.noData = true
Copy link

Choose a reason for hiding this comment

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

It seems better to clear c.cs here...

go/mysql/conn.go Outdated Show resolved Hide resolved

next chan *sqltypes.Result
done chan error
quit chan error
Copy link

Choose a reason for hiding this comment

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

As written we don't currently cancel the query anywhere by sending on this channel. As mentioned below though, I think we actually need to sometimes. When we do do that, we probably need to select like:

select {
  conn.cs.quit <- errToCancelTheQueryHere:
  errWhichWonTheRace := <- conn.cs.done:
}

in order to avoid deadlocking...

@jycor jycor requested a review from reltuk March 28, 2023 19:15
Copy link

@reltuk reltuk left a comment

Choose a reason for hiding this comment

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

Took another pass at comments. Looking better and better. Really close now :).

go/mysql/conn.go Outdated
if !ok {
// check if there was an error
if err = <- c.cs.done; err != nil {
if werr := c.writeErrorPacketFromError(err); werr != nil {
Copy link

Choose a reason for hiding this comment

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

Should c.cs get set to nil here?

case ComStmtExecute:
// flush is called at the end of this block.
// To simplify error handling, we do not
// encapsulate it with a defer'd func()
c.startWriterBuffering()

queryStart := time.Now()
stmtID, _, err := c.parseComStmtExecute(c.PrepareData, data)
stmtID, cursorType, err := c.parseComStmtExecute(c.PrepareData, data)
Copy link

Choose a reason for hiding this comment

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

I think we need to prevent ComStmtExecute, ComStmtPrepare and ComStmtQuery while a single cursor is outstanding. Thoughts?

Copy link
Author

Choose a reason for hiding this comment

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

I think that makes sense for ComStmtExecute, since that would open another cursor, potentially causing problems, especially since we're only going to support one cursor at a time. I don't think ComStmtPrepare would be affected by any open cursors. ComStmtQuery might cause problems if the query affects the table the cursor is reading from, like inserting or updating rows.

Copy link

Choose a reason for hiding this comment

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

Yeah, I think there are situations where it would be fine to let Prepares run, but my concerns is with adding concurrency on the session when there wasn't any previously. For example, I'm pretty sure the database catalog has a transactional view of the schemas of the tables being queried, and that's referenced in the analyzer. It seems most expedient to just disallow it for now, assuming existing clients don't rely on this.

go/mysql/conn.go Show resolved Hide resolved
go/mysql/conn.go Show resolved Hide resolved
go/mysql/conn.go Outdated Show resolved Hide resolved
go/mysql/conn.go Outdated Show resolved Hide resolved
go/mysql/conn.go Outdated

go func() {
err = handler.ComStmtExecute(c, prepare, func(qr *sqltypes.Result) error {
defer func(){
Copy link

Choose a reason for hiding this comment

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

This whole defer block goes outside the ComStmtExecute call.

Copy link

Choose a reason for hiding this comment

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

What's our testing strategy here in general? I don't think we have enginetests which currently test this code path. We need to make sure we have tests in place that are testing this code path with larger-than-128-rows result sets, where the placement of this defer would have been clearly wrong.

go/mysql/conn.go Outdated Show resolved Hide resolved
Copy link

@reltuk reltuk left a comment

Choose a reason for hiding this comment

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

Looks quite reasonable. A few comments.

go/mysql/conn.go Outdated
if !ok {
// check if there was an error
if err, ok = <- c.cs.done; ok {
close(c.cs.done)
Copy link

Choose a reason for hiding this comment

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

If we ever want this to close, we should have the sender close it. This server code should be structured to only ever successfully receive from it once though.

case ComStmtExecute:
// flush is called at the end of this block.
// To simplify error handling, we do not
// encapsulate it with a defer'd func()
c.startWriterBuffering()

queryStart := time.Now()
stmtID, _, err := c.parseComStmtExecute(c.PrepareData, data)
stmtID, cursorType, err := c.parseComStmtExecute(c.PrepareData, data)
Copy link

Choose a reason for hiding this comment

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

Yeah, I think there are situations where it would be fine to let Prepares run, but my concerns is with adding concurrency on the session when there wasn't any previously. For example, I'm pretty sure the database catalog has a transactional view of the schemas of the tables being queried, and that's referenced in the analyzer. It seems most expedient to just disallow it for now, assuming existing clients don't rely on this.

@jycor jycor merged commit 8d863f0 into main Apr 4, 2023
@Hydrocharged Hydrocharged deleted the james/cursors branch February 7, 2024 13:45
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.

2 participants