-
Notifications
You must be signed in to change notification settings - Fork 136
feat(arrow/flight/sql): Add is_update field to ActionCreatePreparedStatementResult #732
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
base: main
Are you sure you want to change the base?
Changes from all commits
09e063c
1dacbb2
0dc7515
a92965a
170b38d
3466969
2c8c152
8c7286c
c6f0a54
0288679
25bed59
fcde3da
a2d9dc5
eb16088
8ba207b
8ce4339
786adc7
4816b1b
c1bc900
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -406,6 +406,8 @@ func (s *FlightSqlClientSuite) TestPreparedStatementExecute() { | |
| defer prepared.Close(context.TODO(), s.callOpts...) | ||
|
|
||
| s.Equal(string(prepared.Handle()), "query") | ||
| _, ok := prepared.IsUpdate() | ||
| s.False(ok) | ||
|
|
||
| info, err := prepared.Execute(context.TODO(), s.callOpts...) | ||
| s.NoError(err) | ||
|
|
@@ -416,13 +418,133 @@ func (s *FlightSqlClientSuite) TestPreparedStatementExecute() { | |
|
|
||
| secondPrepare := flightsql.NewPreparedStatement(&s.sqlClient, prepared.Handle()) | ||
| s.Equal(string(secondPrepare.Handle()), "query") | ||
| _, ok = secondPrepare.IsUpdate() | ||
| s.False(ok) | ||
|
|
||
| defer secondPrepare.Close(context.TODO(), s.callOpts...) | ||
|
|
||
| info, err = secondPrepare.Execute(context.TODO(), s.callOpts...) | ||
| s.NoError(err) | ||
| s.Equal(&emptyFlightInfo, info) | ||
| } | ||
|
|
||
| func (s *FlightSqlClientSuite) TestPreparedStatementExecuteWithIsUpdateFalse() { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add a test for the unset/ Both new client tests here, and both new server tests, assert
val, ok := prepared.IsUpdate()
s.False(ok)
s.False(val)Worth locking down explicitly because a future refactor that switched
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want the
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I addressed this in eb16088 I do not think it makes sense to have the check in |
||
| const query = "query" | ||
|
|
||
| cmd := &pb.ActionCreatePreparedStatementRequest{Query: query} | ||
| action := getAction(cmd) | ||
| action.Type = flightsql.CreatePreparedStatementActionType | ||
| closeAct := getAction(&pb.ActionClosePreparedStatementRequest{PreparedStatementHandle: []byte(query)}) | ||
| closeAct.Type = flightsql.ClosePreparedStatementActionType | ||
|
|
||
| isUpdate := false | ||
| result := &pb.ActionCreatePreparedStatementResult{ | ||
| PreparedStatementHandle: []byte(query), // handle is the query string for simplicity | ||
| IsUpdate: &isUpdate, | ||
| } | ||
| var out anypb.Any | ||
| out.MarshalFrom(result) | ||
| data, _ := proto.Marshal(&out) | ||
|
|
||
| createRsp := &mockDoActionClient{} | ||
| defer createRsp.AssertExpectations(s.T()) | ||
| createRsp.On("Recv").Return(&pb.Result{Body: data}, nil).Once() | ||
| createRsp.On("Recv").Return(&pb.Result{}, io.EOF).Once() | ||
| createRsp.On("CloseSend").Return(nil).Once() | ||
|
|
||
| closeRsp := &mockDoActionClient{} | ||
| defer closeRsp.AssertExpectations(s.T()) | ||
| closeRsp.On("Recv").Return(&pb.Result{}, io.EOF) | ||
| closeRsp.On("CloseSend").Return(nil) | ||
|
|
||
| s.mockClient.On("DoAction", flightsql.CreatePreparedStatementActionType, action.Body, s.callOpts). | ||
| Return(createRsp, nil).Once() | ||
| s.mockClient.On("DoAction", flightsql.ClosePreparedStatementActionType, closeAct.Body, s.callOpts). | ||
| Return(closeRsp, nil) | ||
|
|
||
| infoCmd := &pb.CommandPreparedStatementQuery{PreparedStatementHandle: []byte(query)} | ||
| desc := getDesc(infoCmd) | ||
| s.mockClient.On("GetFlightInfo", desc.Type, desc.Cmd, s.callOpts).Return(&emptyFlightInfo, nil).Once() | ||
|
|
||
| prepared, err := s.sqlClient.Prepare(context.TODO(), query, s.callOpts...) | ||
| s.NoError(err) | ||
| defer prepared.Close(context.TODO(), s.callOpts...) | ||
|
|
||
| s.Equal(string(prepared.Handle()), query) | ||
| val, ok := prepared.IsUpdate() | ||
| s.Require().True(ok) | ||
| s.Require().False(val) | ||
|
|
||
| info, err := prepared.Execute(context.TODO(), s.callOpts...) | ||
| s.NoError(err) | ||
| s.Equal(&emptyFlightInfo, info) | ||
| } | ||
|
|
||
| func (s *FlightSqlClientSuite) TestPreparedStatementExecuteUpdateWithIsUpdateTrue() { | ||
| const query = "DML query" | ||
|
|
||
| cmd := &pb.ActionCreatePreparedStatementRequest{Query: query} | ||
| action := getAction(cmd) | ||
| action.Type = flightsql.CreatePreparedStatementActionType | ||
| closeAct := getAction(&pb.ActionClosePreparedStatementRequest{PreparedStatementHandle: []byte(query)}) | ||
| closeAct.Type = flightsql.ClosePreparedStatementActionType | ||
|
|
||
| // Set is_update to true | ||
| isUpdate := true | ||
| result := &pb.ActionCreatePreparedStatementResult{ | ||
| PreparedStatementHandle: []byte(query), | ||
| IsUpdate: &isUpdate, | ||
| } | ||
| var out anypb.Any | ||
| out.MarshalFrom(result) | ||
| data, _ := proto.Marshal(&out) | ||
|
|
||
| createRsp := &mockDoActionClient{} | ||
| defer createRsp.AssertExpectations(s.T()) | ||
| createRsp.On("Recv").Return(&pb.Result{Body: data}, nil).Once() | ||
| createRsp.On("Recv").Return(&pb.Result{}, io.EOF).Once() | ||
| createRsp.On("CloseSend").Return(nil).Once() | ||
|
|
||
| closeRsp := &mockDoActionClient{} | ||
| defer closeRsp.AssertExpectations(s.T()) | ||
| closeRsp.On("Recv").Return(&pb.Result{}, io.EOF) | ||
| closeRsp.On("CloseSend").Return(nil) | ||
|
|
||
| s.mockClient.On("DoAction", flightsql.CreatePreparedStatementActionType, action.Body, s.callOpts). | ||
| Return(createRsp, nil).Once() | ||
| s.mockClient.On("DoAction", flightsql.ClosePreparedStatementActionType, closeAct.Body, s.callOpts). | ||
| Return(closeRsp, nil) | ||
|
|
||
| // Mock DoPut for ExecuteUpdate | ||
| updateCmd := &pb.CommandPreparedStatementUpdate{PreparedStatementHandle: []byte(query)} | ||
| updateDesc := getDesc(updateCmd) | ||
| updateResult := &pb.DoPutUpdateResult{RecordCount: 1} | ||
| resdata, _ := proto.Marshal(updateResult) | ||
|
|
||
| mockedPut := &mockDoPutClient{} | ||
| defer mockedPut.AssertExpectations(s.T()) | ||
| mockedPut.On("Send", mock.MatchedBy(func(fd *flight.FlightData) bool { | ||
| return proto.Equal(updateDesc, fd.FlightDescriptor) | ||
| })).Return(nil) | ||
| mockedPut.On("CloseSend").Return(nil) | ||
| mockedPut.On("Recv").Return(&pb.PutResult{AppMetadata: resdata}, nil) | ||
| s.mockClient.On("DoPut", s.callOpts).Return(mockedPut, nil) | ||
|
|
||
| prepared, err := s.sqlClient.Prepare(context.TODO(), query, s.callOpts...) | ||
| s.NoError(err) | ||
| defer prepared.Close(context.TODO(), s.callOpts...) | ||
|
|
||
| s.Equal(string(prepared.Handle()), query) | ||
| val, ok := prepared.IsUpdate() | ||
| s.Require().True(ok) | ||
| s.Require().True(val) | ||
|
|
||
| // Execute as update | ||
| num, err := prepared.ExecuteUpdate(context.TODO(), s.callOpts...) | ||
| s.NoError(err) | ||
| s.EqualValues(1, num) | ||
| } | ||
|
|
||
| func (s *FlightSqlClientSuite) TestPreparedStatementExecuteParamBinding() { | ||
| const query = "query" | ||
| const handle = "handle" | ||
|
|
@@ -881,9 +1003,31 @@ func (s *FlightSqlClientSuite) TestPreparedStatementLoadFromResult() { | |
| s.NoError(err) | ||
| defer datasetRec.Release() | ||
|
|
||
| _, ok := prepared.IsUpdate() | ||
| s.False(ok) | ||
|
|
||
| s.Equal(string(prepared.Handle()), "query") | ||
| } | ||
|
|
||
| func (s *FlightSqlClientSuite) TestPreparedStatementLoadFromResultWithIsUpdate() { | ||
| const query = "query" | ||
|
|
||
| isUpdate := true | ||
| result := &pb.ActionCreatePreparedStatementResult{ | ||
| PreparedStatementHandle: []byte(query), | ||
| IsUpdate: &isUpdate, | ||
| } | ||
|
|
||
| prepared, err := s.sqlClient.LoadPreparedStatementFromResult(result) | ||
| s.NoError(err) | ||
|
|
||
| s.Equal(string(prepared.Handle()), "query") | ||
|
|
||
| val, ok := prepared.IsUpdate() | ||
| s.True(ok) | ||
| s.True(val) | ||
| } | ||
|
|
||
| func TestFlightSqlClient(t *testing.T) { | ||
| suite.Run(t, new(FlightSqlClientSuite)) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -155,6 +155,9 @@ type ActionCreatePreparedStatementResult struct { | |
| Handle []byte | ||
| DatasetSchema *arrow.Schema | ||
| ParameterSchema *arrow.Schema | ||
| // IsUpdate indicates whether the prepared statement should be executed | ||
| // as an update (true) or query (false). If nil, the client can choose. | ||
| IsUpdate *bool | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a defect, but this needs a release note, because the public API impact is wider than this struct.
type CreatePreparedStatementResult = pb.ActionCreatePreparedStatementResultBeing a type alias, the regenerated proto field lands on a public return flightsql.ActionCreatePreparedStatementResult{handle, ds, ps}, nilwill stop compiling. That's permitted under the Go 1 compatibility rules — unkeyed literals of imported struct types are explicitly not covered — and The unconditional
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The comment mentions 2 distinct public structs. For clarity, you are asking about release notes for both of them, right? I updated the PR description with the breaking change, let me know if I'm supposed to add it somewhere else. The type alias refers to But the line about the unkeyed literal is referring to a different struct Thank you for pointing out the compatibility rules, for reference I found them at https://go.dev/doc/go1compat:
|
||
| } | ||
|
|
||
| type ActionBeginTransactionRequest interface{} | ||
|
|
@@ -1251,6 +1254,7 @@ func (f *flightSqlServer) DoAction(cmd *flight.Action, stream flight.FlightServi | |
| if output.ParameterSchema != nil { | ||
| result.ParameterSchema = flight.SerializeSchema(output.ParameterSchema, f.mem) | ||
| } | ||
| result.IsUpdate = output.IsUpdate | ||
|
|
||
| if err := anycmd.MarshalFrom(&result); err != nil { | ||
| return status.Errorf(codes.Internal, "unable to marshal final response: %s", err.Error()) | ||
|
|
@@ -1286,6 +1290,7 @@ func (f *flightSqlServer) DoAction(cmd *flight.Action, stream flight.FlightServi | |
| if output.ParameterSchema != nil { | ||
| result.ParameterSchema = flight.SerializeSchema(output.ParameterSchema, f.mem) | ||
| } | ||
| result.IsUpdate = output.IsUpdate | ||
|
|
||
| if err := anycmd.MarshalFrom(&result); err != nil { | ||
| return status.Errorf(codes.Internal, "unable to marshal final response: %s", err.Error()) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two small things on this propagation site.
Untested.
TestPreparedStatementLoadFromResult(client_test.go:971) is the only coverage forLoadPreparedStatementFromResult, and it doesn't assertIsUpdate. Since that test already constructs the result and checks the other two propagated fields (ParameterSchema,DatasetSchema), adding anIsUpdateassertion there would be consistent and nearly free. (Couldn't leave this as an inline comment on that line — it's outside the diff.)Nit, no change required: this is the one place that stores a
*boolowned by the caller, so a caller mutatingresult.IsUpdateafter this call would change the statement's view.Prepare/PrepareSubstraitdon't have this property since they point at their own unmarshaled local. Not worth a defensive copy unless you'd want it forhandletoo — just noting it so the asymmetry is a conscious choice.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TestPreparedStatementLoadFromResultI let things as they are, and tested with the new field unset, see 8ba207b. Note that this test has the same nuance aboutvalthat I pointed out in feat(arrow/flight/sql): Add is_update field to ActionCreatePreparedStatementResult #732 (comment)I added a new test that checks that the field is properly loaded also when set, see a334b86
handle, but I will admit I had my own reservations while doing it.I will not change this in this PR unless you ask me to, but in your opinion is it worth a follow-up PR to have a defensive copy for both
handleandisUpdate? If so I can tackle that after this one