-
Notifications
You must be signed in to change notification settings - Fork 362
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
feat: add support for DataReader.GetBytes #7395
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally fine, but a couple of questions.
@@ -147,8 +148,33 @@ public T GetFieldValue<T>(string columnName) | |||
public override byte GetByte(int i) => GetFieldValue<byte>(i); | |||
|
|||
/// <inheritdoc /> | |||
public override long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) => throw | |||
new NotSupportedException("Spanner does not support conversion to byte arrays."); | |||
public override long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's annoying that the parameter names don't match the ones we're overriding (and they're unconventional) but fixing that now would be a breaking change, technically. I think we could reasonably argue that any call to this would previously have failed anyway, so now is a reasonable time to change the parameter names - the original declaration is:
public abstract long GetBytes (int ordinal, long dataOffset, byte[]? buffer, int bufferOffset, int length);
Thoughts, @amanda-tarafa?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I agree that we can make the change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've changed them to match the original declaration.
GaxPreconditions.CheckArgumentRange(length, nameof(length), 0, buffer?.Length ?? int.MaxValue); | ||
if (buffer != null) | ||
{ | ||
GaxPreconditions.CheckArgumentRange(bufferoffset + length, nameof(bufferoffset) + "+" + nameof(length), 0, buffer.Length); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer this to just use the name of a single parameter. I don't mind which it is, but ArgumentOutOfRangeException.ParamName
really should be an actual parameter name IMO.
(I suspect length is a simpler option to use here.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to nameof(length)
} | ||
|
||
var bytes = IsDBNull(i) ? null : GetFieldValue<byte[]>(i); | ||
if (buffer == null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this behavior documented somewhere? https://docs.microsoft.com/en-us/dotnet/api/system.data.common.dbdatareader.getbytes doesn't specify it. (In general ADO.NET isn't very well specified unfortunately, from a provider perspective.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have not been able to find any documentation regarding this. From what I can tell from the source of other implementations:
- SQL Server will throw an exception: https://github.com/dotnet/SqlClient/blob/27c56af560cd8049cf894a3ae6a4686c5b7f6481/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs#L1652
- From what I can tell from the PostgreSQL source it will return -1: https://github.com/npgsql/npgsql/blob/925b6fc66cac0c593e66c6227e35c042d5771910/src/Npgsql/NpgsqlDataReader.cs#L2150 (The PostgreSQL wire protocol returns -1 as length for NULL values: https://www.postgresql.org/docs/current/protocol-message-formats.html)
- MySQL returns 0: https://github.com/mysql-net/MySqlConnector/blob/2ea826b4788e3d1e2c2800de64593c57bb8bff7c/src/MySqlConnector/Core/Row.cs#L135
So AFAICT there's no real consensus on what the best option is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, sorry, I thought you meant for the case when the column value is NULL
. There is nothing in the specs regarding what the method should do in that case.
The case when buffer
is null is documented as the way of getting the length of the column, which is in our case unfortunately somewhat inefficient, as it means that in most cases we'll be decoding the BASE64 string twice; once for getting the length and once for getting the actual bytes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, just a tests request.
And a link to the spec where the bahviour of the null buffer is described.
@@ -147,8 +148,33 @@ public T GetFieldValue<T>(string columnName) | |||
public override byte GetByte(int i) => GetFieldValue<byte>(i); | |||
|
|||
/// <inheritdoc /> | |||
public override long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) => throw | |||
new NotSupportedException("Spanner does not support conversion to byte arrays."); | |||
public override long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I agree that we can make the change.
} | ||
|
||
var bytes = IsDBNull(i) ? null : GetFieldValue<byte[]>(i); | ||
if (buffer == null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GaxPreconditions.CheckArgumentRange(length, nameof(length), 0, buffer?.Length ?? int.MaxValue); | ||
if (buffer != null) | ||
{ | ||
GaxPreconditions.CheckArgumentRange(bufferoffset + length, nameof(bufferoffset) + "+" + nameof(length), 0, buffer.Length); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
|
||
namespace Google.Cloud.Spanner.Data.Tests | ||
{ | ||
public class SpannerDataReaderTests |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a test for a type that's not supporte, would be fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've now added a test case for that as well.
115c8d7
to
c57251e
Compare
Adds support for DataReader.GetBytes to SpannerDataReader. Although Spanner does not support any streaming of bytes from a result set, which means that there is no performance gain to be achieved by implementing this method, it still makes sense to support it as some frameworks (NHibernate) use the method by default for columns that store binary data.
c57251e
to
e12d75a
Compare
@jskeet Do you want to take a second look at this? And when do we want to merge this, as it is technically a breaking change? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine with this - and with merging it now. We'll only break users who are calling it with named arguments, and they'd be getting an exception before anyway...
Assert.Equal(expectedBytes, buffer); | ||
} | ||
|
||
[Fact] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect these ArgumentOutOfRangeException tests could all be done with one theory, but I'm fine leaving it as it is.
Changes in Google.Cloud.Spanner.Data version 3.13.0: - [Commit 02b16d7](googleapis@02b16d7): fix: clone each SpannerParameter when cloning a parameter collection - [Commit c653720](googleapis@c653720): feat: add support for DataReader.GetBytes ([issue 7395](googleapis#7395)) - [Commit 9bc116d](googleapis@9bc116d): fix: allow setting parameter size to the same as the current value ([issue 7401](googleapis#7401)) - [Commit 0666123](googleapis@0666123): feat: Spanner Read API Packages in this release: - Release Google.Cloud.Spanner.Admin.Database.V1 version 3.13.0 - Release Google.Cloud.Spanner.Admin.Instance.V1 version 3.13.0 - Release Google.Cloud.Spanner.Common.V1 version 3.13.0 - Release Google.Cloud.Spanner.Data version 3.13.0 - Release Google.Cloud.Spanner.V1 version 3.13.0
Changes in Google.Cloud.Spanner.Data version 3.13.0: - [Commit 02b16d7](02b16d7): fix: clone each SpannerParameter when cloning a parameter collection - [Commit c653720](c653720): feat: add support for DataReader.GetBytes ([issue 7395](#7395)) - [Commit 9bc116d](9bc116d): fix: allow setting parameter size to the same as the current value ([issue 7401](#7401)) - [Commit 0666123](0666123): feat: Spanner Read API Packages in this release: - Release Google.Cloud.Spanner.Admin.Database.V1 version 3.13.0 - Release Google.Cloud.Spanner.Admin.Instance.V1 version 3.13.0 - Release Google.Cloud.Spanner.Common.V1 version 3.13.0 - Release Google.Cloud.Spanner.Data version 3.13.0 - Release Google.Cloud.Spanner.V1 version 3.13.0
Adds support for DataReader.GetBytes to SpannerDataReader. Although Spanner does not support any
streaming of bytes from a result set, which means that there is no performance gain to be achieved
by implementing this method, it still makes sense to support it as some frameworks (NHibernate)
use the method by default for columns that store binary data.