Skip to content

Commit 0e29ba4

Browse files
author
Sébastien HOUZÉ
authored
feat(dynamodb): add execute statement operation (#1254)
* feat(dynamodb): add execute statement operation See https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_ExecuteStatement.html I've followed https://async-aws.com/contribute/generate.html#creating-a-new-client-operation guide * generate --all * psalm suppress RedundantCastGivenDocblockType * generate --all * add unit tests * psalm baseline * fix unit tests * remove useless diffs * unit tests * cs fix * fix output changed * remove useless diffs
1 parent d99ce57 commit 0e29ba4

File tree

8 files changed

+573
-0
lines changed

8 files changed

+573
-0
lines changed

src/DynamoDbClient.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use AsyncAws\DynamoDb\Enum\Select;
1616
use AsyncAws\DynamoDb\Enum\TableClass;
1717
use AsyncAws\DynamoDb\Exception\ConditionalCheckFailedException;
18+
use AsyncAws\DynamoDb\Exception\DuplicateItemException;
1819
use AsyncAws\DynamoDb\Exception\InternalServerErrorException;
1920
use AsyncAws\DynamoDb\Exception\ItemCollectionSizeLimitExceededException;
2021
use AsyncAws\DynamoDb\Exception\LimitExceededException;
@@ -29,6 +30,7 @@
2930
use AsyncAws\DynamoDb\Input\DeleteItemInput;
3031
use AsyncAws\DynamoDb\Input\DeleteTableInput;
3132
use AsyncAws\DynamoDb\Input\DescribeTableInput;
33+
use AsyncAws\DynamoDb\Input\ExecuteStatementInput;
3234
use AsyncAws\DynamoDb\Input\GetItemInput;
3335
use AsyncAws\DynamoDb\Input\ListTablesInput;
3436
use AsyncAws\DynamoDb\Input\PutItemInput;
@@ -43,6 +45,7 @@
4345
use AsyncAws\DynamoDb\Result\DeleteItemOutput;
4446
use AsyncAws\DynamoDb\Result\DeleteTableOutput;
4547
use AsyncAws\DynamoDb\Result\DescribeTableOutput;
48+
use AsyncAws\DynamoDb\Result\ExecuteStatementOutput;
4649
use AsyncAws\DynamoDb\Result\GetItemOutput;
4750
use AsyncAws\DynamoDb\Result\ListTablesOutput;
4851
use AsyncAws\DynamoDb\Result\PutItemOutput;
@@ -284,6 +287,48 @@ public function describeTable($input): DescribeTableOutput
284287
return new DescribeTableOutput($response);
285288
}
286289

290+
/**
291+
* This operation allows you to perform reads and singleton writes on data stored in DynamoDB, using PartiQL.
292+
*
293+
* @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_ExecuteStatement.html
294+
* @see https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-dynamodb-2012-08-10.html#executestatement
295+
*
296+
* @param array{
297+
* Statement: string,
298+
* Parameters?: AttributeValue[],
299+
* ConsistentRead?: bool,
300+
* NextToken?: string,
301+
* ReturnConsumedCapacity?: ReturnConsumedCapacity::*,
302+
* Limit?: int,
303+
* @region?: string,
304+
* }|ExecuteStatementInput $input
305+
*
306+
* @throws ConditionalCheckFailedException
307+
* @throws ProvisionedThroughputExceededException
308+
* @throws ResourceNotFoundException
309+
* @throws ItemCollectionSizeLimitExceededException
310+
* @throws TransactionConflictException
311+
* @throws RequestLimitExceededException
312+
* @throws InternalServerErrorException
313+
* @throws DuplicateItemException
314+
*/
315+
public function executeStatement($input): ExecuteStatementOutput
316+
{
317+
$input = ExecuteStatementInput::create($input);
318+
$response = $this->getResponse($input->request(), new RequestContext(['operation' => 'ExecuteStatement', 'region' => $input->getRegion(), 'exceptionMapping' => [
319+
'ConditionalCheckFailedException' => ConditionalCheckFailedException::class,
320+
'ProvisionedThroughputExceededException' => ProvisionedThroughputExceededException::class,
321+
'ResourceNotFoundException' => ResourceNotFoundException::class,
322+
'ItemCollectionSizeLimitExceededException' => ItemCollectionSizeLimitExceededException::class,
323+
'TransactionConflictException' => TransactionConflictException::class,
324+
'RequestLimitExceeded' => RequestLimitExceededException::class,
325+
'InternalServerError' => InternalServerErrorException::class,
326+
'DuplicateItemException' => DuplicateItemException::class,
327+
]]));
328+
329+
return new ExecuteStatementOutput($response);
330+
}
331+
287332
/**
288333
* The `GetItem` operation returns a set of attributes for the item with the given primary key. If there is no matching
289334
* item, `GetItem` does not return any data and there will be no `Item` element in the response.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace AsyncAws\DynamoDb\Exception;
4+
5+
use AsyncAws\Core\Exception\Http\ClientException;
6+
use Symfony\Contracts\HttpClient\ResponseInterface;
7+
8+
/**
9+
* There was an attempt to insert an item with the same primary key as an item that already exists in the DynamoDB
10+
* table.
11+
*/
12+
final class DuplicateItemException extends ClientException
13+
{
14+
protected function populateResult(ResponseInterface $response): void
15+
{
16+
$data = $response->toArray(false);
17+
18+
if (null !== $v = (isset($data['message']) ? (string) $data['message'] : null)) {
19+
$this->message = $v;
20+
}
21+
}
22+
}

src/Input/ExecuteStatementInput.php

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
<?php
2+
3+
namespace AsyncAws\DynamoDb\Input;
4+
5+
use AsyncAws\Core\Exception\InvalidArgument;
6+
use AsyncAws\Core\Input;
7+
use AsyncAws\Core\Request;
8+
use AsyncAws\Core\Stream\StreamFactory;
9+
use AsyncAws\DynamoDb\Enum\ReturnConsumedCapacity;
10+
use AsyncAws\DynamoDb\ValueObject\AttributeValue;
11+
12+
final class ExecuteStatementInput extends Input
13+
{
14+
/**
15+
* The PartiQL statement representing the operation to run.
16+
*
17+
* @required
18+
*
19+
* @var string|null
20+
*/
21+
private $statement;
22+
23+
/**
24+
* The parameters for the PartiQL statement, if any.
25+
*
26+
* @var AttributeValue[]|null
27+
*/
28+
private $parameters;
29+
30+
/**
31+
* The consistency of a read operation. If set to `true`, then a strongly consistent read is used; otherwise, an
32+
* eventually consistent read is used.
33+
*
34+
* @var bool|null
35+
*/
36+
private $consistentRead;
37+
38+
/**
39+
* Set this value to get remaining results, if `NextToken` was returned in the statement response.
40+
*
41+
* @var string|null
42+
*/
43+
private $nextToken;
44+
45+
/**
46+
* @var ReturnConsumedCapacity::*|null
47+
*/
48+
private $returnConsumedCapacity;
49+
50+
/**
51+
* The maximum number of items to evaluate (not necessarily the number of matching items). If DynamoDB processes the
52+
* number of items up to the limit while processing the results, it stops the operation and returns the matching values
53+
* up to that point, along with a key in `LastEvaluatedKey` to apply in a subsequent operation so you can pick up where
54+
* you left off. Also, if the processed dataset size exceeds 1 MB before DynamoDB reaches this limit, it stops the
55+
* operation and returns the matching values up to the limit, and a key in `LastEvaluatedKey` to apply in a subsequent
56+
* operation to continue the operation.
57+
*
58+
* @var int|null
59+
*/
60+
private $limit;
61+
62+
/**
63+
* @param array{
64+
* Statement?: string,
65+
* Parameters?: AttributeValue[],
66+
* ConsistentRead?: bool,
67+
* NextToken?: string,
68+
* ReturnConsumedCapacity?: ReturnConsumedCapacity::*,
69+
* Limit?: int,
70+
* @region?: string,
71+
* } $input
72+
*/
73+
public function __construct(array $input = [])
74+
{
75+
$this->statement = $input['Statement'] ?? null;
76+
$this->parameters = isset($input['Parameters']) ? array_map([AttributeValue::class, 'create'], $input['Parameters']) : null;
77+
$this->consistentRead = $input['ConsistentRead'] ?? null;
78+
$this->nextToken = $input['NextToken'] ?? null;
79+
$this->returnConsumedCapacity = $input['ReturnConsumedCapacity'] ?? null;
80+
$this->limit = $input['Limit'] ?? null;
81+
parent::__construct($input);
82+
}
83+
84+
public static function create($input): self
85+
{
86+
return $input instanceof self ? $input : new self($input);
87+
}
88+
89+
public function getConsistentRead(): ?bool
90+
{
91+
return $this->consistentRead;
92+
}
93+
94+
public function getLimit(): ?int
95+
{
96+
return $this->limit;
97+
}
98+
99+
public function getNextToken(): ?string
100+
{
101+
return $this->nextToken;
102+
}
103+
104+
/**
105+
* @return AttributeValue[]
106+
*/
107+
public function getParameters(): array
108+
{
109+
return $this->parameters ?? [];
110+
}
111+
112+
/**
113+
* @return ReturnConsumedCapacity::*|null
114+
*/
115+
public function getReturnConsumedCapacity(): ?string
116+
{
117+
return $this->returnConsumedCapacity;
118+
}
119+
120+
public function getStatement(): ?string
121+
{
122+
return $this->statement;
123+
}
124+
125+
/**
126+
* @internal
127+
*/
128+
public function request(): Request
129+
{
130+
// Prepare headers
131+
$headers = [
132+
'Content-Type' => 'application/x-amz-json-1.0',
133+
'X-Amz-Target' => 'DynamoDB_20120810.ExecuteStatement',
134+
];
135+
136+
// Prepare query
137+
$query = [];
138+
139+
// Prepare URI
140+
$uriString = '/';
141+
142+
// Prepare Body
143+
$bodyPayload = $this->requestBody();
144+
$body = empty($bodyPayload) ? '{}' : json_encode($bodyPayload, 4194304);
145+
146+
// Return the Request
147+
return new Request('POST', $uriString, $query, $headers, StreamFactory::create($body));
148+
}
149+
150+
public function setConsistentRead(?bool $value): self
151+
{
152+
$this->consistentRead = $value;
153+
154+
return $this;
155+
}
156+
157+
public function setLimit(?int $value): self
158+
{
159+
$this->limit = $value;
160+
161+
return $this;
162+
}
163+
164+
public function setNextToken(?string $value): self
165+
{
166+
$this->nextToken = $value;
167+
168+
return $this;
169+
}
170+
171+
/**
172+
* @param AttributeValue[] $value
173+
*/
174+
public function setParameters(array $value): self
175+
{
176+
$this->parameters = $value;
177+
178+
return $this;
179+
}
180+
181+
/**
182+
* @param ReturnConsumedCapacity::*|null $value
183+
*/
184+
public function setReturnConsumedCapacity(?string $value): self
185+
{
186+
$this->returnConsumedCapacity = $value;
187+
188+
return $this;
189+
}
190+
191+
public function setStatement(?string $value): self
192+
{
193+
$this->statement = $value;
194+
195+
return $this;
196+
}
197+
198+
private function requestBody(): array
199+
{
200+
$payload = [];
201+
if (null === $v = $this->statement) {
202+
throw new InvalidArgument(sprintf('Missing parameter "Statement" for "%s". The value cannot be null.', __CLASS__));
203+
}
204+
$payload['Statement'] = $v;
205+
if (null !== $v = $this->parameters) {
206+
$index = -1;
207+
$payload['Parameters'] = [];
208+
foreach ($v as $listValue) {
209+
++$index;
210+
$payload['Parameters'][$index] = $listValue->requestBody();
211+
}
212+
}
213+
if (null !== $v = $this->consistentRead) {
214+
$payload['ConsistentRead'] = (bool) $v;
215+
}
216+
if (null !== $v = $this->nextToken) {
217+
$payload['NextToken'] = $v;
218+
}
219+
if (null !== $v = $this->returnConsumedCapacity) {
220+
if (!ReturnConsumedCapacity::exists($v)) {
221+
throw new InvalidArgument(sprintf('Invalid parameter "ReturnConsumedCapacity" for "%s". The value "%s" is not a valid "ReturnConsumedCapacity".', __CLASS__, $v));
222+
}
223+
$payload['ReturnConsumedCapacity'] = $v;
224+
}
225+
if (null !== $v = $this->limit) {
226+
$payload['Limit'] = $v;
227+
}
228+
229+
return $payload;
230+
}
231+
}

0 commit comments

Comments
 (0)