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

Expose Typed Search Attributes #553

Merged
merged 26 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0f98b14
Add SearchAttributeKey value objects
roxblnfk Dec 23, 2024
ffd108d
Convert values to forType->setType with update DTO as the result
roxblnfk Dec 27, 2024
b75f71d
Polish Typed SA DTOs
roxblnfk Dec 27, 2024
b8f6c68
Add collection TypedSearchAttributes
roxblnfk Dec 30, 2024
421d819
WorkflowInfo: add $typedSearchAttributes property
roxblnfk Dec 30, 2024
f5e6323
Add UpsertTypedSearchAttributes command
roxblnfk Dec 30, 2024
0b021da
Add `Workflow::upsertTypedSearchAttributes()` helper
roxblnfk Dec 30, 2024
dd420a7
Prepare test environment
roxblnfk Dec 30, 2024
54436ae
SearchAttributeCollection: add offsetGet(); cover by tests
roxblnfk Jan 1, 2025
cc37bce
Cover TypedSearchAttributes::fromJsonArray
roxblnfk Jan 2, 2025
258ea9c
Client `WorkflowOptions`: add typed SA field and method.
roxblnfk Jan 3, 2025
b7f6900
Remove deprecation from `WorkflowOptions::withSearchAttributes()`
roxblnfk Jan 6, 2025
da2539b
Merge branch 'master' into search-attributes
roxblnfk Jan 14, 2025
bcb9980
Read TSA and fill WorkflowInfo on WorkflowStart
roxblnfk Jan 14, 2025
770b41d
Don't upsert an empty collection of Typed SA; don't collect Upsert Se…
roxblnfk Jan 14, 2025
37247c5
Add TypedSA tests
roxblnfk Jan 14, 2025
ec88dde
Convert datetime SA from client request into RFC3339 string; add tests
roxblnfk Jan 14, 2025
401b621
Merge TypedSA in Workflow scope;
roxblnfk Jan 15, 2025
912c7e7
Typed SA collection now has typed datetime values (ImmutableDatetime)…
roxblnfk Jan 16, 2025
ac40848
Update psalm baseline
roxblnfk Jan 16, 2025
fffcc39
Bump composer dependencies
roxblnfk Jan 20, 2025
f6660c7
Sync DeduplicationTest with Harness repo
roxblnfk Jan 20, 2025
220633b
Add acceptance tests for SearchAttributes
roxblnfk Feb 4, 2025
b15a177
Cleanup
roxblnfk Feb 4, 2025
505b187
Fix psalm issues
roxblnfk Feb 4, 2025
8ca1c3f
Generate ServiceClient with new functions.
roxblnfk Feb 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 3 additions & 10 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
* text=auto

.github/ export-ignore
/.* export-ignore
config/ export-ignore
resources/scripts/ export-ignore
runtime/ export-ignore
tests/ export-ignore

.editorconfig export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.php-cs-fixer.dist.php export-ignore
dload.xml export-ignore
/*.xml export-ignore
/*.xml.dist export-ignore
Makefile export-ignore
phpunit.xml.dist export-ignore
psalm.xml export-ignore
psalm-baseline.xml export-ignore
phpdoc.dist.xml export-ignore
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ Thumbs.db
*.exe
rr
temporal-test-server
.ai
49 changes: 25 additions & 24 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,24 @@
"php": ">=8.1",
"ext-curl": "*",
"ext-json": "*",
"google/common-protos": "^1.3 || ^2.0 || ^3.0 || ^4.0",
"google/protobuf": "^3.22 || ^4.0",
"grpc/grpc": "^1.42",
"nesbot/carbon": "^2.72 || ^3.0.2",
"psr/log": "^2.0 || ^3.0",
"ramsey/uuid": "^4.7",
"react/promise": "^2.9",
"roadrunner-php/roadrunner-api-dto": "^1.9.0",
"roadrunner-php/version-checker": "^1.0",
"spiral/attributes": "^3.1.6",
"spiral/roadrunner": "^2024.3",
"spiral/roadrunner-cli": "^2.5",
"spiral/roadrunner-kv": "^4.2",
"spiral/roadrunner-worker": "^3.5",
"symfony/filesystem": "^5.4 || ^6.0 || ^7.0",
"symfony/http-client": "^5.4.3 || ^6.0.3 || ^7.0",
"symfony/process": "^5.4 || ^6.0 || ^7.0"
"google/common-protos": "^1.4 || ^2.2 || ^3.2 || ^4.9",
"google/protobuf": "^3.25.5 || ^4.29.3",
"grpc/grpc": "^1.57",
"nesbot/carbon": "^2.72.6 || ^3.8.4",
"psr/log": "^2.0 || ^3.0.2",
"ramsey/uuid": "^4.7.6",
"react/promise": "^2.11",
"roadrunner-php/roadrunner-api-dto": "^1.10.0",
"roadrunner-php/version-checker": "^1.0.1",
"spiral/attributes": "^3.1.8",
"spiral/roadrunner": "^2024.3.2",
"spiral/roadrunner-cli": "^2.6",
"spiral/roadrunner-kv": "^4.3",
"spiral/roadrunner-worker": "^3.6.1",
"symfony/filesystem": "^5.4.45 || ^6.4.13 || ^7.0",
"symfony/http-client": "^5.4.49 || ^6.4.17 || ^7.0",
"symfony/polyfill-php83": "^1.31.0",
"symfony/process": "^5.4.47 || ^6.4.15 || ^7.0"
},
"autoload": {
"psr-4": {
Expand All @@ -51,18 +52,18 @@
}
},
"require-dev": {
"buggregator/trap": "^1.10.1",
"composer/composer": "^2.0",
"buggregator/trap": "^1.12.0",
"composer/composer": "^2.8.4",
"dereuromark/composer-prefer-lowest": "^0.1.10",
"doctrine/annotations": "^1.14|^2.0.0",
"doctrine/annotations": "^1.14.4|^2.0.2",
"internal/dload": "^1.0",
"jetbrains/phpstorm-attributes": "dev-master@dev",
"laminas/laminas-code": "^4.0",
"phpunit/phpunit": "^10.5",
"laminas/laminas-code": "^4.16",
"phpunit/phpunit": "^10.5.41",
"spiral/code-style": "~2.1.2",
"spiral/core": "^3.13",
"spiral/core": "^3.14.9",
"ta-tikoma/phpunit-architecture-test": "^0.8.4",
"vimeo/psalm": "^4.30 || ^5.4"
"vimeo/psalm": "^5.26.1"
},
"autoload-dev": {
"psr-4": {
Expand Down
103 changes: 103 additions & 0 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -181,14 +181,20 @@
</file>
<file src="src/Client/WorkflowOptions.php">
<ImpureMethodCall>
<code><![CDATA[$this->typedSearchAttributes]]></code>
<code><![CDATA[setFields]]></code>
<code><![CDATA[setIndexedFields]]></code>
<code><![CDATA[setIndexedFields]]></code>
<code><![CDATA[toPayload]]></code>
<code><![CDATA[toPayload]]></code>
<code><![CDATA[toPayload]]></code>
</ImpureMethodCall>
<PossiblyNullReference>
<code><![CDATA[mergeWith]]></code>
</PossiblyNullReference>
<UnnecessaryVarAnnotation>
<code><![CDATA[SearchAttributeKey]]></code>
</UnnecessaryVarAnnotation>
</file>
<file src="src/Common/CronSchedule.php">
<DeprecatedClass>
Expand All @@ -214,6 +220,95 @@
<code><![CDATA[DateInterval::toDuration($this->maximumInterval)]]></code>
</PossiblyNullArgument>
</file>
<file src="src/Common/SearchAttributes/SearchAttributeKey.php">
<ImpureMethodCall>
<code><![CDATA[valueSet]]></code>
<code><![CDATA[valueUnset]]></code>
</ImpureMethodCall>
</file>
<file src="src/Common/SearchAttributes/SearchAttributeKey/BoolValue.php">
<LessSpecificReturnStatement>
<code><![CDATA[$this->prepareValueSet($value)]]></code>
</LessSpecificReturnStatement>
<MoreSpecificReturnType>
<code><![CDATA[SearchAttributeUpdate]]></code>
</MoreSpecificReturnType>
</file>
<file src="src/Common/SearchAttributes/SearchAttributeKey/DatetimeValue.php">
<LessSpecificReturnStatement>
<code><![CDATA[$this->prepareValueSet(match (true) {
\is_string($value) => new \DateTimeImmutable($value),
$value instanceof \DateTimeImmutable => $value,
default => \DateTimeImmutable::createFromInterface($value),
})]]></code>
</LessSpecificReturnStatement>
<MoreSpecificReturnType>
<code><![CDATA[SearchAttributeUpdate]]></code>
</MoreSpecificReturnType>
</file>
<file src="src/Common/SearchAttributes/SearchAttributeKey/FloatValue.php">
<LessSpecificReturnStatement>
<code><![CDATA[$this->prepareValueSet($value)]]></code>
</LessSpecificReturnStatement>
<MoreSpecificReturnType>
<code><![CDATA[SearchAttributeUpdate]]></code>
</MoreSpecificReturnType>
</file>
<file src="src/Common/SearchAttributes/SearchAttributeKey/IntValue.php">
<LessSpecificReturnStatement>
<code><![CDATA[$this->prepareValueSet($value)]]></code>
</LessSpecificReturnStatement>
<MoreSpecificReturnType>
<code><![CDATA[SearchAttributeUpdate]]></code>
</MoreSpecificReturnType>
</file>
<file src="src/Common/SearchAttributes/SearchAttributeKey/KeywordListValue.php">
<LessSpecificReturnStatement>
<code><![CDATA[$this->prepareValueSet($values)]]></code>
</LessSpecificReturnStatement>
<MismatchingDocblockParamType>
<code><![CDATA[iterable<string|\Stringable>]]></code>
</MismatchingDocblockParamType>
<MoreSpecificReturnType>
<code><![CDATA[SearchAttributeUpdate]]></code>
</MoreSpecificReturnType>
</file>
<file src="src/Common/SearchAttributes/SearchAttributeKey/KeywordValue.php">
<LessSpecificReturnStatement>
<code><![CDATA[$this->prepareValueSet((string) $value)]]></code>
</LessSpecificReturnStatement>
<MoreSpecificReturnType>
<code><![CDATA[SearchAttributeUpdate]]></code>
</MoreSpecificReturnType>
</file>
<file src="src/Common/SearchAttributes/SearchAttributeKey/StringValue.php">
<LessSpecificReturnStatement>
<code><![CDATA[$this->prepareValueSet((string) $value)]]></code>
</LessSpecificReturnStatement>
<MoreSpecificReturnType>
<code><![CDATA[SearchAttributeUpdate]]></code>
</MoreSpecificReturnType>
</file>
<file src="src/Common/TypedSearchAttributes.php">
<InvalidArgument>
<code><![CDATA[$key]]></code>
<code><![CDATA[$key]]></code>
</InvalidArgument>
<PossiblyInvalidClone>
<code><![CDATA[clone $this->collection]]></code>
</PossiblyInvalidClone>
<PossiblyNullArrayAccess>
<code><![CDATA[$this->collection[$found]]]></code>
<code><![CDATA[$this->collection[$key]]]></code>
</PossiblyNullArrayAccess>
<PossiblyNullReference>
<code><![CDATA[$this->collection]]></code>
<code><![CDATA[$this->collection]]></code>
</PossiblyNullReference>
<UndefinedMethod>
<code><![CDATA[valueSet]]></code>
</UndefinedMethod>
</file>
<file src="src/Common/Uuid.php">
<LessSpecificReturnStatement>
<code><![CDATA[\vsprintf('%s-%s-%s-%s-%s', [
Expand Down Expand Up @@ -1017,6 +1112,11 @@
<code><![CDATA[UpsertSearchAttributes]]></code>
</MissingImmutableAnnotation>
</file>
<file src="src/Internal/Transport/Request/UpsertTypedSearchAttributes.php">
<MissingImmutableAnnotation>
<code><![CDATA[UpsertTypedSearchAttributes]]></code>
</MissingImmutableAnnotation>
</file>
<file src="src/Internal/Transport/Router/CancelWorkflow.php">
<DocblockTypeContradiction>
<code><![CDATA[$process === null]]></code>
Expand Down Expand Up @@ -1194,6 +1294,9 @@
</PossiblyNullReference>
</file>
<file src="src/Internal/Workflow/ScopeContext.php">
<InaccessibleProperty>
<code><![CDATA[$this->input->info->typedSearchAttributes]]></code>
</InaccessibleProperty>
<MissingImmutableAnnotation>
<code><![CDATA[ScopeContext]]></code>
</MissingImmutableAnnotation>
Expand Down
4 changes: 2 additions & 2 deletions src/Client/WorkflowClientInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public function start($workflow, ...$args): WorkflowRunInterface;
* @param object|WorkflowStubInterface $workflow
* @param non-empty-string $signal
*
* @since 2.12.0
* @since SDK 2.12.0
*/
public function signalWithStart(
$workflow,
Expand Down Expand Up @@ -76,7 +76,7 @@ public function startWithSignal(
* @return UpdateHandle
*
* @note Experimental feature.
* @since 2.12.0
* @since SDK 2.12.0
*/
public function updateWithStart(
$workflow,
Expand Down
61 changes: 53 additions & 8 deletions src/Client/WorkflowOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
use Temporal\Common\IdReusePolicy;
use Temporal\Common\MethodRetry;
use Temporal\Common\RetryOptions;
use Temporal\Common\SearchAttributes\SearchAttributeKey;
use Temporal\Common\TypedSearchAttributes;
use Temporal\Common\Uuid;
use Temporal\Common\WorkflowIdConflictPolicy;
use Temporal\DataConverter\DataConverterInterface;
Expand Down Expand Up @@ -143,11 +145,19 @@ final class WorkflowOptions extends Options
* ElasticSearch). The key and value type must be registered on Temporal
* server side.
*
* @psalm-var array<string, mixed>|null
* @psalm-var array<non-empty-string, mixed>|null
*/
#[Marshal(name: 'SearchAttributes', type: NullableType::class, of: ArrayType::class)]
public ?array $searchAttributes = null;

/**
* Optional indexed info that can be used in query of List/Scan/Count
* workflow APIs (only supported when Temporal server is using
* ElasticSearch). The key and value type must be registered on Temporal
* server side.
*/
public ?TypedSearchAttributes $typedSearchAttributes = null;

/**
* @throws \Exception
*/
Expand Down Expand Up @@ -412,15 +422,40 @@ public function withMemo(?array $memo): self
/**
* Specifies additional indexed information in result of list workflow.
*
* The search attributes can be used in query of List/Scan/Count workflow APIs.
* The key and its value type must be registered on Temporal server side.
*
* @return $this
*/
#[Pure]
public function withSearchAttributes(?array $searchAttributes): self
{
$self = clone $this;
if ($this->typedSearchAttributes !== null) {
throw new \LogicException('Cannot have typed search attributes and search attributes.');
}

$self = clone $this;
$self->searchAttributes = $searchAttributes;
return $self;
}

/**
* Specifies Search Attributes that will be attached to the Workflow.
*
* Search Attributes are additional indexed information attributed to workflow and used for search and visibility.
*
* The search attributes can be used in query of List/Scan/Count workflow APIs.
* The key and its value type must be registered on Temporal server side.
*/
#[Pure]
public function withTypedSearchAttributes(TypedSearchAttributes $attributes): self
{
if ($this->searchAttributes !== null) {
throw new \LogicException('Cannot have typed search attributes and search attributes.');
}

$self = clone $this;
$self->typedSearchAttributes = $attributes;
return $self;
}

Expand Down Expand Up @@ -450,18 +485,28 @@ public function toMemo(DataConverterInterface $converter): ?Memo
*/
public function toSearchAttributes(DataConverterInterface $converter): ?SearchAttributes
{
if ($this->searchAttributes === null) {
if ($this->searchAttributes === null && $this->typedSearchAttributes === null) {
return null;
}

$fields = [];
foreach ($this->searchAttributes as $key => $value) {
$fields[$key] = $converter->toPayload($value);
$search = new SearchAttributes();

// Untyped SA
if ($this->searchAttributes !== null) {
foreach ($this->searchAttributes as $key => $value) {
$fields[$key] = $converter->toPayload($value);
}

return $search->setIndexedFields($fields);
}

$search = new SearchAttributes();
$search->setIndexedFields($fields);
// Typed SA
/** @var SearchAttributeKey $key For the IDE */
foreach ($this->typedSearchAttributes as $key => $value) {
$fields[$key->getName()] = $converter->toPayload($value);
}

return $search;
return $search->setIndexedFields($fields);
}
}
Loading
Loading