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
13 changes: 12 additions & 1 deletion internal/arrowscan/coltype.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ type ColumnTypeInfo struct {
// (math.MaxInt64), matching Thrift; fixed-width types report (0, false).
Length int64
HasLength bool
// Precision / Scale report a DECIMAL column's declared qualifiers.
Precision int64
Scale int64
HasPrecisionScale bool
}

// Scan types, matching the Thrift path's vars in internal/rows/rows.go so both
Expand Down Expand Up @@ -85,7 +89,14 @@ func ColumnTypeInfoFor(dt arrow.DataType) ColumnTypeInfo {
// Under WithKernelDecimalAsFloat the value is a float64 instead of a string;
// the scan type stays RawBytes, a deliberate Thrift-matching exception to the
// scan-type/value lockstep (Thrift does the same under UseArrowNativeDecimal).
return ColumnTypeInfo{DatabaseTypeName: "DECIMAL", ScanType: scanTypeRawBytes}
decimalType := dt.(*arrow.Decimal128Type)
return ColumnTypeInfo{
DatabaseTypeName: "DECIMAL",
ScanType: scanTypeRawBytes,
Precision: int64(decimalType.Precision),
Scale: int64(decimalType.Scale),
HasPrecisionScale: true,
}
case arrow.LIST, arrow.LARGE_LIST, arrow.FIXED_SIZE_LIST:
return varLen("ARRAY", scanTypeRawBytes)
case arrow.MAP:
Expand Down
14 changes: 14 additions & 0 deletions internal/arrowscan/coltype_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,20 @@ func TestColumnTypeInfoFor(t *testing.T) {
}
}

func TestColumnTypeInfoForDecimalSize(t *testing.T) {
decimal := ColumnTypeInfoFor(&arrow.Decimal128Type{Precision: 38, Scale: 18})
if decimal.Precision != 38 || decimal.Scale != 18 || !decimal.HasPrecisionScale {
t.Errorf("decimal size = (%d,%d,%v), want (38,18,true)",
decimal.Precision, decimal.Scale, decimal.HasPrecisionScale)
}

integer := ColumnTypeInfoFor(arrow.PrimitiveTypes.Int64)
if integer.Precision != 0 || integer.Scale != 0 || integer.HasPrecisionScale {
t.Errorf("integer decimal size = (%d,%d,%v), want (0,0,false)",
integer.Precision, integer.Scale, integer.HasPrecisionScale)
}
}

// TestColumnTypeInfoScanTypeCoversScanner is the lockstep guard: every Arrow type
// ScanCellCached handles must have a non-fallback ColumnTypeInfoFor entry, so a new
// scanner type without matching metadata is caught here, not at runtime.
Expand Down
11 changes: 11 additions & 0 deletions internal/backend/kernel/rows.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ var (
_ driver.RowsColumnTypeDatabaseTypeName = (*kernelRows)(nil)
_ driver.RowsColumnTypeNullable = (*kernelRows)(nil)
_ driver.RowsColumnTypeLength = (*kernelRows)(nil)
_ driver.RowsColumnTypePrecisionScale = (*kernelRows)(nil)
// Public batch API, matching the Thrift path (rows.rows). GetArrowBatches is
// real; GetArrowIPCStreams rejects (kernel C ABI exports C Data, not IPC bytes).
_ dbrows.Rows = (*kernelRows)(nil)
Expand Down Expand Up @@ -174,6 +175,16 @@ func (r *kernelRows) ColumnTypeLength(index int) (length int64, ok bool) {
return ct.Length, ct.HasLength
}

// ColumnTypePrecisionScale returns the declared precision and scale for a
// DECIMAL column, matching the Thrift path.
func (r *kernelRows) ColumnTypePrecisionScale(index int) (precision, scale int64, ok bool) {
if index < 0 || index >= len(r.colTypes) {
return 0, 0, false
}
ct := r.colTypes[index]
return ct.Precision, ct.Scale, ct.HasPrecisionScale
}

// Close releases the current batch, the kernel result stream, and (query-path
// ownership) the server operation. Idempotent.
func (r *kernelRows) Close() error {
Expand Down
24 changes: 24 additions & 0 deletions internal/rows/rows.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
// RowsColumnTypeDatabaseTypeName
// RowsColumnTypeNullable
// RowsColumnTypeLength
// RowsColumnTypePrecisionScale
type rows struct {
// The RowScanner is responsible for handling the different
// formats in which the query results can be returned
Expand Down Expand Up @@ -92,6 +93,7 @@ var _ driver.RowsColumnTypeScanType = (*rows)(nil)
var _ driver.RowsColumnTypeDatabaseTypeName = (*rows)(nil)
var _ driver.RowsColumnTypeNullable = (*rows)(nil)
var _ driver.RowsColumnTypeLength = (*rows)(nil)
var _ driver.RowsColumnTypePrecisionScale = (*rows)(nil)
var _ dbsqlrows.Rows = (*rows)(nil)

// TelemetryCallbacks bundles the optional telemetry hooks passed into NewRows.
Expand Down Expand Up @@ -384,6 +386,28 @@ func (r *rows) ColumnTypeLength(index int) (length int64, ok bool) {
return columnTypeLengthForID(rowscanner.GetDBTypeID(columnInfo))
}

// ColumnTypePrecisionScale returns the declared precision and scale for a
// DECIMAL column. Non-decimal columns and incomplete metadata return ok=false.
func (r *rows) ColumnTypePrecisionScale(index int) (precision, scale int64, ok bool) {
columnInfo, err := r.getColumnMetadataByIndex(index)
if err != nil || rowscanner.GetDBTypeID(columnInfo) != cli_service.TTypeId_DECIMAL_TYPE {
return 0, 0, false
}

qualifiers := rowscanner.GetDBTypeQualifiers(columnInfo)
if qualifiers == nil {
return 0, 0, false
}
precisionQualifier := qualifiers.Qualifiers["precision"]
scaleQualifier := qualifiers.Qualifiers["scale"]
if precisionQualifier == nil || !precisionQualifier.IsSetI32Value() ||
scaleQualifier == nil || !scaleQualifier.IsSetI32Value() {
return 0, 0, false
}

return int64(precisionQualifier.GetI32Value()), int64(scaleQualifier.GetI32Value()), true
}

// columnTypeLengthForID is the variable-length classification behind
// ColumnTypeLength, factored out of the method (which needs a live *rows and a
// server round-trip to reach it) so the exact rule can be reused verbatim — in
Expand Down
46 changes: 46 additions & 0 deletions internal/rows/rows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,52 @@ func TestColumnTypeLength(t *testing.T) {
}
}

func TestColumnTypePrecisionScale(t *testing.T) {
precision := int32(38)
scale := int32(18)
missingScalePrecision := int32(10)

column := func(typeID cli_service.TTypeId, qualifiers map[string]*cli_service.TTypeQualifierValue) *cli_service.TColumnDesc {
return &cli_service.TColumnDesc{
TypeDesc: &cli_service.TTypeDesc{Types: []*cli_service.TTypeEntry{{
PrimitiveEntry: &cli_service.TPrimitiveTypeEntry{
Type: typeID,
TypeQualifiers: &cli_service.TTypeQualifiers{Qualifiers: qualifiers},
},
}}},
}
}

rowSet := &rows{
client: &client.TestClient{},
schema: &cli_service.TTableSchema{Columns: []*cli_service.TColumnDesc{
column(cli_service.TTypeId_DECIMAL_TYPE, map[string]*cli_service.TTypeQualifierValue{
"precision": {I32Value: &precision},
"scale": {I32Value: &scale},
}),
column(cli_service.TTypeId_INT_TYPE, map[string]*cli_service.TTypeQualifierValue{
"precision": {I32Value: &precision},
"scale": {I32Value: &scale},
}),
column(cli_service.TTypeId_DECIMAL_TYPE, map[string]*cli_service.TTypeQualifierValue{
"precision": {I32Value: &missingScalePrecision},
}),
}},
}

gotPrecision, gotScale, ok := rowSet.ColumnTypePrecisionScale(0)
assert.Equal(t, int64(38), gotPrecision)
assert.Equal(t, int64(18), gotScale)
assert.True(t, ok)

for _, index := range []int{-1, 1, 2, 3} {
gotPrecision, gotScale, ok = rowSet.ColumnTypePrecisionScale(index)
assert.Zero(t, gotPrecision)
assert.Zero(t, gotScale)
assert.False(t, ok)
}
}

func TestColumnTypeDatabaseTypeName(t *testing.T) {
var getMetadataCount, fetchResultsCount int

Expand Down