amazon-ion / ion-go

A Go implementation of Amazon Ion.
https://amazon-ion.github.io/ion-docs/
Apache License 2.0
174 stars 31 forks source link

Unexpected behavior of Timestamp fraction precision #173

Closed bancek closed 3 years ago

bancek commented 3 years ago

Amazon QLDB fails to insert a timestamp if fraction precision is 3 and milliseconds are divisible by 10.

Example:

type Entry struct {
    Time ion.Timestamp `ion:"time"`
}

millis := int64(1608218715340)
t := time.Unix(0, millis*1000000)
ts := ion.NewTimestampWithFractionalSeconds(t, ion.TimestampPrecisionNanosecond, ion.TimezoneUTC, 3)

entry := Entry{
    Time: ts,
}

_, err = driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
    return txn.Execute("INSERT INTO Test ?", entry)
})

The error from Amazon QLDB is

BadRequestException: Digests don't match
{
  RespMetadata: {
    StatusCode: 400,
    RequestID: "3GH3JFguR5O3LLcAwTQiiN"
  },
  Code_: "412",
  Message_: "Digests don't match"
}

Is this an expected behavior?

I've fixed this with a helper to calculate the fraction precision but I would expect the ion-go library to do automatically.

func getFractionPrecision(t time.Time) uint8 {
    millis := (t.UnixNano() / 1000000) % 1000
    fractionPrecision := uint8(3)

    if millis == 0 {
        fractionPrecision = 0
    } else {
        for millis > 0 && (millis%10) == 0 {
            millis /= 10
            fractionPrecision--
        }
    }

    return fractionPrecision
}

This is also a problem when using time.Time in a struct and setting it to time.Now().UTC(). It fails with same error Digests don't match.

Full example:

package main

import (
    "context"
    "time"

    "github.com/amzn/ion-go/ion"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/qldbsession"
    "github.com/awslabs/amazon-qldb-driver-go/qldbdriver"
)

func main() {
    awsSession := session.Must(session.NewSession(aws.NewConfig().WithRegion("us-east-1")))
    qldbSession := qldbsession.New(awsSession)

    driver, err := qldbdriver.New(
        "quick-start",
        qldbSession,
        func(options *qldbdriver.DriverOptions) {
            options.LoggerVerbosity = qldbdriver.LogInfo
        })
    if err != nil {
        panic(err)
    }
    defer driver.Shutdown(context.Background())

    _, err = driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
        _, err := txn.Execute("CREATE TABLE Test")
        if err != nil {
            return nil, err
        }
        return nil, nil
    })
    if err != nil {
        panic(err)
    }

    type Entry struct {
        Time ion.Timestamp `ion:"time"`
    }

    millis := int64(1608218715340)
    t := time.Unix(0, millis*1000000)
    ts := ion.NewTimestampWithFractionalSeconds(t, ion.TimestampPrecisionNanosecond, ion.TimezoneUTC, 3)

    entry := Entry{
        Time: ts,
    }

    _, err = driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
        return txn.Execute("INSERT INTO Test ?", entry)
    })
    if err != nil {
        panic(err)
    }
}

Versions:

github.com/amzn/ion-go v1.1.0
github.com/amzn/ion-hash-go v1.1.0
github.com/aws/aws-sdk-go v1.36.8
github.com/awslabs/amazon-qldb-driver-go v1.0.1
byronlin13 commented 3 years ago

Hi @bancek ,

We have released a new version of the Go driver that fixes this bug.

bancek commented 3 years ago

Thanks 🎉