From a105bfa72b1928cdd276bebe461fec72e7fa24c3 Mon Sep 17 00:00:00 2001 From: David Supplee Date: Mon, 21 Aug 2017 17:08:54 -0400 Subject: [PATCH] [BC Break] Implement waitUntilComplete and block while waiting for query results (#642) * implement waitUntilComplete on job and block waiting for queryResults * fix getIterator docblock --- src/BigQuery/BigQueryClient.php | 94 ++++++------------- src/BigQuery/Job.php | 72 ++++++++++++-- src/BigQuery/QueryResults.php | 92 ++++++------------ .../snippets/BigQuery/BigQueryClientTest.php | 78 +++++++++------ tests/snippets/BigQuery/JobTest.php | 17 +++- tests/snippets/BigQuery/QueryResultsTest.php | 19 ---- tests/unit/BigQuery/BigQueryClientTest.php | 29 ++++-- tests/unit/BigQuery/JobTest.php | 32 ++++++- tests/unit/BigQuery/QueryResultsTest.php | 23 ++--- 9 files changed, 250 insertions(+), 206 deletions(-) diff --git a/src/BigQuery/BigQueryClient.php b/src/BigQuery/BigQueryClient.php index 5a9e3339ec0e..dc22e59402e8 100644 --- a/src/BigQuery/BigQueryClient.php +++ b/src/BigQuery/BigQueryClient.php @@ -140,7 +140,7 @@ public function __construct(array $config = []) * ``` * $queryResults = $bigQuery->runQuery('SELECT commit FROM `bigquery-public-data.github_repos.commits` LIMIT 100'); * - * foreach ($queryResults->rows() as $row) { + * foreach ($queryResults as $row) { * echo $row['commit']; * } * ``` @@ -156,7 +156,7 @@ public function __construct(array $config = []) * ] * ]); * - * foreach ($queryResults->rows() as $row) { + * foreach ($queryResults as $row) { * echo $row['commit']; * } * ``` @@ -168,7 +168,7 @@ public function __construct(array $config = []) * 'parameters' => ['A commit message.'] * ]); * - * foreach ($queryResults->rows() as $row) { + * foreach ($queryResults as $row) { * echo $row['commit']; * } * ``` @@ -183,78 +183,46 @@ public function __construct(array $config = []) * of results. Setting this flag to a small value such as 1000 and * then paging through results might improve reliability when the * query result set is large. - * @type array $defaultDataset Specifies the default datasetId and - * projectId to assume for any unqualified table names in the - * query. If not set, all table names in the query string must be - * qualified in the format 'datasetId.tableId'. + * @type int $startIndex Zero-based index of the starting row. * @type int $timeoutMs How long to wait for the query to complete, in * milliseconds. **Defaults to** `10000` milliseconds (10 seconds). * @type int $maxRetries The number of times to retry, checking if the * query has completed. **Defaults to** `100`. - * @type bool $useQueryCache Whether to look for the result in the query - * cache. - * @type bool $useLegacySql If set to true the query will use - * [BigQuery's legacy SQL](https://cloud.google.com/bigquery/docs/reference/legacy-sql), - * otherwise [BigQuery's standard SQL](https://cloud.google.com/bigquery/sql-reference). - * **Defaults to** `false`. * @type array $parameters Only available for standard SQL queries. * When providing a non-associative array positional parameters * (`?`) will be used. When providing an associative array * named parameters will be used (`@name`). + * @type array $jobConfig Configuration settings for a query job are + * outlined in the [API Docs for `configuration.query`](https://goo.gl/PuRa3I). + * If not provided default settings will be used, with the exception + * of `configuration.query.useLegacySql`, which defaults to `false` + * in this client. * } * @return QueryResults - * @throws \RuntimeException if the maximum number of retries while waiting + * @throws \RuntimeException If the maximum number of retries while waiting * for query completion has been exceeded. */ public function runQuery($query, array $options = []) { - $options += [ - 'maxRetries' => 100, - 'useLegacySql' => false - ]; - - if (isset($options['parameters'])) { - $options += $this->formatQueryParameters($options['parameters']); - unset($options['parameters']); - } - - $queryOptions = $options; - unset($queryOptions['timeoutMs'], $queryOptions['maxRetries']); - - $response = $this->connection->query([ - 'projectId' => $this->projectId, - 'query' => $query - ] + $queryOptions); - - $results = new QueryResults( - $this->connection, - $response['jobReference']['jobId'], - $this->projectId, - $response, - $options, - $this->mapper - ); - - if (!$results->isComplete()) { - $retryFn = function (QueryResults $results, array $options) { - $results->reload($options); - - if (!$results->isComplete()) { - throw new \RuntimeException('Job did not complete within the allowed number of retries.'); - } - }; - - $retry = new ExponentialBackoff($options['maxRetries']); - $retry->execute($retryFn, [$results, $options]); - } - - return $results; + $jobOptions = $this->pluckArray([ + 'parameters', + 'jobConfig' + ], $options); + $queryResultsOptions = $this->pluckArray([ + 'maxResults', + 'startIndex', + 'timeoutMs', + 'maxRetries' + ], $options); + + return $this->runQueryAsJob( + $query, + $jobOptions + $options + )->queryResults($queryResultsOptions + $options); } /** - * Runs a BigQuery SQL query in an asynchronous fashion. Running a query - * in this fashion requires you to poll for the status before being able - * to access results. + * Runs a BigQuery SQL query in an asynchronous fashion. * * Queries constructed using * [standard SQL](https://cloud.google.com/bigquery/docs/reference/standard-sql/) @@ -264,17 +232,9 @@ public function runQuery($query, array $options = []) * Example: * ``` * $job = $bigQuery->runQueryAsJob('SELECT commit FROM `bigquery-public-data.github_repos.commits` LIMIT 100'); - * - * $isComplete = false; * $queryResults = $job->queryResults(); * - * while (!$isComplete) { - * sleep(1); // let's wait for a moment... - * $queryResults->reload(); // trigger a network request - * $isComplete = $queryResults->isComplete(); // check the query's status - * } - * - * foreach ($queryResults->rows() as $row) { + * foreach ($queryResults as $row) { * echo $row['commit']; * } * ``` diff --git a/src/BigQuery/Job.php b/src/BigQuery/Job.php index 3c04f3552710..771fc184b322 100644 --- a/src/BigQuery/Job.php +++ b/src/BigQuery/Job.php @@ -18,7 +18,9 @@ namespace Google\Cloud\BigQuery; use Google\Cloud\BigQuery\Connection\ConnectionInterface; +use Google\Cloud\Core\ArrayTrait; use Google\Cloud\Core\Exception\NotFoundException; +use Google\Cloud\Core\ExponentialBackoff; /** * [Jobs](https://cloud.google.com/bigquery/docs/reference/v2/jobs) are objects @@ -27,6 +29,10 @@ */ class Job { + use ArrayTrait; + + const MAX_RETRIES = 100; + /** * @var ConnectionInterface Represents a connection to BigQuery. */ @@ -121,7 +127,8 @@ public function cancel(array $options = []) } /** - * Retrieves the results of a query job. + * Retrieves the results of a query job, blocking until results are + * available. * * Example: * ``` @@ -138,21 +145,50 @@ public function cancel(array $options = []) * @type int $startIndex Zero-based index of the starting row. * @type int $timeoutMs How long to wait for the query to complete, in * milliseconds. **Defaults to** `10000` milliseconds (10 seconds). + * @type int $maxRetries The number of times to retry, checking if the + * query has completed. **Defaults to** `100`. * } * @return QueryResults + * @throws \RuntimeException If the maximum number of retries while waiting + * for query completion has been exceeded. */ public function queryResults(array $options = []) { - $response = $this->connection->getQueryResults($options + $this->identity); - - return new QueryResults( + $maxRetries = $this->pluck('maxRetries', $options, false); + $results = new QueryResults( $this->connection, $this->identity['jobId'], $this->identity['projectId'], - $response, - $options, + $this->connection->getQueryResults($options + $this->identity), $this->mapper ); + $this->wait($results, $options + [ + 'maxRetries' => $maxRetries + ]); + + return $results; + } + + /** + * Blocks until the job is complete. + * + * Example: + * ``` + * $job->waitUntilComplete(); + * ``` + * + * @param array $options [optional] { + * Configuration options. + * + * @type int $maxRetries The number of times to retry, checking if the + * query has completed. **Defaults to** `100`. + * } + * @throws \RuntimeException If the maximum number of retries while waiting + * for query completion has been exceeded. + */ + public function waitUntilComplete(array $options = []) + { + $this->wait($this, $options); } /** @@ -171,6 +207,7 @@ public function queryResults(array $options = []) * * echo 'Query complete!'; * ``` + * * @param array $options [optional] Configuration options. * @return bool */ @@ -255,4 +292,27 @@ public function identity() { return $this->identity; } + + /** + * Waits for an operation to complete. + * + * @param mixed $context + * @param array $options + */ + private function wait($context, array $options) + { + if (!$context->isComplete()) { + $maxRetries = $this->pluck('maxRetries', $options, false) ?: self::MAX_RETRIES; + $retryFn = function () use ($context, $options) { + $context->reload($options); + + if (!$context->isComplete()) { + throw new \RuntimeException('Job did not complete within the allowed number of retries.'); + } + }; + + $retry = new ExponentialBackoff($maxRetries); + $retry->execute($retryFn); + } + } } diff --git a/src/BigQuery/QueryResults.php b/src/BigQuery/QueryResults.php index d1b7e5f6e866..5625f5ed676b 100644 --- a/src/BigQuery/QueryResults.php +++ b/src/BigQuery/QueryResults.php @@ -30,7 +30,7 @@ * calling {@see Google\Cloud\BigQuery\BigQueryClient::runQuery()} or * {@see Google\Cloud\BigQuery\Job::queryResults()}. */ -class QueryResults +class QueryResults implements \IteratorAggregate { /** * @var ConnectionInterface Represents a connection to BigQuery. @@ -52,18 +52,12 @@ class QueryResults */ private $mapper; - /** - * @var array The options to use when reloading query data. - */ - private $reloadOptions; - /** * @param ConnectionInterface $connection Represents a connection to * BigQuery. * @param string $jobId The job's ID. * @param string $projectId The project's ID. * @param array $info The query result's metadata. - * @param array $reloadOptions The options to use when reloading query data. * @param ValueMapper $mapper Maps values between PHP and BigQuery. */ public function __construct( @@ -71,12 +65,10 @@ public function __construct( $jobId, $projectId, array $info, - array $reloadOptions, ValueMapper $mapper ) { $this->connection = $connection; $this->info = $info; - $this->reloadOptions = $reloadOptions; $this->identity = [ 'jobId' => $jobId, 'projectId' => $projectId @@ -86,8 +78,7 @@ public function __construct( /** * Retrieves the rows associated with the query and merges them together - * with the table's schema. It is recommended to check the completeness of - * the query before attempting to access rows. + * with the table's schema. * * Refer to the table below for a guide on how BigQuery types are mapped as * they come back from the API. @@ -109,27 +100,19 @@ public function __construct( * * Example: * ``` - * $isComplete = $queryResults->isComplete(); + * $rows = $queryResults->rows(); * - * if ($isComplete) { - * $rows = $queryResults->rows(); - * - * foreach ($rows as $row) { - * echo $row['name'] . PHP_EOL; - * } + * foreach ($rows as $row) { + * echo $row['name'] . PHP_EOL; * } * ``` * * @param array $options [optional] Configuration options. * @return ItemIterator - * @throws GoogleException Thrown if the query has not yet completed. + * @throws GoogleException Thrown in the case of a malformed response. */ public function rows(array $options = []) { - if (!$this->isComplete()) { - throw new GoogleException('The query has not completed yet.'); - } - $schema = $this->info['schema']['fields']; return new ItemIterator( @@ -163,30 +146,6 @@ function (array $row) use ($schema) { ); } - /** - * Checks the query's completeness. Useful in combination with - * {@see Google\Cloud\BigQuery\QueryResults::reload()} to poll for query status. - * - * Example: - * ``` - * $isComplete = $queryResults->isComplete(); - * - * while (!$isComplete) { - * sleep(1); // small delay between requests - * $queryResults->reload(); - * $isComplete = $queryResults->isComplete(); - * } - * - * echo 'Query complete!'; - * ``` - * - * @return bool - */ - public function isComplete() - { - return $this->info['jobComplete']; - } - /** * Retrieves the cached query details. * @@ -209,20 +168,9 @@ public function info() /** * Triggers a network request to reload the query's details. * - * Useful when needing to poll an incomplete query - * for status. Configuration options will be inherited from - * {@see Google\Cloud\BigQuery\Job::queryResults()} or - * {@see Google\Cloud\BigQuery\BigQueryClient::runQuery()}, but they can be - * overridden if needed. - * * Example: * ``` - * $queryResults->isComplete(); // returns false - * sleep(1); // let's wait for a moment... - * $queryResults->reload(); // executes a network request - * if ($queryResults->isComplete()) { - * echo "Query complete!"; - * } + * $queryResults->reload(); * ``` * * @see https://cloud.google.com/bigquery/docs/reference/v2/jobs/getQueryResults @@ -240,8 +188,9 @@ public function info() */ public function reload(array $options = []) { - $options += $this->identity; - return $this->info = $this->connection->getQueryResults($options + $this->reloadOptions); + return $this->info = $this->connection->getQueryResults( + $options + $this->identity + ); } /** @@ -260,4 +209,25 @@ public function identity() { return $this->identity; } + + /** + * Checks the query's completeness. + * + * @access private + * @return bool + */ + public function isComplete() + { + return $this->info['jobComplete']; + } + + /** + * @access private + * @return ItemIterator + * @throws GoogleException Thrown in the case of a malformed response. + */ + public function getIterator() + { + return $this->rows(); + } } diff --git a/tests/snippets/BigQuery/BigQueryClientTest.php b/tests/snippets/BigQuery/BigQueryClientTest.php index 0f9693fab76e..e89986a6272f 100644 --- a/tests/snippets/BigQuery/BigQueryClientTest.php +++ b/tests/snippets/BigQuery/BigQueryClientTest.php @@ -79,7 +79,7 @@ public function testRunQuery() { $snippet = $this->snippetFromMethod(BigQueryClient::class, 'runQuery'); $snippet->addLocal('bigQuery', $this->client); - $this->connection->query(Argument::any()) + $this->connection->insertJob(Argument::any()) ->shouldBeCalled() ->willReturn([ 'jobComplete' => false, @@ -101,27 +101,39 @@ public function testRunQueryWithNamedParameters() { $snippet = $this->snippetFromMethod(BigQueryClient::class, 'runQuery', 1); $snippet->addLocal('bigQuery', $this->client); + $expectedQuery = 'SELECT commit FROM `bigquery-public-data.github_repos.commits`' . + 'WHERE author.date < @date AND message = @message LIMIT 100'; $this->connection - ->query(Argument::withEntry('queryParameters', [ - [ - 'name' => 'date', - 'parameterType' => [ - 'type' => 'TIMESTAMP' - ], - 'parameterValue' => [ - 'value' => '1980-01-01 12:15:00.000000+00:00' - ] - ], - [ - 'name' => 'message', - 'parameterType' => [ - 'type' => 'STRING' - ], - 'parameterValue' => [ - 'value' => 'A commit message.' + ->insertJob([ + 'projectId' => 'my-awesome-project', + 'configuration' => [ + 'query' => [ + 'parameterMode' => 'named', + 'useLegacySql' => false, + 'queryParameters' => [ + [ + 'name' => 'date', + 'parameterType' => [ + 'type' => 'TIMESTAMP' + ], + 'parameterValue' => [ + 'value' => '1980-01-01 12:15:00.000000+00:00' + ] + ], + [ + 'name' => 'message', + 'parameterType' => [ + 'type' => 'STRING' + ], + 'parameterValue' => [ + 'value' => 'A commit message.' + ] + ] + ], + 'query' => $expectedQuery ] ] - ])) + ]) ->shouldBeCalledTimes(1) ->willReturn([ 'jobComplete' => false, @@ -143,17 +155,28 @@ public function testRunQueryWithPositionalParameters() { $snippet = $this->snippetFromMethod(BigQueryClient::class, 'runQuery', 2); $snippet->addLocal('bigQuery', $this->client); + $expectedQuery = 'SELECT commit FROM `bigquery-public-data.github_repos.commits` WHERE message = ? LIMIT 100'; $this->connection - ->query(Argument::withEntry('queryParameters', [ - [ - 'parameterType' => [ - 'type' => 'STRING' - ], - 'parameterValue' => [ - 'value' => 'A commit message.' + ->insertJob([ + 'projectId' => 'my-awesome-project', + 'configuration' => [ + 'query' => [ + 'parameterMode' => 'positional', + 'useLegacySql' => false, + 'queryParameters' => [ + [ + 'parameterType' => [ + 'type' => 'STRING' + ], + 'parameterValue' => [ + 'value' => 'A commit message.' + ] + ] + ], + 'query' => $expectedQuery ] ] - ])) + ]) ->shouldBeCalledTimes(1) ->willReturn([ 'jobComplete' => false, @@ -175,7 +198,6 @@ public function testRunQueryAsJob() { $snippet = $this->snippetFromMethod(BigQueryClient::class, 'runQueryAsJob'); $snippet->addLocal('bigQuery', $this->client); - $snippet->replace('sleep(1);', ''); $this->connection->insertJob(Argument::any()) ->shouldBeCalledTimes(1) ->willReturn([ diff --git a/tests/snippets/BigQuery/JobTest.php b/tests/snippets/BigQuery/JobTest.php index b9a28cbeb2c5..32c6aa58d37c 100644 --- a/tests/snippets/BigQuery/JobTest.php +++ b/tests/snippets/BigQuery/JobTest.php @@ -82,7 +82,6 @@ public function testCancel() ] ]); $snippet = $this->snippetFromMethod(Job::class, 'cancel'); - $snippet->replace('sleep(1);', ''); $snippet->addLocal('job', $job); $snippet->invoke(); } @@ -91,7 +90,7 @@ public function testQueryResults() { $this->connection->getQueryResults(Argument::any()) ->shouldBeCalledTimes(1) - ->willReturn([]); + ->willReturn(['jobComplete' => true]); $job = $this->getJob($this->connection); $snippet = $this->snippetFromMethod(Job::class, 'queryResults'); $snippet->addLocal('job', $job); @@ -100,6 +99,18 @@ public function testQueryResults() $this->assertInstanceOf(QueryResults::class, $res->returnVal()); } + public function testWaitUntilComplete() + { + $job = $this->getJob($this->connection, [ + 'status' => [ + 'state' => 'DONE' + ] + ]); + $snippet = $this->snippetFromMethod(Job::class, 'waitUntilComplete'); + $snippet->addLocal('job', $job); + $snippet->invoke(); + } + public function testIsComplete() { $this->connection->getJob(Argument::any()) @@ -116,7 +127,6 @@ public function testIsComplete() ]); $snippet = $this->snippetFromMethod(Job::class, 'isComplete'); $snippet->addLocal('job', $job); - $snippet->replace('sleep(1);', ''); $snippet->invoke(); } @@ -151,7 +161,6 @@ public function testReload() ]); $snippet = $this->snippetFromMethod(Job::class, 'reload'); $snippet->addLocal('job', $job); - $snippet->replace('sleep(1);', ''); $snippet->invoke(); } diff --git a/tests/snippets/BigQuery/QueryResultsTest.php b/tests/snippets/BigQuery/QueryResultsTest.php index 058164489466..11918b34e58d 100644 --- a/tests/snippets/BigQuery/QueryResultsTest.php +++ b/tests/snippets/BigQuery/QueryResultsTest.php @@ -60,7 +60,6 @@ public function setUp() ] ] ]; - $this->reload = []; $this->connection = $this->prophesize(ConnectionInterface::class); $this->qr = \Google\Cloud\Dev\stub(QueryResults::class, [ @@ -68,7 +67,6 @@ public function setUp() self::JOB_ID, self::PROJECT, $this->info, - $this->reload, new ValueMapper(false) ]); } @@ -90,21 +88,6 @@ public function testRows() $this->assertEquals('abcd', trim($res->output())); } - public function testIsComplete() - { - $snippet = $this->snippetFromMethod(QueryResults::class, 'isComplete'); - $snippet->addLocal('queryResults', $this->qr); - - $this->info['jobComplete'] = true; - $this->connection->getQueryResults(Argument::any()) - ->willReturn($this->info); - - $this->qr->___setProperty('connection', $this->connection->reveal()); - - $res = $snippet->invoke(); - $this->assertEquals('Query complete!', $res->output()); - } - public function testIdentity() { $snippet = $this->snippetFromMethod(QueryResults::class, 'identity'); @@ -133,9 +116,7 @@ public function testReload() $snippet = $this->snippetFromMethod(QueryResults::class, 'reload'); $snippet->addLocal('queryResults', $this->qr); - $snippet->replace('sleep(1);', ''); $res = $snippet->invoke(); - $this->assertEquals('Query complete!', $res->output()); } } diff --git a/tests/unit/BigQuery/BigQueryClientTest.php b/tests/unit/BigQuery/BigQueryClientTest.php index 2a16851e7e9a..95bb39d4c7c6 100644 --- a/tests/unit/BigQuery/BigQueryClientTest.php +++ b/tests/unit/BigQuery/BigQueryClientTest.php @@ -51,7 +51,19 @@ public function setUp() */ public function testRunsQuery($query, $options, $expected) { - $this->connection->query($expected) + $projectId = $expected['projectId']; + unset($expected['projectId']); + $this->connection->insertJob([ + 'projectId' => $projectId, + 'configuration' => [ + 'query' => $expected + ] + ]) + ->willReturn([ + 'jobReference' => ['jobId' => $this->jobId] + ]) + ->shouldBeCalledTimes(1); + $this->connection->getQueryResults(Argument::any()) ->willReturn([ 'jobReference' => [ 'jobId' => $this->jobId @@ -71,15 +83,18 @@ public function testRunsQuery($query, $options, $expected) */ public function testRunsQueryWithRetry($query, $options, $expected) { - $this->connection->query($expected) + $projectId = $expected['projectId']; + unset($expected['projectId']); + $this->connection->insertJob([ + 'projectId' => $projectId, + 'configuration' => [ + 'query' => $expected + ] + ]) ->willReturn([ - 'jobReference' => [ - 'jobId' => $this->jobId - ], - 'jobComplete' => false + 'jobReference' => ['jobId' => $this->jobId] ]) ->shouldBeCalledTimes(1); - $this->connection->getQueryResults(Argument::any()) ->willReturn([ 'jobReference' => [ diff --git a/tests/unit/BigQuery/JobTest.php b/tests/unit/BigQuery/JobTest.php index b89885d36ef7..ed2d0a205941 100644 --- a/tests/unit/BigQuery/JobTest.php +++ b/tests/unit/BigQuery/JobTest.php @@ -79,13 +79,43 @@ public function testCancel() public function testGetsQueryResults() { $this->connection->getQueryResults(Argument::any()) - ->willReturn(['jobReference' => ['jobId' => $this->jobId]]) + ->willReturn([ + 'jobReference' => [ + 'jobId' => $this->jobId + ], + 'jobComplete' => true + ]) ->shouldBeCalledTimes(1); $job = $this->getJob($this->connection); $this->assertInstanceOf(QueryResults::class, $job->queryResults()); } + public function testWaitsUntilComplete() + { + $this->jobInfo['status']['state'] = 'RUNNING'; + $this->connection->getJob(Argument::any()) + ->willReturn([ + 'status' => [ + 'state' => 'DONE' + ] + ])->shouldBeCalledTimes(1); + $job = $this->getJob($this->connection, $this->jobInfo); + $job->waitUntilComplete(); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Job did not complete within the allowed number of retries. + */ + public function testWaitsUntilCompleteThrowsExceptionAfterMaxRetries() + { + $this->jobInfo['status']['state'] = 'RUNNING'; + + $job = $this->getJob($this->connection, $this->jobInfo); + $job->waitUntilComplete(['maxRetries' => 1]); + } + public function testIsCompleteTrue() { $job = $this->getJob($this->connection, $this->jobInfo); diff --git a/tests/unit/BigQuery/QueryResultsTest.php b/tests/unit/BigQuery/QueryResultsTest.php index 8f655d3a71aa..bd8ebb55ba0e 100644 --- a/tests/unit/BigQuery/QueryResultsTest.php +++ b/tests/unit/BigQuery/QueryResultsTest.php @@ -57,23 +57,10 @@ public function getQueryResults($connection, array $data = []) $this->jobId, $this->projectId, $data, - [], new ValueMapper(false) ); } - /** - * @expectedException \Google\Cloud\Core\Exception\GoogleException - */ - public function testGetsRowsThrowsExceptionWhenQueryNotComplete() - { - $this->queryData['jobComplete'] = false; - unset($this->queryData['rows']); - $this->connection->getQueryResults()->shouldNotBeCalled(); - $queryResults = $this->getQueryResults($this->connection, $this->queryData); - $queryResults->rows()->next(); - } - public function testGetsRowsWithNoResults() { $this->connection->getQueryResults()->shouldNotBeCalled(); @@ -107,6 +94,16 @@ public function testGetsRowsWithToken() $this->assertEquals('Alton', $rows[1]['first_name']); } + public function testGetIterator() + { + $this->connection->getQueryResults()->shouldNotBeCalled(); + unset($this->queryData['rows']); + $queryResults = $this->getQueryResults($this->connection, $this->queryData); + $rows = iterator_to_array($queryResults); + + $this->assertEmpty($rows); + } + public function testIsCompleteTrue() { $queryResults = $this->getQueryResults($this->connection, $this->queryData);