nodejs-datastore icon indicating copy to clipboard operation
nodejs-datastore copied to clipboard

Saving an empty Buffer throws an error.

Open vicb opened this issue 4 years ago • 9 comments

Environment details

  • OS: MacOs
  • Node.js version: v14.4.0
  • npm version: 6.14.8
  • @google-cloud/datastore version: 6.3.0

Steps to reproduce

Execute the following code:

async function saveBlob() {
  const key = datastore.key(['TEST']);
  
  await datastore.save({
    key: key,
    data: {
      name: 'test',
      blob: Buffer.from([])
    },
  });
}

It will trigger an error: Error: 3 INVALID_ARGUMENT: The value "blob" does not contain a value.

I expect to be able to save an empty Buffer without error.

If you change the blob value to Buffer.from([1, 2, 3]) the error disappears.

My use case is that I have a protocol buffer composed of repeated fields only:

message LiveTrack {
  optional int64 id = 1 [jstype = JS_NUMBER];
  optional string name = 2;   
  repeated float lat = 3;
  repeated float lon = 4;
  repeated int32 alt = 5;
  repeated int32 time_sec = 6;
  repeated uint32 flags = 7;
  map<uint32, LiveExtra> extra = 8;
}

It could happen that all repeated fields (including the map) are empty. Because the optional field are not populated, the proto would serialize to an empty Buffer.

Workaround

The workaround would be to set the value to null when the Buffer size is 0. This requires extra code both while saving (to replace the Buffer with null) and while loading (to replace null with the default message).

Other

The wording of the message could be improved: The value "blob" does not contain a value., i.e. use field or key instead of the first "value".

vicb avatar Nov 17 '20 05:11 vicb

EDIT: The actual issue in protobuf-js, while this should also resolve the issue it probably better to fix protobufjs (see below for details)

I have update encodeValue to read like:

        if (value instanceof Buffer) {
            // was valueProto.blobValue = buffer;    
            valueProto.blobValue = value.length == 0 ? '' : value;
            return valueProto;
        }

And it fixes the issue:

  • The column is save as a Blob without vale (verified in the Cloud Console),
  • Reading the column returns an empty buffer.

Anyway this code looks like it fixes the issue.

An other way to fix would be valueProto.blobValue = buffer.toString('base64');.

I don't know the code well enough to determine if this is the best possible fix. What do you think ?

vicb avatar Nov 17 '20 16:11 vicb

I have done some digging into this bug:

Calling datastore.save(...) ends up @grpc/proto-loader createSerializer

function createSerializer(cls: Protobuf.Type): Serialize<object> {
  return function serialize(arg: object): Buffer {
    const message = cls.fromObject(arg);
    return cls.encode(message).finish() as Buffer;
  };
}

The problems seems to be in const message = cls.fromObject(arg);

The arg is:

mode:'NON_TRANSACTIONAL'
mutations:(1) [
  0: {
    upsert:{
      key:{path: Array(1)},
      properties:{
        name: {stringValue: 'test-null'},
        track: {blobValue: Buffer(0), excludeFromIndexes: true}
      }
    }
]

But the message variable has lost the value:

upsert: Entity {
  key:Key {path: Array(1)}
  properties: {
    name:Value {stringValue: 'test-null'}
    track:Value {excludeFromIndexes: true}
  }
}

There should be a blobValue: Buffer(0) for the track.

When the Buffer is not empty the message variable would look like:

upsert:Entity {
  key:Key {path: Array(1)}
  properties:{
    name:Value {stringValue: 'test-null'}
    track:Value {blobValue: Buffer(1), excludeFromIndexes: true}
  }
}

Here there is a blobValue for the track which is the expected result.

vicb avatar Nov 20 '20 19:11 vicb

It seems like this might be related to:

  • https://github.com/protobufjs/protobuf.js/pull/1500
  • https://github.com/protobufjs/protobuf.js/issues/885

vicb avatar Nov 20 '20 19:11 vicb

Patching protobufjs/protobuf.js#1500 did resolve the issue.

I'll try to move that PR forward and then propagate the fix through the dependencies.

vicb avatar Nov 20 '20 20:11 vicb

What work remains to close this issue?

crwilcox avatar Jun 21 '21 21:06 crwilcox

See https://github.com/protobufjs/protobuf.js/pull/1514#pullrequestreview-545366600, the proper fix in protobuf.js should be in v7

The current nodejs-datastore code uses a workaround that could be removed once protobuf.js is fixed - that v7 is released and nodejs-datastore is updated to use this dep.

/cc @alexander-fenster

vicb avatar Jun 21 '21 22:06 vicb

Update v7.0 of protobufjs is staged but not relased.

https://github.com/protobufjs/protobuf.js/pull/1519

crwilcox avatar Jul 23 '21 22:07 crwilcox

https://github.com/protobufjs/protobuf.js/releases shows releases for 7.x+ — checking to see if we've picked these up yet.

meredithslota avatar Sep 20 '22 18:09 meredithslota

The current minimum version of google-gax required is 3.3.0, and it looks like protobufjs v7 was required in v3.1.4, so we should be good to go to unwind this workaround. I've removed the "external" label accordingly.

meredithslota avatar Sep 20 '22 18:09 meredithslota

Seems to run ok without workaround. Will create a PR and a test.

danieljbruce avatar Feb 24 '23 15:02 danieljbruce

https://github.com/googleapis/nodejs-datastore/pull/1072

danieljbruce avatar Feb 24 '23 16:02 danieljbruce