in go/adbc/driver/bigquery/statement.go [498:662]
func arrowValueToQueryParameterValue(field arrow.Field, value arrow.Array, i int) (bigquery.QueryParameter, error) {
// https://cloud.google.com/bigquery/docs/reference/storage#arrow_schema_details
// https://cloud.google.com/bigquery/docs/reference/rest/v2/StandardSqlDataType#typekind
parameter := bigquery.QueryParameter{}
sqlDataType, err := arrowDataTypeToTypeKind(field, value)
if err != nil {
return bigquery.QueryParameter{}, err
}
if value.IsNull(i) {
parameter.Value = &bigquery.QueryParameterValue{
Type: sqlDataType,
Value: "NULL",
}
return parameter, nil
}
switch value.DataType().ID() {
case arrow.BOOL:
parameter.Value = &bigquery.QueryParameterValue{
Type: sqlDataType,
Value: value.ValueStr(i),
}
case arrow.INT8, arrow.INT16, arrow.INT32, arrow.INT64, arrow.UINT8, arrow.UINT16, arrow.UINT32, arrow.UINT64:
parameter.Value = &bigquery.QueryParameterValue{
Type: sqlDataType,
Value: value.ValueStr(i),
}
case arrow.FLOAT16, arrow.FLOAT32, arrow.FLOAT64:
parameter.Value = &bigquery.QueryParameterValue{
Type: sqlDataType,
Value: value.ValueStr(i),
}
case arrow.BINARY, arrow.BINARY_VIEW, arrow.LARGE_BINARY, arrow.FIXED_SIZE_BINARY:
// Encoded as a base64 string per RFC 4648, section 4.
parameter.Value = &bigquery.QueryParameterValue{
Type: sqlDataType,
Value: value.ValueStr(i),
}
case arrow.STRING, arrow.STRING_VIEW, arrow.LARGE_STRING:
parameter.Value = &bigquery.QueryParameterValue{
Type: sqlDataType,
Value: value.ValueStr(i),
}
case arrow.DATE32:
// Encoded as RFC 3339 full-date format string: 1985-04-12
parameter.Value = &bigquery.QueryParameterValue{
Type: sqlDataType,
Value: value.ValueStr(i),
}
case arrow.DATE64:
// Encoded as RFC 3339 full-date format string: 1985-04-12
parameter.Value = &bigquery.QueryParameterValue{
Type: sqlDataType,
Value: value.ValueStr(i),
}
case arrow.TIMESTAMP:
// Encoded as an RFC 3339 timestamp with mandatory "Z" time zone string: 1985-04-12T23:20:50.52Z
// BigQuery can only do microsecond resolution
toTime, _ := value.DataType().(*arrow.TimestampType).GetToTimeFunc()
encoded := toTime(value.(*array.Timestamp).Value(i)).Format("2006-01-02T15:04:05.999999Z07:00")
parameter.Value = &bigquery.QueryParameterValue{
Type: sqlDataType,
Value: encoded,
}
case arrow.TIME32:
// Encoded as RFC 3339 partial-time format string: 23:20:50.52
encoded := value.(*array.Time32).Value(i).FormattedString(value.DataType().(*arrow.Time32Type).Unit)
parameter.Value = &bigquery.QueryParameterValue{
Type: sqlDataType,
Value: encoded,
}
case arrow.TIME64:
// Encoded as RFC 3339 partial-time format string: 23:20:50.52
//
// cannot use the default format, which will cause errors like
// googleapi: Error 400: Unparseable query parameter `` in type `TYPE_TIME`,
// Invalid time string "00:00:00.000000001" value: '00:00:00.000000001', invalid
encoded := value.(*array.Time64).Value(i).FormattedString(arrow.Microsecond)
parameter.Value = &bigquery.QueryParameterValue{
Type: sqlDataType,
Value: encoded,
}
case arrow.DECIMAL128:
parameter.Value = &bigquery.QueryParameterValue{
Type: sqlDataType,
Value: value.ValueStr(i),
}
case arrow.DECIMAL256:
parameter.Value = &bigquery.QueryParameterValue{
Type: sqlDataType,
Value: value.ValueStr(i),
}
case arrow.LIST, arrow.FIXED_SIZE_LIST, arrow.LIST_VIEW:
start, end := value.(*array.List).ValueOffsets(i)
elemField := field.Type.(*arrow.ListType).ElemField()
arrayValues := make([]bigquery.QueryParameterValue, end-start)
for row := start; row < end; row++ {
pv, err := arrowValueToQueryParameterValue(elemField, value.(*array.List).ListValues(), int(row))
if err != nil {
return bigquery.QueryParameter{}, err
}
arrayValues[row-start].Value = pv.Value
}
parameter.Value = &bigquery.QueryParameterValue{
Type: sqlDataType,
ArrayValue: arrayValues,
}
case arrow.LARGE_LIST_VIEW:
start, end := value.(*array.LargeListView).ValueOffsets(i)
elemField := field.Type.(*arrow.LargeListType).ElemField()
arrayValues := make([]bigquery.QueryParameterValue, end-start)
for row := start; row < end; row++ {
pv, err := arrowValueToQueryParameterValue(elemField, value.(*array.LargeListView).ListValues(), int(row))
if err != nil {
return bigquery.QueryParameter{}, err
}
arrayValues[row-start].Value = pv.Value
}
parameter.Value = &bigquery.QueryParameterValue{
Type: sqlDataType,
ArrayValue: arrayValues,
}
case arrow.STRUCT:
numFields := value.(*array.Struct).NumField()
childFields := field.Type.(*arrow.StructType).Fields()
structValues := make(map[string]bigquery.QueryParameterValue)
for j := 0; j < numFields; j++ {
currentField := childFields[j]
fieldName := currentField.Name
if len(fieldName) == 0 {
return bigquery.QueryParameter{}, adbc.Error{
Code: adbc.StatusInvalidArgument,
Msg: "child field name cannot be empty for structs",
}
}
currentFieldArray := value.(*array.Struct).Field(j)
pv, err := arrowValueToQueryParameterValue(currentField, currentFieldArray, i)
if err != nil {
return bigquery.QueryParameter{}, err
}
_, found := structValues[fieldName]
if found {
return bigquery.QueryParameter{}, adbc.Error{
Code: adbc.StatusInvalidArgument,
Msg: fmt.Sprintf("duplicated child field `%s` found in structs", fieldName),
}
}
structValues[fieldName] = *pv.Value.(*bigquery.QueryParameterValue)
}
parameter.Value = &bigquery.QueryParameterValue{
Type: sqlDataType,
StructValue: structValues,
}
default:
// todo: implement all other types
return parameter, adbc.Error{
Code: adbc.StatusNotImplemented,
Msg: fmt.Sprintf("Parameter type %v is not yet implemented for BigQuery driver", value.DataType().ID()),
}
}
return parameter, nil
}