Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spanner: NullString returning quoted strings #1610

Closed
arminm opened this issue Oct 10, 2019 · 7 comments
Closed

Spanner: NullString returning quoted strings #1610

arminm opened this issue Oct 10, 2019 · 7 comments
Assignees
Labels
api: spanner Issues related to the Spanner API. type: question Request for information or clarification. Not an issue.

Comments

@arminm
Copy link

arminm commented Oct 10, 2019

The NullString's String() function is returning quoted strings instead of just the string value. The implementation is quite strange where it casts strings into a %q format instead of just a %s, or not using fmt.Sprintf at all!

NullString.StringVal is already a string type:

// NullString represents a Cloud Spanner STRING that may be NULL.
type NullString struct {
  StringVal string
  Valid     bool // Valid is true if StringVal is not NULL.
}

but its String() function is using a quoted format to return the value:

// String implements Stringer.String for NullString
func (n NullString) String() string {
  if !n.Valid {
    return fmt.Sprintf("%v", "<null>")
  }
  return fmt.Sprintf("%q", n.StringVal)
}

NullString reference: https://github.com/googleapis/google-cloud-go/blob/master/spanner/value.go#L67-L73

There's also NullTime that is also returning a quoted string when it doesn't need to format the response:
NullTime: https://github.com/googleapis/google-cloud-go/blob/master/spanner/value.go#L109-L115

and NullDate where probably %s should be used.

NullDate: https://github.com/googleapis/google-cloud-go/blob/master/spanner/value.go#L123-L129

@olavloite olavloite changed the title NullString returning quoted strings Spanner: NullString returning quoted strings Oct 10, 2019
@olavloite olavloite added the api: spanner Issues related to the Spanner API. label Oct 10, 2019
@olavloite
Copy link
Contributor

@arminm Thanks for your detailed report.

You're observation is correct, but I don't think this is something we would want to change. The String() method should return a descriptive string of the value, but is not guaranteed to return the raw string value if the underlying value could be a string. The decision to return a quoted string for NullString, NullTime and NullDate was apparently made in the initial version the client library, and others might be relying on that.

Could you elaborate a little bit on why you would want this changed and/or what other means might also mitigate any (potential) problems that you are running into with this?

@olavloite olavloite self-assigned this Oct 10, 2019
@jeanbza jeanbza added the type: question Request for information or clarification. Not an issue. label Oct 10, 2019
@arminm
Copy link
Author

arminm commented Oct 11, 2019

String() is supposed to return a string representation of the value, which is a simple string, but it's adding new characters to it. That's quite counter-intuitive, and as it caught me by surprise in a weird bug I had to spend hours to find, it can affect others. I expected the field to just work when it's not null, so i can pass it to functions that call String() on an object like fmt.Sprintf or loggers.

I also can't pass a variable with NullString type to an interface that accepts Stringer.String and have to wrap it in another struct to return the StringVal instead now.

It's just quite a strange decision made by Google here, to assume that the String() function is only used for logging and it's ok to add quotes... Spanner's lack of support for nullable fields has been quite annoying for me in migrations (i've been using Spanner since January in production), and i was excited to see there's a type to handle nulls, but now it has this annoying issue.

I personally think a better solution to handle nullable fields is to fix the ToStruct method to be able to handle nulls instead of returning errors. The struct descriptions could have another option as well like:

type Model struct {
  ID       string  `spanner:"id"`
  Name string  `spanner:"name,nullable"`
}

and just handle that to return "" when Name is null, or just null if it's a pointer.

@olavloite
Copy link
Contributor

@arminm After an internal discussion we are inclined to agree with you that the current implementation of String() for NullString might be worth changing, even if it will change the current behavior that others might be relying on. Adding characters to the underlying string really isn't very intuitive. I also like your suggestion for letting ToStruct handle null values better.

So we'll look into two separate changes:

  1. Changing the current behavior of the String() methods of NullString, NullTime and NullDate to return a non-quoted string.
  2. Let the ToStruct method accept null values for fields that are explicitly marked as nullable.

@arminm
Copy link
Author

arminm commented Oct 17, 2019

Thank you for being so receptive of my feedback. I'll keep an eye open for the changes in the upcoming versions. 🙏

gopherbot pushed a commit that referenced this issue Oct 30, 2019
The String() method of NullString, NullTime and NullDate should
all return unquoted strings, instead of adding double quotes
around the returned value. This makes it easier to use the values
in standard methods that expect a Stringer, as the returned value
corresponds to the actual underlying value.

Updates #1610.

Change-Id: I20b6d5d25ee8b9325e6f64336dca6b89a2d396db
Reviewed-on: https://code-review.googlesource.com/c/gocloud/+/47192
Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Shanika Kuruppu <skuruppu@google.com>
Reviewed-by: Jean de Klerk <deklerk@google.com>
gopherbot pushed a commit that referenced this issue Dec 9, 2019
This change adds support for decoding result set values from Spanner
into custom types that point back to supported types. This allows
decoding a result set into for example a struct like this:

type blobField []byte
type dbProductInfo struct {
    ProductID  int64     `spanner:"ProductID"`
    UpdateDate time.Time `spanner:"UpdateDate"`
    Data       blobField `spanner:"Data"`
}

Decoding Spanner column values to one of the known types
(string, []byte, int64, float64, time.Timestamp, civil.Date) and
their known Null<type> versions is done using a type switch
statement. Decoding to a custom type is done with reflection. The
reason for not using reflection for all types, including the known
base types, is the performance difference. Benchmark results executed
locally showing the difference between the two are shown below based
on decoding a single string value to a standard string or a custom
string type.

goos: linux
goarch: amd64
pkg: cloud.google.com/go/spanner
BenchmarkDecodeString-8         200000000   8.21 ns/op
BenchmarkDecodeCustomString-8   10000000    135  ns/op

Updates #1610.
Fixes #853.

Change-Id: Iad689b3704acadb83809375c762a7e0b564a7b2f
Reviewed-on: https://code-review.googlesource.com/c/gocloud/+/48090
Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Tyler Bui-Palsulich <tbp@google.com>
Reviewed-by: Hengfeng Li <hengfeng@google.com>
@skuruppu
Copy link
Contributor

@arminm just wanted to check whether you think the work in #1735 helped you or do you think there's more that we could do here?

@larkee
Copy link
Contributor

larkee commented May 27, 2020

Given that the work seems to have been done, I am closing this issue. If you feel that there is more that needs to be done, please feel free to reopen 👍

@larkee larkee closed this as completed May 27, 2020
@arminm
Copy link
Author

arminm commented Oct 10, 2020

@skuruppu it definitely helped! I quickly updated to the version that added the native support for JSON, and refactored my code to use that instead of my custom translations. 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api: spanner Issues related to the Spanner API. type: question Request for information or clarification. Not an issue.
Projects
None yet
Development

No branches or pull requests

5 participants