Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Release History

## Unreleased
- Fix a data race when timestamp results are read from concurrent queries: Arrow v12's `TimestampType.GetToTimeFunc` lazily caches the type's `*time.Location` without synchronization, and the driver calls it on the shared `arrow.FixedWidthTypes` singletons. The cache is now warmed at package init so later calls are read-only (databricks/databricks-sql-go#179)

## v1.15.1 (2026-09-01)
- Pin the seven per-platform kernel bindings modules to v1.0.0.
- Disable kernel telemetry by default when `enableTelemetry` is unset; explicit `true` and `false` values are unchanged (databricks/databricks-sql-go#464).
Expand Down
17 changes: 17 additions & 0 deletions internal/rows/arrowbased/arrowRows.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,23 @@ type SparkArrowRecord interface {
arrow.Record
}

// Arrow v12's TimestampType caches its *time.Location lazily on the first
// GetZone/GetToTimeFunc call without synchronization (apache/arrow#38795,
// fixed in later Arrow versions). NewArrowRowScanner calls GetToTimeFunc on
// the shared arrow.FixedWidthTypes singletons, so concurrent queries race on
// that first call. Warming the cache here, before any concurrency is
// possible, makes every later call a plain read.
func init() {
for _, dt := range []arrow.DataType{
arrow.FixedWidthTypes.Timestamp_s,
arrow.FixedWidthTypes.Timestamp_ms,
arrow.FixedWidthTypes.Timestamp_us,
arrow.FixedWidthTypes.Timestamp_ns,
} {
_, _ = dt.(*arrow.TimestampType).GetToTimeFunc()
}
}

type timeStampFn func(arrow.Timestamp) time.Time

type colInfo struct {
Expand Down
33 changes: 33 additions & 0 deletions internal/rows/arrowbased/arrowRows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"math/big"
"os"
"strings"
"sync"
"testing"
"time"

Expand Down Expand Up @@ -2398,3 +2399,35 @@ func TestDecimalInComplexTypes(t *testing.T) {
assert.Equal(t, `{"col2":null}`, v)
})
}

// Regression test for databricks/databricks-sql-go#179: Arrow v12's
// TimestampType.GetToTimeFunc lazily caches the type's *time.Location without
// synchronization, so the first concurrent calls on the shared
// arrow.FixedWidthTypes timestamp singletons were a data race. The package
// init() in arrowRows.go warms that cache; without it, this test fails under
// the race detector.
func TestSharedTimestampGetToTimeFuncConcurrency(t *testing.T) {
sharedTimestampTypes := []arrow.DataType{
arrow.FixedWidthTypes.Timestamp_s,
arrow.FixedWidthTypes.Timestamp_ms,
arrow.FixedWidthTypes.Timestamp_us,
arrow.FixedWidthTypes.Timestamp_ns,
}

var wg sync.WaitGroup
for i := 0; i < 32; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for _, dt := range sharedTimestampTypes {
toTime, err := dt.(*arrow.TimestampType).GetToTimeFunc()
if err != nil {
t.Errorf("GetToTimeFunc failed for %s: %v", dt, err)
return
}
_ = toTime(arrow.Timestamp(0))
}
}()
}
wg.Wait()
}