This is an automated email from the ASF dual-hosted git repository.

lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git


The following commit(s) were added to refs/heads/main by this push:
     new 58525bdb78 feat(go/adbc/driver/flightsql): handle is_update in 
prepared stmts (#4161)
58525bdb78 is described below

commit 58525bdb784e5949c376f7e5a4eed4dc7f68a524
Author: Pedro Matias <[email protected]>
AuthorDate: Thu Sep 24 02:18:27 2026 +0100

    feat(go/adbc/driver/flightsql): handle is_update in prepared stmts (#4161)
    
    ### Description
    This adds support for executing queries with `DoPut` on
    `Statement.executeQuery()`.
    
    The driver decides on the proper RPC to use (`DoGet` vs `DoPut`)
    according to the value for
    `ActionCreatePreparedStatementResult.is_update` set by the server. When
    the value is not set,
    the driver executes with `DoGet`, preserving backward compatibility.
    
    ### AI Disclaimer
    This change was created with AI assistance (Augment Code). All lines
    were manually reviewed by a human. The output is not copyrightable
    subject matter.
    
    Closes #4074
---
 .../driver/flightsql/flightsql_adbc_server_test.go | 155 +++++++++++++++++++++
 go/adbc/driver/flightsql/flightsql_statement.go    |   8 ++
 2 files changed, 163 insertions(+)

diff --git a/go/adbc/driver/flightsql/flightsql_adbc_server_test.go 
b/go/adbc/driver/flightsql/flightsql_adbc_server_test.go
index 276b7259c7..c0fd98ecd8 100644
--- a/go/adbc/driver/flightsql/flightsql_adbc_server_test.go
+++ b/go/adbc/driver/flightsql/flightsql_adbc_server_test.go
@@ -3258,3 +3258,158 @@ func (suite *BulkIngestTests) 
TestBulkIngestViaExecuteQuery() {
        suite.Require().Len(ingestedData, 1)
        suite.Equal(int64(2), ingestedData[0].NumRows())
 }
+
+// ---- IsUpdate Prepared Statement Tests --------------------
+
+type IsUpdateTestServer struct {
+       flightsql.BaseServer
+
+       getFlightInfoPreparedCalled     bool
+       doPutPreparedStatementUpdCalled bool
+       doGetPreparedStatementCalled    bool
+       doGetPreparedStatementHandle    []byte
+}
+
+func (srv *IsUpdateTestServer) CreatePreparedStatement(_ context.Context, req 
flightsql.ActionCreatePreparedStatementRequest) 
(flightsql.ActionCreatePreparedStatementResult, error) {
+       result := flightsql.ActionCreatePreparedStatementResult{
+               Handle: []byte(req.GetQuery()),
+       }
+       switch req.GetQuery() {
+       case "UPDATE t SET x = 1":
+               t := true
+               result.IsUpdate = &t
+       case "SELECT 1":
+               f := false
+               result.IsUpdate = &f
+       }
+       // default: IsUpdate remains nil
+       return result, nil
+}
+
+func (srv *IsUpdateTestServer) ClosePreparedStatement(_ context.Context, _ 
flightsql.ActionClosePreparedStatementRequest) error {
+       return nil
+}
+
+func (srv *IsUpdateTestServer) GetFlightInfoPreparedStatement(_ 
context.Context, cmd flightsql.PreparedStatementQuery, desc 
*flight.FlightDescriptor) (*flight.FlightInfo, error) {
+       srv.getFlightInfoPreparedCalled = true
+
+       query := &flightproto.CommandPreparedStatementQuery{
+               PreparedStatementHandle: cmd.GetPreparedStatementHandle(),
+       }
+       var ticket anypb.Any
+       if err := ticket.MarshalFrom(query); err != nil {
+               return nil, err
+       }
+       tkt, err := proto.Marshal(&ticket)
+       if err != nil {
+               return nil, err
+       }
+       return &flight.FlightInfo{
+               FlightDescriptor: desc,
+               Endpoint: []*flight.FlightEndpoint{
+                       {Ticket: &flight.Ticket{Ticket: tkt}},
+               },
+               TotalRecords: 0,
+               TotalBytes:   -1,
+       }, nil
+}
+
+func (srv *IsUpdateTestServer) DoGetPreparedStatement(_ context.Context, cmd 
flightsql.PreparedStatementQuery) (*arrow.Schema, <-chan flight.StreamChunk, 
error) {
+       srv.doGetPreparedStatementCalled = true
+       srv.doGetPreparedStatementHandle = cmd.GetPreparedStatementHandle()
+       sc := arrow.NewSchema([]arrow.Field{}, nil)
+       ch := make(chan flight.StreamChunk)
+       close(ch)
+       return sc, ch, nil
+}
+
+func (srv *IsUpdateTestServer) DoPutPreparedStatementUpdate(_ context.Context, 
_ flightsql.PreparedStatementUpdate, _ flight.MessageReader) (int64, error) {
+       srv.doPutPreparedStatementUpdCalled = true
+       return 42, nil
+}
+
+type IsUpdateTests struct {
+       ServerBasedTests
+
+       srv *IsUpdateTestServer
+}
+
+func (suite *IsUpdateTests) SetupSuite() {
+       suite.srv = &IsUpdateTestServer{}
+       suite.srv.Alloc = memory.DefaultAllocator
+       suite.DoSetupSuite(suite.srv, nil, nil)
+}
+
+func (suite *IsUpdateTests) SetupTest() {
+       suite.srv.getFlightInfoPreparedCalled = false
+       suite.srv.doPutPreparedStatementUpdCalled = false
+       suite.srv.doGetPreparedStatementCalled = false
+       suite.srv.doGetPreparedStatementHandle = nil
+       suite.ServerBasedTests.SetupTest()
+}
+
+// TestIsUpdateNil verifies that when IsUpdate is nil (not set by server),
+// the driver calls GetFlightInfoPreparedStatement (Execute path).
+func (suite *IsUpdateTests) TestIsUpdateNil() {
+       stmt, err := suite.cnxn.NewStatement()
+       suite.Require().NoError(err)
+       defer validation.CheckedClose(suite.T(), stmt)
+
+       suite.Require().NoError(stmt.SetSqlQuery("SELECT *"))
+       suite.Require().NoError(stmt.Prepare(context.Background()))
+
+       rdr, _, err := stmt.ExecuteQuery(context.Background())
+       suite.Require().NoError(err)
+       defer rdr.Release()
+
+       suite.True(suite.srv.getFlightInfoPreparedCalled, "Expected 
GetFlightInfoPreparedStatement to be called when IsUpdate is nil")
+       suite.False(suite.srv.doPutPreparedStatementUpdCalled, "Expected 
DoPutPreparedStatementUpdate NOT to be called when IsUpdate is nil")
+       suite.True(suite.srv.doGetPreparedStatementCalled, "Expected 
DoGetPreparedStatement to be called when IsUpdate is nil")
+       suite.Equal([]byte("SELECT *"), suite.srv.doGetPreparedStatementHandle, 
"Expected DoGetPreparedStatement to receive the correct prepared statement 
handle")
+}
+
+// TestIsUpdateFalse verifies that when IsUpdate is explicitly false,
+// the driver calls GetFlightInfoPreparedStatement (Execute path).
+func (suite *IsUpdateTests) TestIsUpdateFalse() {
+       stmt, err := suite.cnxn.NewStatement()
+       suite.Require().NoError(err)
+       defer validation.CheckedClose(suite.T(), stmt)
+
+       suite.Require().NoError(stmt.SetSqlQuery("SELECT 1"))
+       suite.Require().NoError(stmt.Prepare(context.Background()))
+
+       rdr, _, err := stmt.ExecuteQuery(context.Background())
+       suite.Require().NoError(err)
+       defer rdr.Release()
+
+       suite.True(suite.srv.getFlightInfoPreparedCalled, "Expected 
GetFlightInfoPreparedStatement to be called when IsUpdate is false")
+       suite.False(suite.srv.doPutPreparedStatementUpdCalled, "Expected 
DoPutPreparedStatementUpdate NOT to be called when IsUpdate is false")
+       suite.True(suite.srv.doGetPreparedStatementCalled, "Expected 
DoGetPreparedStatement to be called when IsUpdate is false")
+       suite.Equal([]byte("SELECT 1"), suite.srv.doGetPreparedStatementHandle, 
"Expected DoGetPreparedStatement to receive the correct prepared statement 
handle")
+}
+
+// TestIsUpdateTrue verifies that when IsUpdate is explicitly true,
+// the driver calls DoPutPreparedStatementUpdate (ExecuteUpdate path)
+// instead of GetFlightInfoPreparedStatement, even when ExecuteQuery is called.
+func (suite *IsUpdateTests) TestIsUpdateTrue() {
+       stmt, err := suite.cnxn.NewStatement()
+       suite.Require().NoError(err)
+       defer validation.CheckedClose(suite.T(), stmt)
+
+       suite.Require().NoError(stmt.SetSqlQuery("UPDATE t SET x = 1"))
+       suite.Require().NoError(stmt.Prepare(context.Background()))
+
+       rdr, nrec, err := stmt.ExecuteQuery(context.Background())
+       suite.Require().NoError(err)
+       defer rdr.Release()
+
+       suite.EqualValues(42, nrec, "Expected ExecuteQuery to return the number 
of rows affected by DoPutPreparedStatementUpdate")
+       suite.False(rdr.Next(), "Expected empty record reader when IsUpdate is 
true")
+       suite.False(suite.srv.getFlightInfoPreparedCalled, "Expected 
GetFlightInfoPreparedStatement NOT to be called when IsUpdate is true")
+       suite.True(suite.srv.doPutPreparedStatementUpdCalled, "Expected 
DoPutPreparedStatementUpdate to be called when IsUpdate is true")
+       suite.False(suite.srv.doGetPreparedStatementCalled, "Expected 
DoGetPreparedStatement NOT to be called when IsUpdate is true")
+}
+
+func TestIsUpdate(t *testing.T) {
+       suite.Run(t, &IsUpdateTests{})
+}
diff --git a/go/adbc/driver/flightsql/flightsql_statement.go 
b/go/adbc/driver/flightsql/flightsql_statement.go
index ec11ec9bd4..dce99e8076 100644
--- a/go/adbc/driver/flightsql/flightsql_statement.go
+++ b/go/adbc/driver/flightsql/flightsql_statement.go
@@ -557,6 +557,14 @@ func (s *statement) ExecuteQuery(ctx context.Context) (rdr 
array.RecordReader, n
        var header, trailer metadata.MD
        opts := append([]grpc.CallOption{}, grpc.Header(&header), 
grpc.Trailer(&trailer), s.timeouts)
        if s.prepared != nil {
+               if isUpdate, ok := s.prepared.IsUpdate(); ok && isUpdate {
+                       nrec, err = s.prepared.ExecuteUpdate(ctx, opts...)
+                       if err != nil {
+                               return nil, -1, 
adbcFromFlightStatusWithDetails(err, header, trailer, "ExecuteUpdate")
+                       }
+                       rdr, err = array.NewRecordReader(arrow.NewSchema(nil, 
nil), nil)
+                       return
+               }
                info, err = s.prepared.Execute(ctx, opts...)
        } else {
                info, err = s.query.execute(ctx, s.cnxn, opts...)

Reply via email to