From 297b0521d370e8521648a075b75e39179c3f74fa Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Fri, 15 Apr 2022 12:04:22 +0100 Subject: [PATCH] Add timestream clients (#1217) * Add timestream client * Fixes * Fixes * Fixes * wip * Update AwsClientFactory.php * Fixes * Updated change logs --- .gitattributes | 5 + .github/FUNDING.yml | 3 + .github/workflows/.editorconfig | 2 + .github/workflows/branch_alias.yml | 76 ++++++ .github/workflows/checks.yml | 27 ++ .github/workflows/ci.yml | 38 +++ .gitignore | 3 + CHANGELOG.md | 7 + LICENSE | 21 ++ Makefile | 12 + README.md | 20 ++ composer.json | 34 +++ phpunit.xml.dist | 23 ++ src/Enum/ScalarType.php | 38 +++ src/Exception/AccessDeniedException.php | 21 ++ src/Exception/ConflictException.php | 21 ++ src/Exception/InternalServerException.php | 21 ++ src/Exception/InvalidEndpointException.php | 21 ++ src/Exception/QueryExecutionException.php | 21 ++ src/Exception/ThrottlingException.php | 21 ++ src/Exception/ValidationException.php | 21 ++ src/Input/CancelQueryRequest.php | 85 ++++++ src/Input/PrepareQueryRequest.php | 111 ++++++++ src/Input/QueryRequest.php | 165 ++++++++++++ src/Result/CancelQueryResponse.php | 29 ++ src/Result/PrepareQueryResponse.php | 140 ++++++++++ src/Result/QueryResponse.php | 247 ++++++++++++++++++ src/TimestreamQueryClient.php | 153 +++++++++++ src/ValueObject/ColumnInfo.php | 48 ++++ src/ValueObject/Datum.php | 88 +++++++ src/ValueObject/ParameterMapping.php | 43 +++ src/ValueObject/QueryStatus.php | 61 +++++ src/ValueObject/Row.php | 37 +++ src/ValueObject/SelectColumn.php | 79 ++++++ src/ValueObject/TimeSeriesDataPoint.php | 48 ++++ src/ValueObject/Type.php | 75 ++++++ tests/.gitignore | 0 .../Integration/TimestreamQueryClientTest.php | 80 ++++++ tests/Unit/Input/CancelQueryRequestTest.php | 29 ++ tests/Unit/Input/PrepareQueryRequestTest.php | 31 +++ tests/Unit/Input/QueryRequestTest.php | 31 +++ tests/Unit/Result/CancelQueryResponseTest.php | 26 ++ .../Unit/Result/PrepareQueryResponseTest.php | 50 ++++ tests/Unit/Result/QueryResponseTest.php | 73 ++++++ tests/Unit/TimestreamQueryClientTest.php | 60 +++++ 45 files changed, 2245 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/FUNDING.yml create mode 100644 .github/workflows/.editorconfig create mode 100644 .github/workflows/branch_alias.yml create mode 100644 .github/workflows/checks.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 composer.json create mode 100644 phpunit.xml.dist create mode 100644 src/Enum/ScalarType.php create mode 100644 src/Exception/AccessDeniedException.php create mode 100644 src/Exception/ConflictException.php create mode 100644 src/Exception/InternalServerException.php create mode 100644 src/Exception/InvalidEndpointException.php create mode 100644 src/Exception/QueryExecutionException.php create mode 100644 src/Exception/ThrottlingException.php create mode 100644 src/Exception/ValidationException.php create mode 100644 src/Input/CancelQueryRequest.php create mode 100644 src/Input/PrepareQueryRequest.php create mode 100644 src/Input/QueryRequest.php create mode 100644 src/Result/CancelQueryResponse.php create mode 100644 src/Result/PrepareQueryResponse.php create mode 100644 src/Result/QueryResponse.php create mode 100644 src/TimestreamQueryClient.php create mode 100644 src/ValueObject/ColumnInfo.php create mode 100644 src/ValueObject/Datum.php create mode 100644 src/ValueObject/ParameterMapping.php create mode 100644 src/ValueObject/QueryStatus.php create mode 100644 src/ValueObject/Row.php create mode 100644 src/ValueObject/SelectColumn.php create mode 100644 src/ValueObject/TimeSeriesDataPoint.php create mode 100644 src/ValueObject/Type.php create mode 100644 tests/.gitignore create mode 100644 tests/Integration/TimestreamQueryClientTest.php create mode 100644 tests/Unit/Input/CancelQueryRequestTest.php create mode 100644 tests/Unit/Input/PrepareQueryRequestTest.php create mode 100644 tests/Unit/Input/QueryRequestTest.php create mode 100644 tests/Unit/Result/CancelQueryResponseTest.php create mode 100644 tests/Unit/Result/PrepareQueryResponseTest.php create mode 100644 tests/Unit/Result/QueryResponseTest.php create mode 100644 tests/Unit/TimestreamQueryClientTest.php diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..410d4a1 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +/.github export-ignore +/tests export-ignore +/.gitignore export-ignore +/Makefile export-ignore +/phpunit.xml.dist export-ignore diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..ef7eb61 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: [nyholm, jderusse] diff --git a/.github/workflows/.editorconfig b/.github/workflows/.editorconfig new file mode 100644 index 0000000..7bd3346 --- /dev/null +++ b/.github/workflows/.editorconfig @@ -0,0 +1,2 @@ +[*.yml] +indent_size = 2 diff --git a/.github/workflows/branch_alias.yml b/.github/workflows/branch_alias.yml new file mode 100644 index 0000000..a79ba44 --- /dev/null +++ b/.github/workflows/branch_alias.yml @@ -0,0 +1,76 @@ +name: Update branch alias + +on: + push: + tags: ['*'] + +jobs: + branch-alias: + name: Update branch alias + runs-on: ubuntu-latest + + steps: + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.1 + coverage: none + + - name: Find branch alias + id: find_alias + run: | + TAG=$(echo $GITHUB_REF | cut -d'/' -f 3) + echo "Last tag was $TAG" + ARR=(${TAG//./ }) + ARR[1]=$((${ARR[1]}+1)) + echo ::set-output name=alias::${ARR[0]}.${ARR[1]} + + - name: Checkout main repo + run: | + git clone --branch master https://${{ secrets.BOT_GITHUB_TOKEN }}:x-oauth-basic@github.com/async-aws/aws aws + + - name: Update branch alias + run: | + cd aws/src/Service/TimestreamQuery + CURRENT_ALIAS=$(composer config extra.branch-alias.dev-master | cut -d'-' -f 1) + + # If there is a current value on the branch alias + if [ ! -z $CURRENT_ALIAS ]; then + NEW_ALIAS=${{ steps.find_alias.outputs.alias }} + CURRENT_ARR=(${CURRENT_ALIAS//./ }) + NEW_ARR=(${NEW_ALIAS//./ }) + + if [ ${CURRENT_ARR[0]} -gt ${NEW_ARR[0]} ]; then + echo "The current value for major version is larger" + exit 1; + fi + + if [ ${CURRENT_ARR[0]} -eq ${NEW_ARR[0]} ] && [ ${CURRENT_ARR[1]} -gt ${NEW_ARR[1]} ]; then + echo "The current value for minor version is larger" + exit 1; + fi + fi + + composer config extra.branch-alias.dev-master ${{ steps.find_alias.outputs.alias }}-dev + + - name: Commit & push the new files + run: | + echo "::group::git status" + cd aws + git status + echo "::endgroup::" + + git add -N . + if [[ $(git diff --numstat | wc -l) -eq 0 ]]; then + echo "No changes found. Exiting." + exit 0; + fi + + git config --local user.email "github@async-aws.com" + git config --local user.name "AsyncAws Bot" + + echo "::group::git push" + git add . + git commit -m "Update branch alias" + git push + echo "::endgroup::" diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 0000000..1a72276 --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,27 @@ +name: BC Check + +on: + push: + branches: + - master + +jobs: + roave-bc-check: + name: Roave BC Check + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Modify composer.json + run: | + sed -i -re 's/"require": \{/"minimum-stability": "dev","prefer-stable": true,"require": \{/' composer.json + cat composer.json + + git config --local user.email "github@async-aws.com" + git config --local user.name "AsyncAws Bot" + git commit -am "Allow unstable dependencies" + + - name: Roave BC Check + uses: docker://nyholm/roave-bc-check-ga diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4e80845 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,38 @@ +name: Tests + +on: + push: + branches: + - master + +jobs: + + build: + name: Build + runs-on: ubuntu-latest + strategy: + max-parallel: 10 + matrix: + php: ['7.2', '7.3', '7.4', '8.0', '8.1'] + + steps: + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + + - name: Checkout code + uses: actions/checkout@v2 + + - name: Initialize tests + run: make initialize + + - name: Download dependencies + run: | + composer config minimum-stability dev + composer req symfony/phpunit-bridge --no-update + composer update --no-interaction --prefer-dist --optimize-autoloader --prefer-stable + + - name: Run tests + run: ./vendor/bin/simple-phpunit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ef8091 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/vendor/ +*.cache +composer.lock diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..457d417 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +# Change Log + +## NOT RELEASED + +## 0.1.0 + +First version diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..50402d9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2021 Jérémy Derussé, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..850dffc --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +.EXPORT_ALL_VARIABLES: + +initialize: start-docker +start-docker: + echo "Noop" + +test: initialize + ./vendor/bin/simple-phpunit + +clean: stop-docker +stop-docker: + echo "Noop" diff --git a/README.md b/README.md new file mode 100644 index 0000000..b7ba35a --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# AsyncAws Timestream Query Client + +![](https://github.com/async-aws/timestream-query/workflows/Tests/badge.svg?branch=master) +![](https://github.com/async-aws/timestream-query/workflows/BC%20Check/badge.svg?branch=master) + +An API client for Timestream Query. + +## Install + +```cli +composer require async-aws/timestream-query +``` + +## Documentation + +See https://async-aws.com/clients/timestream-query.html for documentation. + +## Contribute + +Contributions are welcome and appreciated. Please read https://async-aws.com/contribute/ diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..adbd8fa --- /dev/null +++ b/composer.json @@ -0,0 +1,34 @@ +{ + "name": "async-aws/timestream-query", + "description": "Timestream Query client, part of the AWS SDK provided by AsyncAws.", + "license": "MIT", + "type": "library", + "keywords": [ + "aws", + "amazon", + "sdk", + "async-aws", + "timestream-query" + ], + "require": { + "php": "^7.2.5 || ^8.0", + "ext-filter": "*", + "ext-json": "*", + "async-aws/core": "^1.9" + }, + "autoload": { + "psr-4": { + "AsyncAws\\TimestreamQuery\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "AsyncAws\\TimestreamQuery\\Tests\\": "tests/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "0.1-dev" + } + } +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..9894ce3 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,23 @@ + + + + + ./src + + + + + + + + ./tests/ + + + diff --git a/src/Enum/ScalarType.php b/src/Enum/ScalarType.php new file mode 100644 index 0000000..7bc0538 --- /dev/null +++ b/src/Enum/ScalarType.php @@ -0,0 +1,38 @@ + true, + self::BOOLEAN => true, + self::DATE => true, + self::DOUBLE => true, + self::INTEGER => true, + self::INTERVAL_DAY_TO_SECOND => true, + self::INTERVAL_YEAR_TO_MONTH => true, + self::TIME => true, + self::TIMESTAMP => true, + self::UNKNOWN => true, + self::VARCHAR => true, + ][$value]); + } +} diff --git a/src/Exception/AccessDeniedException.php b/src/Exception/AccessDeniedException.php new file mode 100644 index 0000000..ea10425 --- /dev/null +++ b/src/Exception/AccessDeniedException.php @@ -0,0 +1,21 @@ +toArray(false); + + if (null !== $v = (isset($data['message']) ? (string) $data['message'] : null)) { + $this->message = $v; + } + } +} diff --git a/src/Exception/ConflictException.php b/src/Exception/ConflictException.php new file mode 100644 index 0000000..9393502 --- /dev/null +++ b/src/Exception/ConflictException.php @@ -0,0 +1,21 @@ +toArray(false); + + if (null !== $v = (isset($data['message']) ? (string) $data['message'] : null)) { + $this->message = $v; + } + } +} diff --git a/src/Exception/InternalServerException.php b/src/Exception/InternalServerException.php new file mode 100644 index 0000000..a07da2f --- /dev/null +++ b/src/Exception/InternalServerException.php @@ -0,0 +1,21 @@ +toArray(false); + + if (null !== $v = (isset($data['message']) ? (string) $data['message'] : null)) { + $this->message = $v; + } + } +} diff --git a/src/Exception/InvalidEndpointException.php b/src/Exception/InvalidEndpointException.php new file mode 100644 index 0000000..1a46949 --- /dev/null +++ b/src/Exception/InvalidEndpointException.php @@ -0,0 +1,21 @@ +toArray(false); + + if (null !== $v = (isset($data['message']) ? (string) $data['message'] : null)) { + $this->message = $v; + } + } +} diff --git a/src/Exception/QueryExecutionException.php b/src/Exception/QueryExecutionException.php new file mode 100644 index 0000000..bc86392 --- /dev/null +++ b/src/Exception/QueryExecutionException.php @@ -0,0 +1,21 @@ +toArray(false); + + if (null !== $v = (isset($data['message']) ? (string) $data['message'] : null)) { + $this->message = $v; + } + } +} diff --git a/src/Exception/ThrottlingException.php b/src/Exception/ThrottlingException.php new file mode 100644 index 0000000..5ad23e6 --- /dev/null +++ b/src/Exception/ThrottlingException.php @@ -0,0 +1,21 @@ +toArray(false); + + if (null !== $v = (isset($data['message']) ? (string) $data['message'] : null)) { + $this->message = $v; + } + } +} diff --git a/src/Exception/ValidationException.php b/src/Exception/ValidationException.php new file mode 100644 index 0000000..0d791f3 --- /dev/null +++ b/src/Exception/ValidationException.php @@ -0,0 +1,21 @@ +toArray(false); + + if (null !== $v = (isset($data['message']) ? (string) $data['message'] : null)) { + $this->message = $v; + } + } +} diff --git a/src/Input/CancelQueryRequest.php b/src/Input/CancelQueryRequest.php new file mode 100644 index 0000000..318aeeb --- /dev/null +++ b/src/Input/CancelQueryRequest.php @@ -0,0 +1,85 @@ +queryId = $input['QueryId'] ?? null; + parent::__construct($input); + } + + public static function create($input): self + { + return $input instanceof self ? $input : new self($input); + } + + public function getQueryId(): ?string + { + return $this->queryId; + } + + /** + * @internal + */ + public function request(): Request + { + // Prepare headers + $headers = [ + 'Content-Type' => 'application/x-amz-json-1.0', + 'X-Amz-Target' => 'Timestream_20181101.CancelQuery', + ]; + + // Prepare query + $query = []; + + // Prepare URI + $uriString = '/'; + + // Prepare Body + $bodyPayload = $this->requestBody(); + $body = empty($bodyPayload) ? '{}' : json_encode($bodyPayload, 4194304); + + // Return the Request + return new Request('POST', $uriString, $query, $headers, StreamFactory::create($body)); + } + + public function setQueryId(?string $value): self + { + $this->queryId = $value; + + return $this; + } + + private function requestBody(): array + { + $payload = []; + if (null === $v = $this->queryId) { + throw new InvalidArgument(sprintf('Missing parameter "QueryId" for "%s". The value cannot be null.', __CLASS__)); + } + $payload['QueryId'] = $v; + + return $payload; + } +} diff --git a/src/Input/PrepareQueryRequest.php b/src/Input/PrepareQueryRequest.php new file mode 100644 index 0000000..674cf0b --- /dev/null +++ b/src/Input/PrepareQueryRequest.php @@ -0,0 +1,111 @@ +queryString = $input['QueryString'] ?? null; + $this->validateOnly = $input['ValidateOnly'] ?? null; + parent::__construct($input); + } + + public static function create($input): self + { + return $input instanceof self ? $input : new self($input); + } + + public function getQueryString(): ?string + { + return $this->queryString; + } + + public function getValidateOnly(): ?bool + { + return $this->validateOnly; + } + + /** + * @internal + */ + public function request(): Request + { + // Prepare headers + $headers = [ + 'Content-Type' => 'application/x-amz-json-1.0', + 'X-Amz-Target' => 'Timestream_20181101.PrepareQuery', + ]; + + // Prepare query + $query = []; + + // Prepare URI + $uriString = '/'; + + // Prepare Body + $bodyPayload = $this->requestBody(); + $body = empty($bodyPayload) ? '{}' : json_encode($bodyPayload, 4194304); + + // Return the Request + return new Request('POST', $uriString, $query, $headers, StreamFactory::create($body)); + } + + public function setQueryString(?string $value): self + { + $this->queryString = $value; + + return $this; + } + + public function setValidateOnly(?bool $value): self + { + $this->validateOnly = $value; + + return $this; + } + + private function requestBody(): array + { + $payload = []; + if (null === $v = $this->queryString) { + throw new InvalidArgument(sprintf('Missing parameter "QueryString" for "%s". The value cannot be null.', __CLASS__)); + } + $payload['QueryString'] = $v; + if (null !== $v = $this->validateOnly) { + $payload['ValidateOnly'] = (bool) $v; + } + + return $payload; + } +} diff --git a/src/Input/QueryRequest.php b/src/Input/QueryRequest.php new file mode 100644 index 0000000..959549e --- /dev/null +++ b/src/Input/QueryRequest.php @@ -0,0 +1,165 @@ +queryString = $input['QueryString'] ?? null; + $this->clientToken = $input['ClientToken'] ?? null; + $this->nextToken = $input['NextToken'] ?? null; + $this->maxRows = $input['MaxRows'] ?? null; + parent::__construct($input); + } + + public static function create($input): self + { + return $input instanceof self ? $input : new self($input); + } + + public function getClientToken(): ?string + { + return $this->clientToken; + } + + public function getMaxRows(): ?int + { + return $this->maxRows; + } + + public function getNextToken(): ?string + { + return $this->nextToken; + } + + public function getQueryString(): ?string + { + return $this->queryString; + } + + /** + * @internal + */ + public function request(): Request + { + // Prepare headers + $headers = [ + 'Content-Type' => 'application/x-amz-json-1.0', + 'X-Amz-Target' => 'Timestream_20181101.Query', + ]; + + // Prepare query + $query = []; + + // Prepare URI + $uriString = '/'; + + // Prepare Body + $bodyPayload = $this->requestBody(); + $body = empty($bodyPayload) ? '{}' : json_encode($bodyPayload, 4194304); + + // Return the Request + return new Request('POST', $uriString, $query, $headers, StreamFactory::create($body)); + } + + public function setClientToken(?string $value): self + { + $this->clientToken = $value; + + return $this; + } + + public function setMaxRows(?int $value): self + { + $this->maxRows = $value; + + return $this; + } + + public function setNextToken(?string $value): self + { + $this->nextToken = $value; + + return $this; + } + + public function setQueryString(?string $value): self + { + $this->queryString = $value; + + return $this; + } + + private function requestBody(): array + { + $payload = []; + if (null === $v = $this->queryString) { + throw new InvalidArgument(sprintf('Missing parameter "QueryString" for "%s". The value cannot be null.', __CLASS__)); + } + $payload['QueryString'] = $v; + if (null === $v = $this->clientToken) { + $v = uuid_create(\UUID_TYPE_RANDOM); + } + $payload['ClientToken'] = $v; + if (null !== $v = $this->nextToken) { + $payload['NextToken'] = $v; + } + if (null !== $v = $this->maxRows) { + $payload['MaxRows'] = $v; + } + + return $payload; + } +} diff --git a/src/Result/CancelQueryResponse.php b/src/Result/CancelQueryResponse.php new file mode 100644 index 0000000..2abce6c --- /dev/null +++ b/src/Result/CancelQueryResponse.php @@ -0,0 +1,29 @@ +initialize(); + + return $this->cancellationMessage; + } + + protected function populateResult(Response $response): void + { + $data = $response->toArray(); + + $this->cancellationMessage = isset($data['CancellationMessage']) ? (string) $data['CancellationMessage'] : null; + } +} diff --git a/src/Result/PrepareQueryResponse.php b/src/Result/PrepareQueryResponse.php new file mode 100644 index 0000000..c75b9b7 --- /dev/null +++ b/src/Result/PrepareQueryResponse.php @@ -0,0 +1,140 @@ +initialize(); + + return $this->columns; + } + + /** + * @return ParameterMapping[] + */ + public function getParameters(): array + { + $this->initialize(); + + return $this->parameters; + } + + public function getQueryString(): string + { + $this->initialize(); + + return $this->queryString; + } + + protected function populateResult(Response $response): void + { + $data = $response->toArray(); + + $this->queryString = (string) $data['QueryString']; + $this->columns = $this->populateResultSelectColumnList($data['Columns']); + $this->parameters = $this->populateResultParameterMappingList($data['Parameters']); + } + + private function populateResultColumnInfo(array $json): ColumnInfo + { + return new ColumnInfo([ + 'Name' => isset($json['Name']) ? (string) $json['Name'] : null, + 'Type' => $this->populateResultType($json['Type']), + ]); + } + + /** + * @return ColumnInfo[] + */ + private function populateResultColumnInfoList(array $json): array + { + $items = []; + foreach ($json as $item) { + $items[] = $this->populateResultColumnInfo($item); + } + + return $items; + } + + private function populateResultParameterMapping(array $json): ParameterMapping + { + return new ParameterMapping([ + 'Name' => (string) $json['Name'], + 'Type' => $this->populateResultType($json['Type']), + ]); + } + + /** + * @return ParameterMapping[] + */ + private function populateResultParameterMappingList(array $json): array + { + $items = []; + foreach ($json as $item) { + $items[] = $this->populateResultParameterMapping($item); + } + + return $items; + } + + private function populateResultSelectColumn(array $json): SelectColumn + { + return new SelectColumn([ + 'Name' => isset($json['Name']) ? (string) $json['Name'] : null, + 'Type' => empty($json['Type']) ? null : $this->populateResultType($json['Type']), + 'DatabaseName' => isset($json['DatabaseName']) ? (string) $json['DatabaseName'] : null, + 'TableName' => isset($json['TableName']) ? (string) $json['TableName'] : null, + 'Aliased' => isset($json['Aliased']) ? filter_var($json['Aliased'], \FILTER_VALIDATE_BOOLEAN) : null, + ]); + } + + /** + * @return SelectColumn[] + */ + private function populateResultSelectColumnList(array $json): array + { + $items = []; + foreach ($json as $item) { + $items[] = $this->populateResultSelectColumn($item); + } + + return $items; + } + + private function populateResultType(array $json): Type + { + return new Type([ + 'ScalarType' => isset($json['ScalarType']) ? (string) $json['ScalarType'] : null, + 'ArrayColumnInfo' => empty($json['ArrayColumnInfo']) ? null : $this->populateResultColumnInfo($json['ArrayColumnInfo']), + 'TimeSeriesMeasureValueColumnInfo' => empty($json['TimeSeriesMeasureValueColumnInfo']) ? null : $this->populateResultColumnInfo($json['TimeSeriesMeasureValueColumnInfo']), + 'RowColumnInfo' => !isset($json['RowColumnInfo']) ? null : $this->populateResultColumnInfoList($json['RowColumnInfo']), + ]); + } +} diff --git a/src/Result/QueryResponse.php b/src/Result/QueryResponse.php new file mode 100644 index 0000000..f85ba75 --- /dev/null +++ b/src/Result/QueryResponse.php @@ -0,0 +1,247 @@ + + */ +class QueryResponse extends Result implements \IteratorAggregate +{ + /** + * A unique ID for the given query. + */ + private $queryId; + + /** + * A pagination token that can be used again on a `Query` call to get the next set of results. + */ + private $nextToken; + + /** + * The result set rows returned by the query. + */ + private $rows; + + /** + * The column data types of the returned result set. + */ + private $columnInfo; + + /** + * Information about the status of the query, including progress and bytes scanned. + */ + private $queryStatus; + + /** + * @return ColumnInfo[] + */ + public function getColumnInfo(): array + { + $this->initialize(); + + return $this->columnInfo; + } + + /** + * Iterates over Rows. + * + * @return \Traversable + */ + public function getIterator(): \Traversable + { + yield from $this->getRows(); + } + + public function getNextToken(): ?string + { + $this->initialize(); + + return $this->nextToken; + } + + public function getQueryId(): string + { + $this->initialize(); + + return $this->queryId; + } + + public function getQueryStatus(): ?QueryStatus + { + $this->initialize(); + + return $this->queryStatus; + } + + /** + * @param bool $currentPageOnly When true, iterates over items of the current page. Otherwise also fetch items in the next pages. + * + * @return iterable + */ + public function getRows(bool $currentPageOnly = false): iterable + { + if ($currentPageOnly) { + $this->initialize(); + yield from $this->rows; + + return; + } + + $client = $this->awsClient; + if (!$client instanceof TimestreamQueryClient) { + throw new InvalidArgument('missing client injected in paginated result'); + } + if (!$this->input instanceof QueryRequest) { + throw new InvalidArgument('missing last request injected in paginated result'); + } + $input = clone $this->input; + $page = $this; + while (true) { + $page->initialize(); + if ($page->nextToken) { + $input->setNextToken($page->nextToken); + + $this->registerPrefetch($nextPage = $client->query($input)); + } else { + $nextPage = null; + } + + yield from $page->rows; + + if (null === $nextPage) { + break; + } + + $this->unregisterPrefetch($nextPage); + $page = $nextPage; + } + } + + protected function populateResult(Response $response): void + { + $data = $response->toArray(); + + $this->queryId = (string) $data['QueryId']; + $this->nextToken = isset($data['NextToken']) ? (string) $data['NextToken'] : null; + $this->rows = $this->populateResultRowList($data['Rows']); + $this->columnInfo = $this->populateResultColumnInfoList($data['ColumnInfo']); + $this->queryStatus = empty($data['QueryStatus']) ? null : $this->populateResultQueryStatus($data['QueryStatus']); + } + + private function populateResultColumnInfo(array $json): ColumnInfo + { + return new ColumnInfo([ + 'Name' => isset($json['Name']) ? (string) $json['Name'] : null, + 'Type' => $this->populateResultType($json['Type']), + ]); + } + + /** + * @return ColumnInfo[] + */ + private function populateResultColumnInfoList(array $json): array + { + $items = []; + foreach ($json as $item) { + $items[] = $this->populateResultColumnInfo($item); + } + + return $items; + } + + private function populateResultDatum(array $json): Datum + { + return new Datum([ + 'ScalarValue' => isset($json['ScalarValue']) ? (string) $json['ScalarValue'] : null, + 'TimeSeriesValue' => !isset($json['TimeSeriesValue']) ? null : $this->populateResultTimeSeriesDataPointList($json['TimeSeriesValue']), + 'ArrayValue' => !isset($json['ArrayValue']) ? null : $this->populateResultDatumList($json['ArrayValue']), + 'RowValue' => empty($json['RowValue']) ? null : $this->populateResultRow($json['RowValue']), + 'NullValue' => isset($json['NullValue']) ? filter_var($json['NullValue'], \FILTER_VALIDATE_BOOLEAN) : null, + ]); + } + + /** + * @return Datum[] + */ + private function populateResultDatumList(array $json): array + { + $items = []; + foreach ($json as $item) { + $items[] = $this->populateResultDatum($item); + } + + return $items; + } + + private function populateResultQueryStatus(array $json): QueryStatus + { + return new QueryStatus([ + 'ProgressPercentage' => isset($json['ProgressPercentage']) ? (float) $json['ProgressPercentage'] : null, + 'CumulativeBytesScanned' => isset($json['CumulativeBytesScanned']) ? (string) $json['CumulativeBytesScanned'] : null, + 'CumulativeBytesMetered' => isset($json['CumulativeBytesMetered']) ? (string) $json['CumulativeBytesMetered'] : null, + ]); + } + + private function populateResultRow(array $json): Row + { + return new Row([ + 'Data' => $this->populateResultDatumList($json['Data']), + ]); + } + + /** + * @return Row[] + */ + private function populateResultRowList(array $json): array + { + $items = []; + foreach ($json as $item) { + $items[] = $this->populateResultRow($item); + } + + return $items; + } + + private function populateResultTimeSeriesDataPoint(array $json): TimeSeriesDataPoint + { + return new TimeSeriesDataPoint([ + 'Time' => (string) $json['Time'], + 'Value' => $this->populateResultDatum($json['Value']), + ]); + } + + /** + * @return TimeSeriesDataPoint[] + */ + private function populateResultTimeSeriesDataPointList(array $json): array + { + $items = []; + foreach ($json as $item) { + $items[] = $this->populateResultTimeSeriesDataPoint($item); + } + + return $items; + } + + private function populateResultType(array $json): Type + { + return new Type([ + 'ScalarType' => isset($json['ScalarType']) ? (string) $json['ScalarType'] : null, + 'ArrayColumnInfo' => empty($json['ArrayColumnInfo']) ? null : $this->populateResultColumnInfo($json['ArrayColumnInfo']), + 'TimeSeriesMeasureValueColumnInfo' => empty($json['TimeSeriesMeasureValueColumnInfo']) ? null : $this->populateResultColumnInfo($json['TimeSeriesMeasureValueColumnInfo']), + 'RowColumnInfo' => !isset($json['RowColumnInfo']) ? null : $this->populateResultColumnInfoList($json['RowColumnInfo']), + ]); + } +} diff --git a/src/TimestreamQueryClient.php b/src/TimestreamQueryClient.php new file mode 100644 index 0000000..b5e0057 --- /dev/null +++ b/src/TimestreamQueryClient.php @@ -0,0 +1,153 @@ +getResponse($input->request(), new RequestContext(['operation' => 'CancelQuery', 'region' => $input->getRegion(), 'exceptionMapping' => [ + 'AccessDeniedException' => AccessDeniedException::class, + 'InternalServerException' => InternalServerException::class, + 'ThrottlingException' => ThrottlingException::class, + 'ValidationException' => ValidationException::class, + 'InvalidEndpointException' => InvalidEndpointException::class, + ]])); + + return new CancelQueryResponse($response); + } + + /** + * A synchronous operation that allows you to submit a query with parameters to be stored by Timestream for later + * running. Timestream only supports using this operation with the `PrepareQueryRequest$ValidateOnly` set to `true`. + * + * @see https://docs.aws.amazon.com/timestream/latest/developerguide/API_Operations_Amazon_Timestream_Query.html/API_PrepareQuery.html + * @see https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-query.timestream-2018-11-01.html#preparequery + * + * @param array{ + * QueryString: string, + * ValidateOnly?: bool, + * @region?: string, + * }|PrepareQueryRequest $input + * + * @throws AccessDeniedException + * @throws InternalServerException + * @throws ThrottlingException + * @throws ValidationException + * @throws InvalidEndpointException + */ + public function prepareQuery($input): PrepareQueryResponse + { + $input = PrepareQueryRequest::create($input); + $response = $this->getResponse($input->request(), new RequestContext(['operation' => 'PrepareQuery', 'region' => $input->getRegion(), 'exceptionMapping' => [ + 'AccessDeniedException' => AccessDeniedException::class, + 'InternalServerException' => InternalServerException::class, + 'ThrottlingException' => ThrottlingException::class, + 'ValidationException' => ValidationException::class, + 'InvalidEndpointException' => InvalidEndpointException::class, + ]])); + + return new PrepareQueryResponse($response); + } + + /** + * `Query` is a synchronous operation that enables you to run a query against your Amazon Timestream data. `Query` will + * time out after 60 seconds. You must update the default timeout in the SDK to support a timeout of 60 seconds. See the + * code sample for details. + * + * @see https://docs.aws.amazon.com/timestream/latest/developerguide/code-samples.run-query.html + * @see https://docs.aws.amazon.com/timestream/latest/developerguide/API_Operations_Amazon_Timestream_Query.html/API_Query.html + * @see https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-query.timestream-2018-11-01.html#query + * + * @param array{ + * QueryString: string, + * ClientToken?: string, + * NextToken?: string, + * MaxRows?: int, + * @region?: string, + * }|QueryRequest $input + * + * @throws AccessDeniedException + * @throws ConflictException + * @throws InternalServerException + * @throws QueryExecutionException + * @throws ThrottlingException + * @throws ValidationException + * @throws InvalidEndpointException + */ + public function query($input): QueryResponse + { + $input = QueryRequest::create($input); + $response = $this->getResponse($input->request(), new RequestContext(['operation' => 'Query', 'region' => $input->getRegion(), 'exceptionMapping' => [ + 'AccessDeniedException' => AccessDeniedException::class, + 'ConflictException' => ConflictException::class, + 'InternalServerException' => InternalServerException::class, + 'QueryExecutionException' => QueryExecutionException::class, + 'ThrottlingException' => ThrottlingException::class, + 'ValidationException' => ValidationException::class, + 'InvalidEndpointException' => InvalidEndpointException::class, + ]])); + + return new QueryResponse($response, $this, $input); + } + + protected function getAwsErrorFactory(): AwsErrorFactoryInterface + { + return new JsonRpcAwsErrorFactory(); + } + + protected function getEndpointMetadata(?string $region): array + { + if (null === $region) { + $region = Configuration::DEFAULT_REGION; + } + + return [ + 'endpoint' => "https://query.timestream.$region.amazonaws.com", + 'signRegion' => $region, + 'signService' => 'timestream', + 'signVersions' => ['v4'], + ]; + } +} diff --git a/src/ValueObject/ColumnInfo.php b/src/ValueObject/ColumnInfo.php new file mode 100644 index 0000000..2d87fd3 --- /dev/null +++ b/src/ValueObject/ColumnInfo.php @@ -0,0 +1,48 @@ +name = $input['Name'] ?? null; + $this->type = isset($input['Type']) ? Type::create($input['Type']) : null; + } + + public static function create($input): self + { + return $input instanceof self ? $input : new self($input); + } + + public function getName(): ?string + { + return $this->name; + } + + public function getType(): Type + { + return $this->type; + } +} diff --git a/src/ValueObject/Datum.php b/src/ValueObject/Datum.php new file mode 100644 index 0000000..11e778d --- /dev/null +++ b/src/ValueObject/Datum.php @@ -0,0 +1,88 @@ +scalarValue = $input['ScalarValue'] ?? null; + $this->timeSeriesValue = isset($input['TimeSeriesValue']) ? array_map([TimeSeriesDataPoint::class, 'create'], $input['TimeSeriesValue']) : null; + $this->arrayValue = isset($input['ArrayValue']) ? array_map([Datum::class, 'create'], $input['ArrayValue']) : null; + $this->rowValue = isset($input['RowValue']) ? Row::create($input['RowValue']) : null; + $this->nullValue = $input['NullValue'] ?? null; + } + + public static function create($input): self + { + return $input instanceof self ? $input : new self($input); + } + + /** + * @return Datum[] + */ + public function getArrayValue(): array + { + return $this->arrayValue ?? []; + } + + public function getNullValue(): ?bool + { + return $this->nullValue; + } + + public function getRowValue(): ?Row + { + return $this->rowValue; + } + + public function getScalarValue(): ?string + { + return $this->scalarValue; + } + + /** + * @return TimeSeriesDataPoint[] + */ + public function getTimeSeriesValue(): array + { + return $this->timeSeriesValue ?? []; + } +} diff --git a/src/ValueObject/ParameterMapping.php b/src/ValueObject/ParameterMapping.php new file mode 100644 index 0000000..276ca33 --- /dev/null +++ b/src/ValueObject/ParameterMapping.php @@ -0,0 +1,43 @@ +name = $input['Name'] ?? null; + $this->type = isset($input['Type']) ? Type::create($input['Type']) : null; + } + + public static function create($input): self + { + return $input instanceof self ? $input : new self($input); + } + + public function getName(): string + { + return $this->name; + } + + public function getType(): Type + { + return $this->type; + } +} diff --git a/src/ValueObject/QueryStatus.php b/src/ValueObject/QueryStatus.php new file mode 100644 index 0000000..7356afa --- /dev/null +++ b/src/ValueObject/QueryStatus.php @@ -0,0 +1,61 @@ +progressPercentage = $input['ProgressPercentage'] ?? null; + $this->cumulativeBytesScanned = $input['CumulativeBytesScanned'] ?? null; + $this->cumulativeBytesMetered = $input['CumulativeBytesMetered'] ?? null; + } + + public static function create($input): self + { + return $input instanceof self ? $input : new self($input); + } + + public function getCumulativeBytesMetered(): ?string + { + return $this->cumulativeBytesMetered; + } + + public function getCumulativeBytesScanned(): ?string + { + return $this->cumulativeBytesScanned; + } + + public function getProgressPercentage(): ?float + { + return $this->progressPercentage; + } +} diff --git a/src/ValueObject/Row.php b/src/ValueObject/Row.php new file mode 100644 index 0000000..d6f661f --- /dev/null +++ b/src/ValueObject/Row.php @@ -0,0 +1,37 @@ +data = isset($input['Data']) ? array_map([Datum::class, 'create'], $input['Data']) : null; + } + + public static function create($input): self + { + return $input instanceof self ? $input : new self($input); + } + + /** + * @return Datum[] + */ + public function getData(): array + { + return $this->data ?? []; + } +} diff --git a/src/ValueObject/SelectColumn.php b/src/ValueObject/SelectColumn.php new file mode 100644 index 0000000..b8f7f5a --- /dev/null +++ b/src/ValueObject/SelectColumn.php @@ -0,0 +1,79 @@ +name = $input['Name'] ?? null; + $this->type = isset($input['Type']) ? Type::create($input['Type']) : null; + $this->databaseName = $input['DatabaseName'] ?? null; + $this->tableName = $input['TableName'] ?? null; + $this->aliased = $input['Aliased'] ?? null; + } + + public static function create($input): self + { + return $input instanceof self ? $input : new self($input); + } + + public function getAliased(): ?bool + { + return $this->aliased; + } + + public function getDatabaseName(): ?string + { + return $this->databaseName; + } + + public function getName(): ?string + { + return $this->name; + } + + public function getTableName(): ?string + { + return $this->tableName; + } + + public function getType(): ?Type + { + return $this->type; + } +} diff --git a/src/ValueObject/TimeSeriesDataPoint.php b/src/ValueObject/TimeSeriesDataPoint.php new file mode 100644 index 0000000..404bd74 --- /dev/null +++ b/src/ValueObject/TimeSeriesDataPoint.php @@ -0,0 +1,48 @@ +time = $input['Time'] ?? null; + $this->value = isset($input['Value']) ? Datum::create($input['Value']) : null; + } + + public static function create($input): self + { + return $input instanceof self ? $input : new self($input); + } + + public function getTime(): string + { + return $this->time; + } + + public function getValue(): Datum + { + return $this->value; + } +} diff --git a/src/ValueObject/Type.php b/src/ValueObject/Type.php new file mode 100644 index 0000000..52088ad --- /dev/null +++ b/src/ValueObject/Type.php @@ -0,0 +1,75 @@ +scalarType = $input['ScalarType'] ?? null; + $this->arrayColumnInfo = isset($input['ArrayColumnInfo']) ? ColumnInfo::create($input['ArrayColumnInfo']) : null; + $this->timeSeriesMeasureValueColumnInfo = isset($input['TimeSeriesMeasureValueColumnInfo']) ? ColumnInfo::create($input['TimeSeriesMeasureValueColumnInfo']) : null; + $this->rowColumnInfo = isset($input['RowColumnInfo']) ? array_map([ColumnInfo::class, 'create'], $input['RowColumnInfo']) : null; + } + + public static function create($input): self + { + return $input instanceof self ? $input : new self($input); + } + + public function getArrayColumnInfo(): ?ColumnInfo + { + return $this->arrayColumnInfo; + } + + /** + * @return ColumnInfo[] + */ + public function getRowColumnInfo(): array + { + return $this->rowColumnInfo ?? []; + } + + /** + * @return ScalarType::*|null + */ + public function getScalarType(): ?string + { + return $this->scalarType; + } + + public function getTimeSeriesMeasureValueColumnInfo(): ?ColumnInfo + { + return $this->timeSeriesMeasureValueColumnInfo; + } +} diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/tests/Integration/TimestreamQueryClientTest.php b/tests/Integration/TimestreamQueryClientTest.php new file mode 100644 index 0000000..ddd1a58 --- /dev/null +++ b/tests/Integration/TimestreamQueryClientTest.php @@ -0,0 +1,80 @@ +getClient(); + + $input = new CancelQueryRequest([ + 'QueryId' => 'change me', + ]); + $result = $client->cancelQuery($input); + + $result->resolve(); + + self::assertSame('changeIt', $result->getCancellationMessage()); + } + + public function testPrepareQuery(): void + { + self::markTestIncomplete('Cannot test without support for timestream.'); + + $client = $this->getClient(); + + $input = new PrepareQueryRequest([ + 'QueryString' => 'change me', + 'ValidateOnly' => false, + ]); + $result = $client->prepareQuery($input); + + $result->resolve(); + + self::assertSame('changeIt', $result->getQueryString()); + // self::assertTODO(expected, $result->getColumns()); + // self::assertTODO(expected, $result->getParameters()); + } + + public function testQuery(): void + { + self::markTestIncomplete('Cannot test without support for timestream.'); + + $client = $this->getClient(); + + $input = new QueryRequest([ + 'QueryString' => 'change me', + 'ClientToken' => 'change me', + 'NextToken' => 'change me', + 'MaxRows' => 1337, + ]); + $result = $client->query($input); + + $result->resolve(); + + self::assertSame('changeIt', $result->getQueryId()); + self::assertSame('changeIt', $result->getNextToken()); + // self::assertTODO(expected, $result->getRows()); + // self::assertTODO(expected, $result->getColumnInfo()); + // self::assertTODO(expected, $result->getQueryStatus()); + } + + private function getClient(): TimestreamQueryClient + { + self::fail('Not implemented'); + + return new TimestreamQueryClient([ + 'endpoint' => 'http://localhost', + ], new NullProvider()); + } +} diff --git a/tests/Unit/Input/CancelQueryRequestTest.php b/tests/Unit/Input/CancelQueryRequestTest.php new file mode 100644 index 0000000..4b87c8c --- /dev/null +++ b/tests/Unit/Input/CancelQueryRequestTest.php @@ -0,0 +1,29 @@ + 'qwertyuiop', + ]); + + // see https://docs.aws.amazon.com/timestream/latest/developerguide/API_Operations_Amazon_Timestream_Query.html/API_CancelQuery.html + $expected = ' + POST / HTTP/1.0 + Content-Type: application/x-amz-json-1.0 + x-amz-target: Timestream_20181101.CancelQuery + + { + "QueryId": "qwertyuiop" + } + '; + + self::assertRequestEqualsHttpRequest($expected, $input->request()); + } +} diff --git a/tests/Unit/Input/PrepareQueryRequestTest.php b/tests/Unit/Input/PrepareQueryRequestTest.php new file mode 100644 index 0000000..fcdd603 --- /dev/null +++ b/tests/Unit/Input/PrepareQueryRequestTest.php @@ -0,0 +1,31 @@ + 'SELECT * FROM db.tbl ORDER BY time DESC LIMIT 10', + 'ValidateOnly' => true, + ]); + + // see https://docs.aws.amazon.com/timestream/latest/developerguide/API_Operations_Amazon_Timestream_Query.html/API_PrepareQuery.html + $expected = ' + POST / HTTP/1.0 + Content-Type: application/x-amz-json-1.0 + x-amz-target: Timestream_20181101.PrepareQuery + + { + "QueryString": "SELECT * FROM db.tbl ORDER BY time DESC LIMIT 10", + "ValidateOnly": true + } + '; + + self::assertRequestEqualsHttpRequest($expected, $input->request()); + } +} diff --git a/tests/Unit/Input/QueryRequestTest.php b/tests/Unit/Input/QueryRequestTest.php new file mode 100644 index 0000000..82b2cde --- /dev/null +++ b/tests/Unit/Input/QueryRequestTest.php @@ -0,0 +1,31 @@ + 'qwertyuiop', + 'QueryString' => 'SELECT * FROM db.tbl ORDER BY time DESC LIMIT 10', + ]); + + // see https://docs.aws.amazon.com/timestream/latest/developerguide/API_Operations_Amazon_Timestream_Query.html/API_Query.html + $expected = ' + POST / HTTP/1.0 + Content-Type: application/x-amz-json-1.0 + x-amz-target: Timestream_20181101.Query + + { + "ClientToken": "qwertyuiop", + "QueryString": "SELECT * FROM db.tbl ORDER BY time DESC LIMIT 10" + } + '; + + self::assertRequestEqualsHttpRequest($expected, $input->request()); + } +} diff --git a/tests/Unit/Result/CancelQueryResponseTest.php b/tests/Unit/Result/CancelQueryResponseTest.php new file mode 100644 index 0000000..177c48c --- /dev/null +++ b/tests/Unit/Result/CancelQueryResponseTest.php @@ -0,0 +1,26 @@ +request('POST', 'http://localhost'), $client, new NullLogger())); + + self::assertSame('Query cancelled.', $result->getCancellationMessage()); + } +} diff --git a/tests/Unit/Result/PrepareQueryResponseTest.php b/tests/Unit/Result/PrepareQueryResponseTest.php new file mode 100644 index 0000000..7924008 --- /dev/null +++ b/tests/Unit/Result/PrepareQueryResponseTest.php @@ -0,0 +1,50 @@ +request('POST', 'http://localhost'), $client, new NullLogger())); + + self::assertCount(1, $result->getColumns()); + self::assertInstanceOf(SelectColumn::class, $result->getColumns()[0]); + self::assertFalse($result->getColumns()[0]->getAliased()); + self::assertSame('db', $result->getColumns()[0]->getDatabaseName()); + self::assertSame('foo', $result->getColumns()[0]->getName()); + self::assertSame('tbl', $result->getColumns()[0]->getTableName()); + self::assertInstanceOf(Type::class, $result->getColumns()[0]->getType()); + self::assertSame(ScalarType::VARCHAR, $result->getColumns()[0]->getType()->getScalarType()); + self::assertSame([], $result->getParameters()); + self::assertSame('query string', $result->getQueryString()); + } +} diff --git a/tests/Unit/Result/QueryResponseTest.php b/tests/Unit/Result/QueryResponseTest.php new file mode 100644 index 0000000..058bb20 --- /dev/null +++ b/tests/Unit/Result/QueryResponseTest.php @@ -0,0 +1,73 @@ +request('POST', 'http://localhost'), $client, new NullLogger()), new TimestreamQueryClient(), new QueryRequest([])); + + self::assertCount(1, $result->getColumnInfo()); + self::assertInstanceOf(ColumnInfo::class, $result->getColumnInfo()[0]); + self::assertSame('foo', $result->getColumnInfo()[0]->getName()); + self::assertInstanceOf(Type::class, $result->getColumnInfo()[0]->getType()); + self::assertSame(ScalarType::VARCHAR, $result->getColumnInfo()[0]->getType()->getScalarType()); + self::assertSame('qwertyuiop', $result->getQueryId()); + self::assertInstanceOf(QueryStatus::class, $result->getQueryStatus()); + self::assertSame('1024', $result->getQueryStatus()->getCumulativeBytesMetered()); + self::assertSame('800', $result->getQueryStatus()->getCumulativeBytesScanned()); + self::assertSame(1.0, $result->getQueryStatus()->getProgressPercentage()); + + $rows = iterator_to_array($result->getRows(true)); + + self::assertCount(1, $rows); + self::assertInstanceOf(Row::class, $rows[0]); + self::assertCount(1, $rows[0]->getData()); + self::assertInstanceOf(Datum::class, $rows[0]->getData()[0]); + self::assertSame('datum', $rows[0]->getData()[0]->getScalarValue()); + } +} diff --git a/tests/Unit/TimestreamQueryClientTest.php b/tests/Unit/TimestreamQueryClientTest.php new file mode 100644 index 0000000..b7a9877 --- /dev/null +++ b/tests/Unit/TimestreamQueryClientTest.php @@ -0,0 +1,60 @@ + 'qwertyuiop', + ]); + $result = $client->cancelQuery($input); + + self::assertInstanceOf(CancelQueryResponse::class, $result); + self::assertFalse($result->info()['resolved']); + } + + public function testPrepareQuery(): void + { + $client = new TimestreamQueryClient([], new NullProvider(), new MockHttpClient()); + + $input = new PrepareQueryRequest([ + 'QueryString' => 'SELECT * FROM db.tbl ORDER BY time DESC LIMIT 10', + 'ValidateOnly' => true, + + ]); + $result = $client->prepareQuery($input); + + self::assertInstanceOf(PrepareQueryResponse::class, $result); + self::assertFalse($result->info()['resolved']); + } + + public function testQuery(): void + { + $client = new TimestreamQueryClient([], new NullProvider(), new MockHttpClient()); + + $input = new QueryRequest([ + 'ClientToken' => 'qwertyuiop', + 'QueryString' => 'SELECT * FROM db.tbl ORDER BY time DESC LIMIT 10', + + ]); + $result = $client->query($input); + + self::assertInstanceOf(QueryResponse::class, $result); + self::assertFalse($result->info()['resolved']); + } +}