convertSpannerValue converts google.protobuf.Value to RowValue. Uses type metadata to preserve INT64 precision and properly handle all Spanner types.
(colType *sppb.Type, v *structpb.Value)
| 444 | // convertSpannerValue converts google.protobuf.Value to RowValue. |
| 445 | // Uses type metadata to preserve INT64 precision and properly handle all Spanner types. |
| 446 | func convertSpannerValue(colType *sppb.Type, v *structpb.Value) *v1pb.RowValue { |
| 447 | if v == nil || v.Kind == nil { |
| 448 | return util.NullRowValue |
| 449 | } |
| 450 | |
| 451 | switch v.Kind.(type) { |
| 452 | case *structpb.Value_NullValue: |
| 453 | return util.NullRowValue |
| 454 | case *structpb.Value_StringValue: |
| 455 | stringValue := v.GetStringValue() |
| 456 | if colType == nil { |
| 457 | return &v1pb.RowValue{Kind: &v1pb.RowValue_StringValue{ |
| 458 | StringValue: stringValue, |
| 459 | }} |
| 460 | } |
| 461 | |
| 462 | switch colType.Code { |
| 463 | case sppb.TypeCode_INT64: |
| 464 | // Spanner encodes INT64 as strings to preserve precision |
| 465 | val, err := strconv.ParseInt(stringValue, 10, 64) |
| 466 | if err != nil { |
| 467 | slog.Error("failed to parse INT64 string value", log.BBError(err)) |
| 468 | return &v1pb.RowValue{Kind: &v1pb.RowValue_StringValue{ |
| 469 | StringValue: stringValue, |
| 470 | }} |
| 471 | } |
| 472 | return &v1pb.RowValue{Kind: &v1pb.RowValue_Int64Value{ |
| 473 | Int64Value: val, |
| 474 | }} |
| 475 | case sppb.TypeCode_TIMESTAMP: |
| 476 | // Spanner encodes TIMESTAMP as RFC3339 string with nanosecond precision |
| 477 | t, err := time.Parse(time.RFC3339Nano, stringValue) |
| 478 | if err != nil { |
| 479 | slog.Error("failed to parse TIMESTAMP string value", log.BBError(err)) |
| 480 | return &v1pb.RowValue{Kind: &v1pb.RowValue_StringValue{ |
| 481 | StringValue: stringValue, |
| 482 | }} |
| 483 | } |
| 484 | // Determine accuracy from the string |
| 485 | accuracy := int32(6) // Default to microsecond precision |
| 486 | if dotIndex := strings.Index(stringValue, "."); dotIndex >= 0 { |
| 487 | // Find the end of fractional seconds (before 'Z' or timezone) |
| 488 | endIndex := strings.IndexAny(stringValue[dotIndex:], "Z+-") |
| 489 | if endIndex > 0 { |
| 490 | accuracy = int32(endIndex - 1) |
| 491 | if accuracy > 9 { |
| 492 | accuracy = 9 // Cap at nanosecond precision |
| 493 | } |
| 494 | } |
| 495 | } |
| 496 | return &v1pb.RowValue{Kind: &v1pb.RowValue_TimestampValue{ |
| 497 | TimestampValue: &v1pb.RowValue_Timestamp{ |
| 498 | GoogleTimestamp: timestamppb.New(t), |
| 499 | Accuracy: accuracy, |
| 500 | }, |
| 501 | }} |
| 502 | case sppb.TypeCode_BYTES: |
| 503 | // Spanner encodes BYTES as base64 string |