diff --git a/go/arrow/flight/flightsql/driver/utils.go b/go/arrow/flight/flightsql/driver/utils.go index a2ed4ef26e227..99352e7251c53 100644 --- a/go/arrow/flight/flightsql/driver/utils.go +++ b/go/arrow/flight/flightsql/driver/utils.go @@ -58,46 +58,56 @@ func (g grpcCredentials) RequireTransportSecurity() bool { // *** Type conversions *** func fromArrowType(arr arrow.Array, idx int) (interface{}, error) { + if arr.IsNull(idx) { + return nil, nil + } + switch c := arr.(type) { case *array.Boolean: return c.Value(idx), nil case *array.Float16: - return float64(c.Value(idx).Float32()), nil + return c.Value(idx), nil case *array.Float32: - return float64(c.Value(idx)), nil + return c.Value(idx), nil case *array.Float64: return c.Value(idx), nil + case *array.Decimal128: + v := arr.DataType().(*arrow.Decimal128Type) + return c.Value(idx).ToFloat64(v.Scale), nil + case *array.Decimal256: + v := arr.DataType().(*arrow.Decimal256Type) + return c.Value(idx).ToFloat64(v.Scale), nil case *array.Int8: - return int64(c.Value(idx)), nil + return c.Value(idx), nil case *array.Int16: - return int64(c.Value(idx)), nil + return c.Value(idx), nil case *array.Int32: - return int64(c.Value(idx)), nil + return c.Value(idx), nil case *array.Int64: return c.Value(idx), nil + case *array.Binary: + return c.Value(idx), nil case *array.String: return c.Value(idx), nil case *array.Time32: - dt, ok := arr.DataType().(*arrow.Time32Type) - if !ok { - return nil, fmt.Errorf("datatype %T not matching time32", arr.DataType()) - } + d32 := arr.DataType().(*arrow.Time32Type) v := c.Value(idx) - return v.ToTime(dt.TimeUnit()), nil + return v.ToTime(d32.TimeUnit()), nil case *array.Time64: - dt, ok := arr.DataType().(*arrow.Time64Type) - if !ok { - return nil, fmt.Errorf("datatype %T not matching time64", arr.DataType()) - } + d64 := arr.DataType().(*arrow.Time64Type) v := c.Value(idx) - return v.ToTime(dt.TimeUnit()), nil + return v.ToTime(d64.TimeUnit()), nil case *array.Timestamp: - dt, ok := arr.DataType().(*arrow.TimestampType) - if !ok { - return nil, fmt.Errorf("datatype %T not matching timestamp", arr.DataType()) - } + ts := arr.DataType().(*arrow.TimestampType) v := c.Value(idx) - return v.ToTime(dt.TimeUnit()), nil + return v.ToTime(ts.TimeUnit()), nil + case *array.Date64: + return c.Value(idx).ToTime(), nil + case *array.DayTimeInterval: + durationDays := time.Duration(c.Value(idx).Days*24) * time.Hour + duration := time.Duration(c.Value(idx).Milliseconds) * time.Millisecond + + return durationDays + duration, nil } return nil, fmt.Errorf("type %T: %w", arr, ErrNotSupported) diff --git a/go/arrow/flight/flightsql/driver/utils_test.go b/go/arrow/flight/flightsql/driver/utils_test.go new file mode 100644 index 0000000000000..355c888f56cc4 --- /dev/null +++ b/go/arrow/flight/flightsql/driver/utils_test.go @@ -0,0 +1,126 @@ +// 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. +package driver + +import ( + "fmt" + "math/big" + "reflect" + "testing" + "time" + + "github.com/apache/arrow/go/v14/arrow" + "github.com/apache/arrow/go/v14/arrow/array" + "github.com/apache/arrow/go/v14/arrow/decimal128" + "github.com/apache/arrow/go/v14/arrow/decimal256" + "github.com/apache/arrow/go/v14/arrow/float16" + "github.com/apache/arrow/go/v14/arrow/memory" + "github.com/stretchr/testify/require" +) + +func Test_fromArrowType(t *testing.T) { + fields := []arrow.Field{ + {Name: "f1-bool", Type: arrow.FixedWidthTypes.Boolean}, + {Name: "f2-f16", Type: arrow.FixedWidthTypes.Float16}, + {Name: "f3-f32", Type: arrow.PrimitiveTypes.Float32}, + {Name: "f4-f64", Type: arrow.PrimitiveTypes.Float64}, + {Name: "f5-d128", Type: &arrow.Decimal128Type{}}, + {Name: "f6-d256", Type: &arrow.Decimal256Type{}}, + {Name: "f7-i8", Type: arrow.PrimitiveTypes.Int8}, + {Name: "f8-i16", Type: arrow.PrimitiveTypes.Int16}, + {Name: "f9-i32", Type: arrow.PrimitiveTypes.Int32}, + {Name: "f10-i64", Type: arrow.PrimitiveTypes.Int64}, + {Name: "f11-binary", Type: arrow.BinaryTypes.Binary}, + {Name: "f12-string", Type: arrow.BinaryTypes.String}, + {Name: "f13-t32s", Type: arrow.FixedWidthTypes.Time32s}, + {Name: "f14-t64us", Type: arrow.FixedWidthTypes.Time64us}, + {Name: "f15-ts_us", Type: arrow.FixedWidthTypes.Timestamp_ns}, + {Name: "f16-d64", Type: arrow.FixedWidthTypes.Date64}, + {Name: "f17-dti", Type: arrow.FixedWidthTypes.DayTimeInterval}, + } + + schema := arrow.NewSchema(fields, nil) + pool := memory.NewGoAllocator() + b := array.NewRecordBuilder(pool, schema) + defer b.Release() + + b.Field(0).(*array.BooleanBuilder).Append(true) + b.Field(1).(*array.Float16Builder).Append(float16.New(1)) + b.Field(2).(*array.Float32Builder).Append(1) + b.Field(3).(*array.Float64Builder).Append(1) + b.Field(4).(*array.Decimal128Builder).Append(decimal128.FromBigInt(big.NewInt(1))) + b.Field(5).(*array.Decimal256Builder).Append(decimal256.FromBigInt(big.NewInt(1))) + b.Field(6).(*array.Int8Builder).Append(1) + b.Field(7).(*array.Int16Builder).Append(1) + b.Field(8).(*array.Int32Builder).Append(1) + b.Field(9).(*array.Int64Builder).Append(1) + b.Field(10).(*array.BinaryBuilder).Append([]byte("a")) + b.Field(11).(*array.StringBuilder).Append("a") + + t32, err := arrow.Time32FromString("12:30:00", arrow.Second) + require.NoError(t, err) + + b.Field(12).(*array.Time32Builder).Append(t32) + + t64, err := arrow.Time64FromString("12:00:00", arrow.Microsecond) + require.NoError(t, err) + + b.Field(13).(*array.Time64Builder).Append(t64) + + ts, err := arrow.TimestampFromString("1970-01-01T12:00:00", arrow.Nanosecond) + require.NoError(t, err) + + fmt.Println(ts.ToTime(arrow.Nanosecond)) + + b.Field(14).(*array.TimestampBuilder).Append(ts) + + testTime := time.Now() + b.Field(15).(*array.Date64Builder).Append(arrow.Date64FromTime(testTime)) + b.Field(16).(*array.DayTimeIntervalBuilder).Append(arrow.DayTimeInterval{Days: 1, Milliseconds: 1000}) + + rec := b.NewRecord() + defer rec.Release() + + tf := func(t *testing.T, idx int, want any) { + t.Run(fmt.Sprintf("fromArrowType %v %s", fields[idx].Type, fields[idx].Name), func(t *testing.T) { + v, err := fromArrowType(rec.Column(idx), 0) + if err != nil { + t.Fatalf("err when converting from arrow: %s", err) + } + if !reflect.DeepEqual(v, want) { + t.Fatalf("test failed, wanted %T %v got %T %v", want, want, v, v) + } + }) + } + + tf(t, 0, true) // "f1-bool" + tf(t, 1, float16.New(1)) // "f2-f16" + tf(t, 2, float32(1)) // "f3-f32" + tf(t, 3, float64(1)) // "f4-f64" + tf(t, 4, float64(1)) // "f5-d128" + tf(t, 5, float64(1)) // "f6-d256" + tf(t, 6, int8(1)) // "f7-i8" + tf(t, 7, int16(1)) // "f8-i16" + tf(t, 8, int32(1)) // "f9-i32" + tf(t, 9, int64(1)) // "f10-i64" + tf(t, 10, []byte("a")) // "f11-binary" + tf(t, 11, "a") // "f12-string" + tf(t, 12, time.Date(1970, 1, 1, 12, 30, 0, 0, time.UTC)) // "f13-t32s" + tf(t, 13, time.Date(1970, 1, 1, 12, 0, 0, 0, time.UTC)) // "f14-t64us" + tf(t, 14, time.Date(1970, 1, 1, 12, 0, 0, 0, time.UTC)) // "f15-ts_us" + tf(t, 15, testTime.In(time.UTC).Truncate(24*time.Hour)) // "f16-d64" + tf(t, 16, time.Duration(24*time.Hour+time.Second)) // "f17-dti" +}