Skip to content

Commit 4298dad

Browse files
Reconcile typed search attributes with schedules (#1848)
Reconcile typed search attributes with schedules
1 parent c012357 commit 4298dad

File tree

7 files changed

+360
-6
lines changed

7 files changed

+360
-6
lines changed

temporal-sdk/src/main/java/io/temporal/client/schedules/ScheduleActionStartWorkflow.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ public <T> Builder setWorkflowType(Class<T> workflowInterface) {
7272
* Set the workflow options to use when starting a workflow action.
7373
*
7474
* @note ID and TaskQueue are required. Some options like ID reuse policy, cron schedule, and
75-
* start signal cannot be set or an error will occur.
75+
* start signal cannot be set or an error will occur. Schedules requires the use of typed
76+
* search attributes and untyped search attributes will be ignored.
7677
*/
7778
public Builder setOptions(WorkflowOptions options) {
7879
this.options = options;
@@ -99,7 +100,10 @@ public Builder setArguments(Object... arguments) {
99100

100101
public ScheduleActionStartWorkflow build() {
101102
return new ScheduleActionStartWorkflow(
102-
workflowType, options, header == null ? Header.empty() : header, arguments);
103+
workflowType,
104+
options,
105+
header == null ? Header.empty() : header,
106+
arguments == null ? new EncodedValues() : arguments);
103107
}
104108
}
105109

temporal-sdk/src/main/java/io/temporal/client/schedules/ScheduleDescription.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
package io.temporal.client.schedules;
2222

2323
import io.temporal.api.common.v1.Payload;
24+
import io.temporal.common.SearchAttributes;
2425
import io.temporal.common.converter.DataConverter;
2526
import java.lang.reflect.Type;
2627
import java.util.List;
@@ -35,6 +36,7 @@ public final class ScheduleDescription {
3536
private final ScheduleInfo info;
3637
private final Schedule schedule;
3738
private final Map<String, List<?>> searchAttributes;
39+
private final SearchAttributes typedSearchAttributes;
3840
private final Map<String, Payload> memo;
3941
private final DataConverter dataConverter;
4042

@@ -43,12 +45,14 @@ public ScheduleDescription(
4345
ScheduleInfo info,
4446
Schedule schedule,
4547
Map<String, List<?>> searchAttributes,
48+
SearchAttributes typedSearchAttributes,
4649
Map<String, Payload> memo,
4750
DataConverter dataConverter) {
4851
this.id = id;
4952
this.info = info;
5053
this.schedule = schedule;
5154
this.searchAttributes = searchAttributes;
55+
this.typedSearchAttributes = typedSearchAttributes;
5256
this.memo = memo;
5357
this.dataConverter = dataConverter;
5458
}
@@ -84,12 +88,23 @@ public ScheduleDescription(
8488
* Gets the search attributes on the schedule.
8589
*
8690
* @return search attributes
91+
* @deprecated use {@link ScheduleDescription#getTypedSearchAttributes} instead.
8792
*/
8893
@Nonnull
8994
public Map<String, List<?>> getSearchAttributes() {
9095
return searchAttributes;
9196
}
9297

98+
/**
99+
* Gets the search attributes on the schedule.
100+
*
101+
* @return search attributes
102+
*/
103+
@Nonnull
104+
public SearchAttributes getTypedSearchAttributes() {
105+
return typedSearchAttributes;
106+
}
107+
93108
@Nullable
94109
public <T> Object getMemo(String key, Class<T> valueClass) {
95110
return getMemo(key, valueClass, valueClass);
@@ -113,12 +128,13 @@ public boolean equals(Object o) {
113128
&& Objects.equals(info, that.info)
114129
&& Objects.equals(schedule, that.schedule)
115130
&& Objects.equals(searchAttributes, that.searchAttributes)
131+
&& Objects.equals(typedSearchAttributes, that.typedSearchAttributes)
116132
&& Objects.equals(memo, that.memo);
117133
}
118134

119135
@Override
120136
public int hashCode() {
121-
return Objects.hash(id, info, schedule, searchAttributes, memo);
137+
return Objects.hash(id, info, schedule, searchAttributes, typedSearchAttributes, memo);
122138
}
123139

124140
@Override
@@ -133,6 +149,8 @@ public String toString() {
133149
+ schedule
134150
+ ", searchAttributes="
135151
+ searchAttributes
152+
+ ", typedSearchAttributes="
153+
+ typedSearchAttributes
136154
+ ", memo="
137155
+ memo
138156
+ '}';

temporal-sdk/src/main/java/io/temporal/client/schedules/ScheduleOptions.java

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
package io.temporal.client.schedules;
2222

23+
import io.temporal.common.SearchAttributes;
2324
import java.util.List;
2425
import java.util.Map;
2526

@@ -38,6 +39,7 @@ public static final class Builder {
3839
private List<ScheduleBackfill> backfills;
3940
private Map<String, Object> memo;
4041
private Map<String, ?> searchAttributes;
42+
private SearchAttributes typedSearchAttributes;
4143

4244
private Builder() {}
4345

@@ -49,6 +51,7 @@ private Builder(ScheduleOptions options) {
4951
this.backfills = options.backfills;
5052
this.memo = options.memo;
5153
this.searchAttributes = options.searchAttributes;
54+
this.typedSearchAttributes = options.typedSearchAttributes;
5255
}
5356

5457
/** Set if the schedule will be triggered immediately upon creation. */
@@ -69,31 +72,45 @@ public Builder setMemo(Map<String, Object> memo) {
6972
return this;
7073
}
7174

72-
/** Set the search attributes for the schedule. */
75+
/**
76+
* Set the search attributes for the schedule.
77+
*
78+
* @deprecated use {@link ScheduleOptions.Builder#setTypedSearchAttributes} instead.
79+
*/
7380
public Builder setSearchAttributes(Map<String, ?> searchAttributes) {
7481
this.searchAttributes = searchAttributes;
7582
return this;
7683
}
7784

85+
/** Set the search attributes for the schedule. */
86+
public Builder setTypedSearchAttributes(SearchAttributes searchAttributes) {
87+
this.typedSearchAttributes = searchAttributes;
88+
return this;
89+
}
90+
7891
public ScheduleOptions build() {
79-
return new ScheduleOptions(triggerImmediately, backfills, memo, searchAttributes);
92+
return new ScheduleOptions(
93+
triggerImmediately, backfills, memo, searchAttributes, typedSearchAttributes);
8094
}
8195
}
8296

8397
private final boolean triggerImmediately;
8498
private final List<ScheduleBackfill> backfills;
8599
private final Map<String, Object> memo;
86100
private final Map<String, ?> searchAttributes;
101+
private final SearchAttributes typedSearchAttributes;
87102

88103
private ScheduleOptions(
89104
boolean triggerImmediately,
90105
List<ScheduleBackfill> backfills,
91106
Map<String, Object> memo,
92-
Map<String, ?> searchAttributes) {
107+
Map<String, ?> searchAttributes,
108+
SearchAttributes typedSearchAttributes) {
93109
this.triggerImmediately = triggerImmediately;
94110
this.backfills = backfills;
95111
this.memo = memo;
96112
this.searchAttributes = searchAttributes;
113+
this.typedSearchAttributes = typedSearchAttributes;
97114
}
98115

99116
/**
@@ -127,8 +144,18 @@ public Map<String, Object> getMemo() {
127144
* Get the search attributes for the schedule.
128145
*
129146
* @return search attributes for the schedule
147+
* @deprecated use {@link ScheduleOptions#getTypedSearchAttributes()} instead.
130148
*/
131149
public Map<String, ?> getSearchAttributes() {
132150
return searchAttributes;
133151
}
152+
153+
/**
154+
* Get the search attributes for the schedule.
155+
*
156+
* @return search attributes for the schedule
157+
*/
158+
public SearchAttributes getTypedSearchAttributes() {
159+
return typedSearchAttributes;
160+
}
134161
}

temporal-sdk/src/main/java/io/temporal/common/SearchAttributeKey.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
package io.temporal.common;
2222

2323
import com.google.common.reflect.TypeToken;
24+
import io.temporal.api.common.v1.Payload;
2425
import io.temporal.api.enums.v1.IndexedValueType;
2526
import java.lang.reflect.Type;
2627
import java.time.OffsetDateTime;
@@ -73,6 +74,17 @@ public static SearchAttributeKey<List<String>> forKeywordList(String name) {
7374
KEYWORD_LIST_REFLECT_TYPE);
7475
}
7576

77+
/**
78+
* Create a search attribute key for an untyped attribute type.
79+
*
80+
* <p>This should only be used when the server can return untyped search attributes, for example,
81+
* when describing a schedule workflow action.
82+
*/
83+
public static SearchAttributeKey<Payload> forUntyped(String name) {
84+
return new SearchAttributeKey<>(
85+
name, IndexedValueType.INDEXED_VALUE_TYPE_UNSPECIFIED, Payload.class);
86+
}
87+
7688
private final String name;
7789
private final IndexedValueType valueType;
7890
private final Class<? super T> valueClass;
@@ -115,11 +127,17 @@ public Type getValueReflectType() {
115127

116128
/** Create an update that sets a value for this key. */
117129
public SearchAttributeUpdate<T> valueSet(@Nonnull T value) {
130+
if (valueType == IndexedValueType.INDEXED_VALUE_TYPE_UNSPECIFIED) {
131+
throw new IllegalStateException("untyped keys should not be used in workflows");
132+
}
118133
return SearchAttributeUpdate.valueSet(this, value);
119134
}
120135

121136
/** Create an update that unsets a value for this key. */
122137
public SearchAttributeUpdate<T> valueUnset() {
138+
if (valueType == IndexedValueType.INDEXED_VALUE_TYPE_UNSPECIFIED) {
139+
throw new IllegalStateException("untyped keys should not be used in workflows");
140+
}
123141
return SearchAttributeUpdate.valueUnset(this);
124142
}
125143

temporal-sdk/src/main/java/io/temporal/internal/client/RootScheduleClientInvoker.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ public RootScheduleClientInvoker(
5656
}
5757

5858
@Override
59+
@SuppressWarnings("deprecation")
5960
public void createSchedule(CreateScheduleInput input) {
6061

6162
CreateScheduleRequest.Builder request =
@@ -75,8 +76,15 @@ public void createSchedule(CreateScheduleInput input) {
7576

7677
if (input.getOptions().getSearchAttributes() != null
7778
&& !input.getOptions().getSearchAttributes().isEmpty()) {
79+
if (input.getOptions().getTypedSearchAttributes() != null) {
80+
throw new IllegalArgumentException(
81+
"Cannot have search attributes and typed search attributes");
82+
}
7883
request.setSearchAttributes(
7984
SearchAttributesUtil.encode(input.getOptions().getSearchAttributes()));
85+
} else if (input.getOptions().getTypedSearchAttributes() != null) {
86+
request.setSearchAttributes(
87+
SearchAttributesUtil.encodeTyped(input.getOptions().getTypedSearchAttributes()));
8088
}
8189

8290
if (input.getOptions().isTriggerImmediately()
@@ -194,6 +202,7 @@ public DescribeScheduleOutput describeSchedule(DescribeScheduleInput input) {
194202
scheduleRequestHeader.protoToSchedule(response.getSchedule()),
195203
Collections.unmodifiableMap(
196204
SearchAttributesUtil.decode(response.getSearchAttributes())),
205+
SearchAttributesUtil.decodeTyped(response.getSearchAttributes()),
197206
response.getMemo().getFieldsMap(),
198207
clientOptions.getDataConverter()));
199208
} catch (Exception e) {

temporal-sdk/src/main/java/io/temporal/internal/common/SearchAttributePayloadConverter.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ final class SearchAttributePayloadConverter {
4848
new SearchAttributePayloadConverter();
4949

5050
public Payload encodeTyped(SearchAttributeKey<?> key, @Nullable Object value) {
51+
if (key.getValueType() == IndexedValueType.INDEXED_VALUE_TYPE_UNSPECIFIED) {
52+
// If we don't have the type we should just leave the payload as is
53+
return (Payload) value;
54+
}
5155
// We can encode as-is because we know it's strictly typed to expected key value. We
5256
// accept a null value because updates for unset can be null.
5357
return DefaultDataConverter.STANDARD_INSTANCE.toPayload(value).get().toBuilder()
@@ -62,6 +66,13 @@ public void decodeTyped(SearchAttributes.Builder builder, String name, @Nonnull
6266
// Get key type
6367
SearchAttributeKey key;
6468
IndexedValueType indexType = getIndexType(payload.getMetadataMap().get(METADATA_TYPE_KEY));
69+
if (indexType == null) {
70+
// If the server didn't write the type metadata we
71+
// don't know how to decode this search attribute
72+
key = SearchAttributeKey.forUntyped(name);
73+
builder.set(key, payload);
74+
return;
75+
}
6576
switch (indexType) {
6677
case INDEXED_VALUE_TYPE_TEXT:
6778
key = SearchAttributeKey.forText(name);

0 commit comments

Comments
 (0)