Skip to content

Commit

Permalink
Merge TypedSA in Workflow scope;
Browse files Browse the repository at this point in the history
Increase tests coverage: test TSA unset command;
Sync with the new RR spec;
  • Loading branch information
roxblnfk committed Jan 15, 2025
1 parent ec88dde commit 401b621
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,8 @@ final class DatetimeValue extends SearchAttributeKey
*/
public function valueSet(string|\DateTimeInterface $value): SearchAttributeUpdate

Check failure on line 19 in src/Common/SearchAttributes/SearchAttributeKey/DatetimeValue.php

View workflow job for this annotation

GitHub Actions / Psalm Validation (PHP 8.3, OS ubuntu-latest)

MoreSpecificReturnType

src/Common/SearchAttributes/SearchAttributeKey/DatetimeValue.php:19:65: MoreSpecificReturnType: The declared return type 'Temporal\Common\SearchAttributes\SearchAttributeUpdate\ValueSet' for Temporal\Common\SearchAttributes\SearchAttributeKey\DatetimeValue::valueSet is more specific than the inferred return type 'Temporal\Common\SearchAttributes\SearchAttributeUpdate' (see https://psalm.dev/070)
{
return $this->prepareValueSet(match (true) {
\is_string($value) => new \DateTimeImmutable($value),
$value instanceof \DateTimeImmutable => $value,
default => \DateTimeImmutable::createFromInterface($value),
});
$datetime = \is_string($value) ? new \DateTimeImmutable($value) : $value;
return $this->prepareValueSet($datetime->format(\DateTimeInterface::RFC3339));

Check failure on line 22 in src/Common/SearchAttributes/SearchAttributeKey/DatetimeValue.php

View workflow job for this annotation

GitHub Actions / Psalm Validation (PHP 8.3, OS ubuntu-latest)

LessSpecificReturnStatement

src/Common/SearchAttributes/SearchAttributeKey/DatetimeValue.php:22:16: LessSpecificReturnStatement: The type 'Temporal\Common\SearchAttributes\SearchAttributeUpdate' is more general than the declared return type 'Temporal\Common\SearchAttributes\SearchAttributeUpdate\ValueSet' for Temporal\Common\SearchAttributes\SearchAttributeKey\DatetimeValue::valueSet (see https://psalm.dev/129)
}

public function getType(): ValueType
Expand Down
2 changes: 1 addition & 1 deletion src/Common/SearchAttributes/ValueType.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ enum ValueType: string
{
case Bool = 'bool';
case Float = 'float64';
case Int = 'int';
case Int = 'int64';
case Keyword = 'keyword';
case KeywordList = 'keyword_list';
case String = 'string';
Expand Down
34 changes: 30 additions & 4 deletions src/Common/TypedSearchAttributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,8 @@ public function hasKey(SearchAttributeKey $key): bool

public function withValue(SearchAttributeKey $key, mixed $value): self
{
$collection = $this->collection === null
? new \SplObjectStorage()
: clone $this->collection;
$collection = $this->withoutValue($key)->collection ?? new \SplObjectStorage();
$collection->offsetSet($key, $value);

return new self($collection);
}

Expand All @@ -121,6 +118,21 @@ public function withUntypedValue(string $name, mixed $value): self
};
}

/**
* @param SearchAttributeKey|non-empty-string $key
*/
public function withoutValue(SearchAttributeKey|string $key): self
{
$found = $this->getKeyByName(\is_string($key) ? $key : $key->getName());
if ($found === null) {
return new self($this->collection === null ? null : clone $this->collection);
}

$collection = clone $this->collection;
$collection->offsetUnset($found);
return new self($collection);
}

/**
* @return int<0, max>
*/
Expand Down Expand Up @@ -157,6 +169,20 @@ public function offsetGet(string $name): mixed
return $key === null ? null : $this->collection[$key];
}

/**
* @return array<non-empty-string, mixed>
*/
public function toArray(): array
{
$result = [];
/** @var SearchAttributeKey $key */
foreach ($this as $key => $value) {
$result[$key->getName()] = $value;
}

return $result;
}

/**
* @param non-empty-string $name
*/
Expand Down
18 changes: 18 additions & 0 deletions src/Internal/Workflow/ScopeContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use React\Promise\Deferred;
use React\Promise\PromiseInterface;
use Temporal\Common\SearchAttributes\SearchAttributeKey;
use Temporal\Common\SearchAttributes\SearchAttributeUpdate;
use Temporal\Exception\Failure\CanceledFailure;
use Temporal\Internal\Transport\CompletableResult;
Expand Down Expand Up @@ -121,6 +122,23 @@ public function upsertSearchAttributes(array $searchAttributes): void
public function upsertTypedSearchAttributes(SearchAttributeUpdate ...$updates): void
{
$this->request(new UpsertTypedSearchAttributes($updates), waitResponse: false);

// Merge changes
$tsa = $this->input->info->typedSearchAttributes;
foreach ($updates as $update) {
if ($update instanceof SearchAttributeUpdate\ValueUnset) {
$tsa = $tsa->withoutValue($update->name);
continue;
}

\assert($update instanceof SearchAttributeUpdate\ValueSet);
$tsa = $tsa->withValue(
SearchAttributeKey::for($update->type, $update->name),
$update->value,
);
}

$this->input->info->typedSearchAttributes = $tsa;
}

#[\Override]
Expand Down
99 changes: 84 additions & 15 deletions tests/Acceptance/Extra/Workflow/TypedSearchAttributesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,19 @@ public function testUpsertTypedSearchAttributes(
'Extra_Workflow_TypedSearchAttributes',
WorkflowOptions::new()
->withTaskQueue($feature->taskQueue)
->withSearchAttributes([
'testBool' => false,
'testInt' => -2,
'testFloat' => 1.1,
'testString' => 'foo',
'testKeyword' => 'bar',
'testKeywordList' => ['baz'],
'testDatetime' => (new \DateTimeImmutable('2019-01-01T00:00:00Z'))
->format(\DateTimeInterface::RFC3339),
])
->withTypedSearchAttributes(
TypedSearchAttributes::empty()
->withValue(SearchAttributeKey::forFloat('testFloat'), 1.1)
->withValue(SearchAttributeKey::forInteger('testInt'), -2)
->withValue(SearchAttributeKey::forBool('testBool'), false)
->withValue(SearchAttributeKey::forString('testString'), 'foo')
->withValue(SearchAttributeKey::forKeyword('testKeyword'), 'bar')
->withValue(SearchAttributeKey::forKeywordList('testKeywordList'), ['baz'])
->withValue(
SearchAttributeKey::forDatetime('testDatetime'),
new \DateTimeImmutable('2019-01-01T00:00:00Z'),
)
),
);

$toSend = [
Expand All @@ -91,7 +94,7 @@ public function testUpsertTypedSearchAttributes(
'testString' => 'foo bar baz',
'testKeyword' => 'foo-bar-baz',
'testKeywordList' => ['foo', 'bar', 'baz'],
'testDatetime' => (new \DateTimeImmutable('2021-01-01T00:00:00Z'))->format(\DateTimeInterface::RFC3339),
'testDatetime' => '2021-01-01T00:00:00+00:00',
];

/** @see TestWorkflow::handle() */
Expand All @@ -104,8 +107,8 @@ public function testUpsertTypedSearchAttributes(

// Get Search Attributes using Client API
$clientSA = \array_intersect_key(
$toSend,
$stub->describe()->info->searchAttributes->getValues(),
$toSend,
);

// Complete workflow
Expand All @@ -119,7 +122,71 @@ public function testUpsertTypedSearchAttributes(
// Get Search Attributes as a Workflow result
$result = $stub->getResult();

$this->assertSame($toSend, $clientSA);
// Normalize datetime field
$clientSA['testDatetime'] = (new \DateTimeImmutable($clientSA['testDatetime']))
->format(\DateTimeInterface::RFC3339);

$this->assertEquals($toSend, $clientSA);
$this->assertEquals($toSend, (array) $result);
}

#[Test]
public function testUpsertTypedSearchAttributesUnset(
WorkflowClientInterface $client,
Feature $feature,
): void {
$stub = $client->newUntypedWorkflowStub(
'Extra_Workflow_TypedSearchAttributes',
WorkflowOptions::new()
->withTaskQueue($feature->taskQueue)
->withTypedSearchAttributes(
TypedSearchAttributes::empty()
->withValue(SearchAttributeKey::forFloat('testFloat'), 1.1)
->withValue(SearchAttributeKey::forInteger('testInt'), -2)
->withValue(SearchAttributeKey::forBool('testBool'), false)
->withValue(SearchAttributeKey::forString('testString'), 'foo')
->withValue(SearchAttributeKey::forKeyword('testKeyword'), 'bar')
->withValue(SearchAttributeKey::forKeywordList('testKeywordList'), ['baz'])
->withValue(
SearchAttributeKey::forDatetime('testDatetime'),
new \DateTimeImmutable('2019-01-01T00:00:00Z'),
)
),
);

$toSend = [
'testInt' => 42,
'testBool' => null,
'testString' => 'bar',
'testKeyword' => null,
'testKeywordList' => ['red'],
'testDatetime' => null,
];

/** @see TestWorkflow::handle() */
$client->start($stub);
try {
$stub->update('setAttributes', $toSend);

// Get Search Attributes using Client API
$clientSA = \array_intersect_key(
$stub->describe()->info->searchAttributes->getValues(),
$toSend,
);

// Complete workflow
/** @see TestWorkflow::exit */
$stub->signal('exit');
} catch (\Throwable $e) {
$stub->terminate('test failed');
throw $e;
}

// Get Search Attributes as a Workflow result
$result = \array_intersect_key((array) $stub->getResult(), $toSend);

$this->assertEquals(\array_filter($toSend), $clientSA);
$this->assertEquals(\array_filter($toSend), $result);
}
}

Expand All @@ -135,7 +202,7 @@ public function handle()
fn(): bool => $this->exit,
);

return Workflow::getInfo()->searchAttributes;
return Workflow::getInfo()->typedSearchAttributes->toArray();
}

#[Workflow\UpdateMethod]
Expand All @@ -148,7 +215,9 @@ public function setAttributes(array $searchAttributes): void
continue;
}

$updates[] = $key->valueSet($searchAttributes[$key->getName()]);
$updates[] = isset($searchAttributes[$key->getName()])
? $key->valueSet($searchAttributes[$key->getName()])
: $updates[] = $key->valueUnset();
}

Workflow::upsertTypedSearchAttributes(...$updates);
Expand Down
28 changes: 28 additions & 0 deletions tests/Unit/Common/TypedSearchAttributesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,24 @@ public function testCount(): void
self::assertCount(2, $collection3);
}

public function testWithoutValueImmutability(): void
{
$collection1 = TypedSearchAttributes::empty();
$collection2 = $collection1->withValue(SearchAttributeKey::forBool('name1'), true);
$collection3 = $collection2->withoutValue(SearchAttributeKey::forBool('name1'));
$collection4 = $collection3->withoutValue(SearchAttributeKey::forBool('name1'));

self::assertNotSame($collection1, $collection2);
self::assertNotSame($collection2, $collection3);
self::assertNotSame($collection1, $collection3);
self::assertNotSame($collection3, $collection4);

self::assertFalse($collection1->hasKey(SearchAttributeKey::forBool('name1')));
self::assertTrue($collection2->hasKey(SearchAttributeKey::forBool('name1')));
self::assertFalse($collection2->hasKey(SearchAttributeKey::forBool('name2')));
self::assertFalse($collection3->hasKey(SearchAttributeKey::forBool('name1')));
}

public function testWithValueImmutability(): void
{
$collection1 = TypedSearchAttributes::empty();
Expand All @@ -38,6 +56,16 @@ public function testWithValueImmutability(): void
self::assertTrue($collection3->hasKey(SearchAttributeKey::forBool('name2')));
}

public function testWithValueOverride(): void
{
$collection = TypedSearchAttributes::empty()
->withValue(SearchAttributeKey::forBool('name2'), false)
->withValue(SearchAttributeKey::forBool('name2'), true);

self::assertCount(1, $collection);
self::assertTrue($collection->offsetGet('name2'));
}

public function testWithUntypedValueImmutability(): void
{
$collection1 = TypedSearchAttributes::empty();
Expand Down

0 comments on commit 401b621

Please sign in to comment.