diff --git a/CHANGELOG.md b/CHANGELOG.md index d4570677..c52a9418 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 1.1.0 [unreleased] + +### Bugs +1. [#13](https://github.com/influxdata/influxdb-client-php/pull/13): Fixed throwing of InvalidArgumentException some option is empty +2. [#13](https://github.com/influxdata/influxdb-client-php/pull/13): FluxCsvParser: fixed throwing FluxQueryException with no reference, default value is 0 +3. [#13](https://github.com/influxdata/influxdb-client-php/pull/13): Fixed error when querying empty data, now returns null + ## 1.0.0 [2020-03-06] ### Features diff --git a/src/InfluxDB2/DefaultApi.php b/src/InfluxDB2/DefaultApi.php index 0f8f0cc6..57b3c9ce 100644 --- a/src/InfluxDB2/DefaultApi.php +++ b/src/InfluxDB2/DefaultApi.php @@ -89,8 +89,19 @@ public function post($payload, $uriPath, $queryParams, $limit = self::DEFAULT_TI function check($key, $value) { - if ($value == null) { - throw new InvalidArgumentException("The '${key}' should be defined as argument or default option: {$this->options}"); + if ((!isset($value) || trim($value) === '')) { + $options = implode(', ', array_map( + function ($v, $k) { + if(is_array($v)){ + return $k.'[]='.implode('&'.$k.'[]=', $v); + }else{ + return $k.'='.$v; + } + }, + $this->options, + array_keys($this->options) + )); + throw new InvalidArgumentException("The '${key}' should be defined as argument or default option: {$options}"); } } } diff --git a/src/InfluxDB2/FluxCsvParser.php b/src/InfluxDB2/FluxCsvParser.php index 1bcab8ea..f30444f4 100644 --- a/src/InfluxDB2/FluxCsvParser.php +++ b/src/InfluxDB2/FluxCsvParser.php @@ -84,7 +84,8 @@ public function each() if ($this->parsingStateError) { $error = $csv[1]; $referenceValue = $csv[2]; - throw new FluxQueryError($error, $referenceValue); + throw new FluxQueryError($error, + !isset($referenceValue) || trim($referenceValue) === '' ? 0 : $referenceValue); } $result = $this->parseLine($csv); diff --git a/src/InfluxDB2/QueryApi.php b/src/InfluxDB2/QueryApi.php index b44a5de0..d7391143 100644 --- a/src/InfluxDB2/QueryApi.php +++ b/src/InfluxDB2/QueryApi.php @@ -33,9 +33,16 @@ public function __construct(array $options) * @param null $dialect csv dialect * @return string */ - public function queryRaw(string $query, $org = null, $dialect = null): string + public function queryRaw(string $query, $org = null, $dialect = null): ?string { - return $this->postQuery($query, $org, $dialect ?: $this->DEFAULT_DIALECT)->getBody()->getContents(); + $result = $this->postQuery($query, $org, $dialect ?: $this->DEFAULT_DIALECT); + + if ($result == null) + { + return null; + } + + return $result->getBody()->getContents(); } /** @@ -46,8 +53,14 @@ public function queryRaw(string $query, $org = null, $dialect = null): string */ public function query($query, $org = null, $dialect = null) { - $response = $response = $this->postQuery($query, $org, $dialect ?: $this->DEFAULT_DIALECT)->getBody(); - $parser = new FluxCsvParser($response); + $response = $this->postQuery($query, $org, $dialect ?: $this->DEFAULT_DIALECT); + + if ($response == null) + { + return null; + } + + $parser = new FluxCsvParser($response->getBody()); foreach ($parser->parse() as $record); @@ -60,13 +73,19 @@ public function query($query, $org = null, $dialect = null) * @param $dialect * @return FluxCsvParser generator */ - public function queryStream($query, $org = null, $dialect = null): FluxCsvParser + public function queryStream($query, $org = null, $dialect = null): ?FluxCsvParser { - $response = $this->postQuery($query, $org, $dialect ?: $this->DEFAULT_DIALECT)->getBody(); - return new FluxCsvParser($response, true); + $response = $this->postQuery($query, $org, $dialect ?: $this->DEFAULT_DIALECT); + + if ($response == null) + { + return null; + } + + return new FluxCsvParser($response->getBody(), true); } - private function postQuery($query, $org, $dialect): ResponseInterface + private function postQuery($query, $org, $dialect): ?ResponseInterface { $orgParam = $org ?: $this->options["org"]; $this->check("org", $orgParam); @@ -74,12 +93,17 @@ private function postQuery($query, $org, $dialect): ResponseInterface $payload = $this->generatePayload($query, $dialect); $queryParams = ["org" => $orgParam]; + if ($payload == null) + { + return null; + } + return $this->post($payload->__toString(), "/api/v2/query", $queryParams); } private function generatePayload($query, $dialect) { - if ($query == null) { + if ((!isset($query) || trim($query) === '')) { return null; } diff --git a/tests/DefaultApiTest.php b/tests/DefaultApiTest.php index 0e326b35..37c7958f 100644 --- a/tests/DefaultApiTest.php +++ b/tests/DefaultApiTest.php @@ -3,6 +3,8 @@ namespace InfluxDB2Test; use GuzzleHttp\Psr7\Response; +use InvalidArgumentException; +use InfluxDB2\ApiException; require_once('BasicTest.php'); @@ -18,4 +20,34 @@ public function testUserAgent() $this->assertStringStartsWith('influxdb-client-php/', strval($request->getHeader("User-Agent")[0])); } + + public function testContentType() + { + $this->mockHandler->append(new Response(204)); + $this->writeApi->write('h2o,location=west value=33i 15'); + + $request = $this->mockHandler->getLastRequest(); + + $this->assertNotEmpty($request->getHeader("Content-Type")); + } + + public function testApiException() + { + $this->mockHandler->append(new Response(400)); + + $this->expectException(ApiException::class); + + $this->writeApi->write('h2o,location=west value=33i 15'); + } + + public function testInvalidArgument() + { + $this->mockHandler->append(new Response(204)); + + $this->writeApi->options["org"] = ''; + + $this->expectException(InvalidArgumentException::class); + + $this->writeApi->write('h2o,location=west value=33i 15'); + } } diff --git a/tests/FluxTableTest.php b/tests/FluxTableTest.php index da94d798..11fe8f50 100644 --- a/tests/FluxTableTest.php +++ b/tests/FluxTableTest.php @@ -3,6 +3,8 @@ namespace InfluxDB2Test; use InfluxDB2\FluxCsvParser; +use InfluxDB2\FluxCsvParserException; +use InfluxDB2\FluxQueryError; use InfluxDB2\FluxRecord; use PHPUnit\Framework\TestCase; @@ -88,7 +90,6 @@ public function testMappingBoolean() */ public function testMappingUnsignedLong() { - $data = '#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,' . "dateTime:RFC3339,long,string,string,string,unsignedLong\n" . "#group,false,false,false,false,false,false,false,false,false,true\n" . @@ -108,6 +109,177 @@ public function testMappingUnsignedLong() $this->assertNull($records[1]->values['value']); } + public function testMappingDouble() + { + $data = '#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,' . + "dateTime:RFC3339,long,string,string,string,double\n" . + "#group,false,false,false,false,false,false,false,false,false,true\n" . + "#default,_result,,,,,,,,,\n" . + ",result,table,_start,_stop,_time,_value,_field,_measurement,host,value\n" . + ",,0,1970-01-01T00:00:10Z,1970-01-01T00:00:20Z,1970-01-01T00:00:10Z,10,free,mem,A,12.25\n" . + ",,0,1970-01-01T00:00:10Z,1970-01-01T00:00:20Z,1970-01-01T00:00:10Z,10,free,mem,A,\n"; + + $fluxCsvParser = new FluxCsvParser($data); + $tables = $fluxCsvParser->parse()->tables; + + $records = $tables[0]->records; + + $this->assertEquals(12.25, $records[0]->values['value']); + $this->assertNull($records[1]->values['value']); + } + + public function testMappingBase64Binary() + { + $binaryData = 'test value'; + $encodedData = base64_encode($binaryData); + + $data = '#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,' . + "dateTime:RFC3339,long,string,string,string,base64Binary\n" . + "#group,false,false,false,false,false,false,false,false,false,true\n" . + "#default,_result,,,,,,,,,\n" . + ",result,table,_start,_stop,_time,_value,_field,_measurement,host,value\n" . + ',,0,1970-01-01T00:00:10Z,1970-01-01T00:00:20Z,1970-01-01T00:00:10Z,10,free,mem,A,' . $encodedData . "\n" . + ",,0,1970-01-01T00:00:10Z,1970-01-01T00:00:20Z,1970-01-01T00:00:10Z,10,free,mem,A,\n"; + + $fluxCsvParser = new FluxCsvParser($data); + $tables = $fluxCsvParser->parse()->tables; + + $records = $tables[0]->records; + + $this->assertEquals($binaryData, $records[0]->values['value']); + $this->assertNull($records[1]->values['value']); + } + + public function testMappingDuration() + { + $data = '#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339' . + ",dateTime:RFC3339,long,string,string,string,duration\n" . + "#group,false,false,false,false,false,false,false,false,false,true\n" . + "#default,_result,,,,,,,,,\n" . + ",result,table,_start,_stop,_time,_value,_field,_measurement,host,value\n" . + ",,0,1970-01-01T00:00:10Z,1970-01-01T00:00:20Z,1970-01-01T00:00:10Z,10,free,mem,A,125\n" . + ",,0,1970-01-01T00:00:10Z,1970-01-01T00:00:20Z,1970-01-01T00:00:10Z,10,free,mem,A,\n"; + + $fluxCsvParser = new FluxCsvParser($data); + $tables = $fluxCsvParser->parse()->tables; + + $records = $tables[0]->records; + + $this->assertEquals(125, $records[0]->values['value']); + $this->assertNull($records[1]->values['value']); + } + + public function testGroupKey() + { + $data = '#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,' . + "dateTime:RFC3339,long,string,string,string,duration\n" . + "#group,false,false,false,false,true,false,false,false,false,true\n" . + "#default,_result,,,,,,,,,\n" . + ",result,table,_start,_stop,_time,_value,_field,_measurement,host,value\n" . + ",,0,1970-01-01T00:00:10Z,1970-01-01T00:00:20Z,1970-01-01T00:00:10Z,10,free,mem,A,125\n" . + ",,0,1970-01-01T00:00:10Z,1970-01-01T00:00:20Z,1970-01-01T00:00:10Z,10,free,mem,A,\n"; + + $fluxCsvParser = new FluxCsvParser($data); + $tables = $fluxCsvParser->parse()->tables; + + $this->assertEquals(10, count($tables[0]->columns)); + $this->assertEquals(2, count($tables[0]->getGroupKey())); + } + + public function testUnknownTypeAsString() + { + $data = '#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,' . + "dateTime:RFC3339,long,string,string,string,unknown\n" . + "#group,false,false,false,false,false,false,false,false,false,true\n" . + "#default,_result,,,,,,,,,\n" . + ",result,table,_start,_stop,_time,_value,_field,_measurement,host,value\n" . + ",,0,1970-01-01T00:00:10Z,1970-01-01T00:00:20Z,1970-01-01T00:00:10Z,10,free,mem,A,12.25\n" . + ",,0,1970-01-01T00:00:10Z,1970-01-01T00:00:20Z,1970-01-01T00:00:10Z,10,free,mem,A,\n"; + + $fluxCsvParser = new FluxCsvParser($data); + $tables = $fluxCsvParser->parse()->tables; + + $records = $tables[0]->records; + + $this->assertEquals('12.25', $records[0]->values['value']); + $this->assertNull($records[1]->values['value']); + } + + public function testError() + { + $data = "#datatype,string,string\n" . + "#group,true,true\n" . + "#default,,\n" . + ",error,reference\n" . + ',failed to create physical plan: invalid time bounds from procedure from: bounds contain zero time,897'; + + $fluxCsvParser = new FluxCsvParser($data); + + try { + $fluxCsvParser->parse(); + $this->fail(); + } + catch (FluxQueryError $e) + { + $this->assertEquals('failed to create physical plan: invalid time bounds from procedure from: bounds contain zero time', + $e->getMessage()); + $this->assertEquals(897, $e->getCode()); + } + catch (\Exception $e) + { + $this->fail(); + } + } + + public function testErrorWithoutReference() + { + $data = "#datatype,string,string\n" . + "#group,true,true\n" . + "#default,,\n" . + ",error,reference\n" . + ',failed to create physical plan: invalid time bounds from procedure from: bounds contain zero time,'; + + $fluxCsvParser = new FluxCsvParser($data); + + try { + $fluxCsvParser->parse(); + $this->fail(); + } + catch (FluxQueryError $e) + { + $this->assertEquals('failed to create physical plan: invalid time bounds from procedure from: bounds contain zero time', + $e->getMessage()); + $this->assertEquals(0, $e->getCode()); + } + catch (\Exception $e) + { + $this->fail(); + } + } + + public function testWithoutTableReference() + { + $data = ",result,table,_start,_stop,_time,_value,_field,_measurement,host,value\n" . + ",,0,1970-01-01T00:00:10Z,1970-01-01T00:00:20Z,1970-01-01T00:00:10Z,10,free,mem,A,12.25\n" . + ",,0,1970-01-01T00:00:10Z,1970-01-01T00:00:20Z,1970-01-01T00:00:10Z,10,free,mem,A,\n"; + + $fluxCsvParser = new FluxCsvParser($data); + + try { + $fluxCsvParser->parse(); + $this->fail(); + } + catch (FluxCsvParserException $e) + { + $this->assertEquals('Unable to parse CSV response. FluxTable definition was not found.', + $e->getMessage()); + } + catch (\Exception $e) + { + $this->fail(); + } + } + private function assertColumns(array $columnHeaders, array $values) { $i = 0; @@ -159,7 +331,6 @@ private function assertMultipleRecords(array $tables) private function assertRecord(FluxRecord $fluxRecord, array $values, $size = 0, $value = null) { - foreach ($values as $key => $val) { $this->assertEquals($values[$key], $fluxRecord->values[$key]); } diff --git a/tests/QueryApiStreamTest.php b/tests/QueryApiStreamTest.php index 2177f889..13f70877 100644 --- a/tests/QueryApiStreamTest.php +++ b/tests/QueryApiStreamTest.php @@ -95,6 +95,13 @@ public function testQueryStreamBreak() $this->assertTrue($parser->closed); } + public function testQueryEmptyData() + { + $result = $this->queryApi->queryStream(null); + + $this->assertNull($result); + } + private function write($values, $measurement) { for ($ii = 0; $ii < $values; $ii++) { diff --git a/tests/QueryApiTest.php b/tests/QueryApiTest.php index e2a44006..3cfb3fbb 100644 --- a/tests/QueryApiTest.php +++ b/tests/QueryApiTest.php @@ -32,7 +32,7 @@ public function testQueryRaw() $this->assertEquals(QueryApiTest::SUCCESS_DATA, $result); } - public function testQuery() + public function testQuery() { $this->mockHandler->append(new Response(204, [], QueryApiTest::SUCCESS_DATA)); @@ -50,4 +50,18 @@ public function testQuery() $this->assertEquals(10, $record->getValue()); $this->assertEquals('free', $record->getField()); } + + public function testQueryRawEmptyData() + { + $result = $this->queryApi->queryRaw(''); + + $this->assertNull($result); + } + + public function testQueryEmptyData() + { + $result = $this->queryApi->query(null); + + $this->assertNull($result); + } }