This is an automated email from the ASF dual-hosted git repository.
zeroshade pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-go.git
The following commit(s) were added to refs/heads/main by this push:
new 7082f4d4 fix(flight/flightsql/example): propagate row iteration errors
(#990)
7082f4d4 is described below
commit 7082f4d4343406bec54567740de1516521b7faa0
Author: Minh Vu <[email protected]>
AuthorDate: Thu Jul 23 20:12:42 2026 +0200
fix(flight/flightsql/example): propagate row iteration errors (#990)
### Rationale for this change
`database/sql` reports errors encountered while advancing through a
result set through `Rows.Err()` after `Rows.Next()` returns false.
`SqlBatchReader.Next()` did not check that error, so a driver failure
after one or more rows could be returned as a successful partial batch
while `SqlBatchReader.Err()` stayed nil.
### What changes are included in this PR?
Check `Rows.Err()` after the iteration loop and return the error before
constructing a record batch.
Add a regression test with a small database driver that returns one row
and then fails during iteration.
### Are these changes tested?
Yes. `go test ./arrow/flight/flightsql/example` and the focused race
test pass.
### Are there any user-facing changes?
Flight SQL example servers now propagate database row iteration failures
instead of returning partial results as successful batches. Successful
queries are unchanged.
---
arrow/flight/flightsql/example/sql_batch_reader.go | 4 +
.../flightsql/example/sql_batch_reader_test.go | 93 ++++++++++++++++++++++
2 files changed, 97 insertions(+)
diff --git a/arrow/flight/flightsql/example/sql_batch_reader.go
b/arrow/flight/flightsql/example/sql_batch_reader.go
index 03c07b2e..826a6aa1 100644
--- a/arrow/flight/flightsql/example/sql_batch_reader.go
+++ b/arrow/flight/flightsql/example/sql_batch_reader.go
@@ -344,6 +344,10 @@ func (r *SqlBatchReader) Next() bool {
rows++
}
+ if err := r.rows.Err(); err != nil {
+ r.err = err
+ return false
+ }
r.record = r.bldr.NewRecordBatch()
return rows > 0
diff --git a/arrow/flight/flightsql/example/sql_batch_reader_test.go
b/arrow/flight/flightsql/example/sql_batch_reader_test.go
new file mode 100644
index 00000000..f2e894bb
--- /dev/null
+++ b/arrow/flight/flightsql/example/sql_batch_reader_test.go
@@ -0,0 +1,93 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//go:build go1.18
+
+package example
+
+import (
+ "context"
+ "database/sql"
+ "database/sql/driver"
+ "errors"
+ "sync"
+ "testing"
+
+ "github.com/apache/arrow-go/v18/arrow"
+ "github.com/apache/arrow-go/v18/arrow/memory"
+ "github.com/stretchr/testify/require"
+)
+
+var errRowIteration = errors.New("row iteration failed")
+var registerFailingRowsDriver sync.Once
+
+type failingRowsDriver struct{}
+
+func (failingRowsDriver) Open(string) (driver.Conn, error) { return
failingRowsConn{}, nil }
+
+type failingRowsConn struct{}
+
+func (failingRowsConn) Prepare(string) (driver.Stmt, error) {
+ return nil, errors.New("not implemented")
+}
+
+func (failingRowsConn) Close() error { return nil }
+
+func (failingRowsConn) Begin() (driver.Tx, error) { return nil,
errors.New("not implemented") }
+
+func (failingRowsConn) QueryContext(context.Context, string,
[]driver.NamedValue) (driver.Rows, error) {
+ return &failingRows{}, nil
+}
+
+type failingRows struct {
+ returnedRow bool
+}
+
+func (*failingRows) Columns() []string { return []string{"value"} }
+
+func (*failingRows) Close() error { return nil }
+
+func (r *failingRows) Next(dest []driver.Value) error {
+ if r.returnedRow {
+ return errRowIteration
+ }
+ r.returnedRow = true
+ dest[0] = int64(1)
+ return nil
+}
+
+func TestSqlBatchReaderPropagatesRowsError(t *testing.T) {
+ const driverName = "arrow-go-failing-rows"
+ registerFailingRowsDriver.Do(func() { sql.Register(driverName,
failingRowsDriver{}) })
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ db, err := sql.Open(driverName, "")
+ require.NoError(t, err)
+ defer db.Close()
+
+ rows, err := db.Query("SELECT value")
+ require.NoError(t, err)
+
+ schema := arrow.NewSchema([]arrow.Field{{Name: "value", Type:
arrow.PrimitiveTypes.Int64}}, nil)
+ r, err := NewSqlBatchReaderWithSchema(mem, schema, rows)
+ require.NoError(t, err)
+ defer r.Release()
+
+ require.False(t, r.Next())
+ require.ErrorIs(t, r.Err(), errRowIteration)
+ require.Nil(t, r.RecordBatch())
+}