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

[Mongo] - Serialization issues with Transaction upsert operation #3575

Open
luigirende opened this issue Oct 21, 2024 · 0 comments
Open

[Mongo] - Serialization issues with Transaction upsert operation #3575

luigirende opened this issue Oct 21, 2024 · 0 comments
Labels
kind/bug Something isn't working

Comments

@luigirende
Copy link
Contributor

This issue is same described dapr/dapr#2016 for Redis State store

Expected Behavior

When a state is saved via the the executeTransaction method via GRPC, Multi method implementation for mongodb state store, and the state is again read in via the getState API, there is a deserialization issue.
In the collection the value field is always stored as string also if the contentType=application/json is present.

Actual Behavior

State saved via transactional API should be read correctly via the Get State API and save the value as bson.D and not as string if the contentType is equals to application/json

Steps to Reproduce the Problem

  1. Install Dapr 1.14.4
  2. Create a GRPC server implementing the Execute Transaction call
  3. Save State via the transaction API GRPC and set in the metadata contentType=application/json
  4. Get State and deserialize

Potential Issue

In the runtime, in the ExecuteStateTransaction method defined in grpc.go file the req.Value parameter of the SetRequest is always set as byte[]

			setReq := state.SetRequest{
				Key: key,
				// Limitation:
				// components that cannot handle byte array need to deserialize/serialize in
				// component specific way in components-contrib repo.
				Value:    req.GetValue(),
				Metadata: req.GetMetadata(),
			}

because the req.GetValue is bytes in the proto request.

// StateItem represents state key, value, and additional options to save state.
message StateItem {
  // Required. The state key
  string key = 1;

  // Required. The state data for key
  bytes value = 2;

  // The entity tag which represents the specific version of data.
  // The exact ETag format is defined by the corresponding data store.
  Etag etag = 3;

  // The metadata which will be passed to state store component.
  map<string,string> metadata = 4;

  // Options for concurrency and consistency to save the state.
  StateOptions options = 5;
}

At the end of ExecuteStateTransaction method the Multi interface is called.
Specifically in mongo component mongodb.go, the byte[] value is marshaled as string, in fact the steps are Multi function call doTransaction and this call setInternal where the value as byte[] are casted to string.

	var v interface{}
	switch obj := req.Value.(type) {
	case []byte:
		v = string(obj)
	case string:
		v = fmt.Sprintf("%q", obj)
	default:
		v = req.Value
	}

My proposal to fix this bug is to check in the doTransaction function if the contentType is present and equals to application/json and the value is byte[] and do the json Unmarshal of the value, like this.

		case state.SetRequest:
			{
				isJson := (len(req.Metadata) > 0 && req.Metadata[metadata.ContentType] == contenttype.JSONContentType)
				if isJson {
					if bytes, ok := req.Value.([]byte); ok {
						err = json.Unmarshal(bytes, &req.Value)
					}
				}
				err = m.setInternal(sessCtx, &req)
			}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/bug Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant