-
Notifications
You must be signed in to change notification settings - Fork 135
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
b3ce6c4
cf15f96
8651cbd
d3595fd
c378e8e
c38caff
140b575
21ab0bb
bd61a0e
24e2862
7437f87
f6a8588
9670cca
a324e27
514d3c6
a334b86
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 a324e27 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.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.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" | ||
|
|
@@ -878,9 +1000,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 |
||
| } | ||
|
|
||
| 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.