-
Notifications
You must be signed in to change notification settings - Fork 164
Deserialized Search Attributes API and full Test Server implementation for Search Attributes #1067
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
Conversation
59778e2
to
969ee08
Compare
71980f2
to
97bb9af
Compare
b431dbf
to
e0d3317
Compare
@@ -83,26 +78,4 @@ private static String convertEnumValues( | |||
} | |||
return parsed.jsonString(); | |||
} | |||
|
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.
This code was generalized and moved to ProtobufEnumNameUtils to be reused in Search Attributes code
e0d3317
to
6320485
Compare
* io.temporal.workflow.Workflow#upsertSearchAttributes(Map)} will lead to unsetting the search | ||
* attribute with the corresponded name if any present. | ||
*/ | ||
public static final List<Object> UNSET_VALUE = Collections.emptyList(); |
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.
To save me from trying to dig through the source here, how does this get represented in the temporal.api.common.v1.SearchAttributes
API? As a "binary/null"
payload? And the server knows that is the equivalent of a delete?
Also, is there never a case where I might want to set an empty list for my Keyword
search attribute? Or from an ES perspective is an empty list equivalent to unset?
EDIT: I am now reading temporalio/temporal#561. My only concern now is we make this value future proof w/ whatever decision they might come to 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.
The server doesn't explicitly delete on '[]', it persists an empty collection until that task is done. But it works effectively as a deletion already now, yes.
it's a json payload.
@@ -0,0 +1,80 @@ | |||
/* | |||
* Copyright (C) 2020 Temporal Technologies, Inc. All Rights Reserved. |
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 personally have been fixing the year on these on new files but I understand that for Java specifically some of the other company names need to stay.
|
||
/** | ||
* Because in Go enums are in a shared namespace, protobuf best practices is to prefix the enums | ||
* with enum type prefix to create unique names. The final result is verbose and when we use a |
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.
protobuf best practices is to prefix the enums with enum type prefix to create unique names.
FWIW, I don't believe that's true in the industry. We just happen to have written ours like that, but most people follow something similar to https://google.aip.dev/126. Not that it matters here because we have added that prefix, just mentioning it.
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.
Well, in this AIP:
"The other values should not be prefixed by the name of the enum itself. This generally requires users to write MyState.MYSTATE_ACTIVE in their code, which is unnecessarily verbose."
But this is also right under:
"To avoid sharing values, APIs may prefix enum values with the name of the enum. In this case, they must do so consistently within the enum."
But I will get rid of "best practices" statement.
public static String uniqueToSimplifiedName(Enum<?> enumm) { | ||
String protoEnumName = enumm.name(); | ||
String prefix = ENUM_CLASS_TO_PREFIX.get(enumm.getClass()); | ||
if (!protoEnumName.startsWith(prefix)) { |
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 safe if the map didn't have this class present? Or do we expect it is always present?
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 will add an explicit exception, thanks for pointing out. It's not always safe, the enums have to be added.
.collect(Collectors.toList()); | ||
|
||
if (indexValues.size() == 1) { | ||
type = indexValues.get(0); |
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.
So a collection of integers is considered type INDEXED_VALUE_TYPE_INT
for metadata purposes even though it may be keyword for SA? I think that makes sense, we just need to document this metadata value for all SDKs is the type of data inside the collection.
return IndexedValueType.INDEXED_VALUE_TYPE_DOUBLE; | ||
} else if (Boolean.class.equals(type)) { | ||
return IndexedValueType.INDEXED_VALUE_TYPE_BOOL; | ||
} else if (OffsetDateTime.class.equals(type)) { |
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.
Hrmm, I wonder if Instant
or even java.util.Date
should be supported? Probably not if we expect to be able to decode to the same type we receive.
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.
Date is probably not doable safely looking at the underlying ES Type.
Instant is a really good thought, but yes, to your point, if we do Instant, we will have to return Instant ideally. And it will restrict us because OffsetDateTime is a more powerful class. But maybe I will actually support Instant on the upsert and will add a note that dates are always deserialized into OffsetDateTime.
public Payload encode(@Nonnull Object obj) { | ||
if (obj instanceof Collection && ((Collection<?>) obj).size() == 1) { | ||
// always serialize an empty collection as one value | ||
obj = ((Collection<?>) obj).iterator().next(); |
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.
Hrmm, it is a bit confusing that if I encode a string list of size 1 and decode it I get a string but if I encode a string list of size 2 and decode it I get a list. If we did encode this as a single-item list, would the server just send us back a single item anyways?
if (isEmpty(searchAttributes)) { | ||
return Collections.emptyMap(); | ||
} | ||
Map<String, List<?>> deserializedMap = new HashMap<>(); |
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.
Since the empty-collection type is rare, should probably give an initial capacity here
public static <T> List<T> getSearchAttributeValues(String name) { | ||
SearchAttributes searchAttributes = getRootWorkflowContext().getContext().getSearchAttributes(); | ||
if (searchAttributes == null) { | ||
return 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.
I wonder if this should return an empty list for no value. Won't getSearchAttributes
map values be empty lists instead of null?
It seems we return null in all cases of no attributes, no attribute, and no attribute value. So they can't use nullability to differentiate set or not anyways. If not, I haven't checked, but we probably need to state in the javadoc that this can never return an empty list then.
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.
Well, we use an empty list as a "delete" token, so I would day it's safer to deserialize it always to null to users.
If not, I haven't checked, but we probably need to state in the javadoc that this can never return an empty list then.
👍
Per off-PR discussion, marking approve with more off-PR discussion to come. Specifically, here is the scenario in Java with this PR:
While this makes sense for Java, in Go I think we might not unwrap a list of one string as just a string but rather leave as list. Unsure about TypeScript. |
6320485
to
606bf6d
Compare
@cretz We haven't implemented search attributes in TS yet. |
00b4753
to
bb8e62b
Compare
…n for Search Attributes
bb8e62b
to
dfe871a
Compare
What was changed
Test server now implements and verifies search attributes upserts and in start call on par with the real server
New methods are added that deserialize search attributes from Payload.
How was this tested
Unit tests covering new methods and test server functionality
Closes #673
Closes #617
Blocked by: