diff --git a/README.md b/README.md index c4603c7d5..f0ffe18c3 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,51 @@ Actually we using Guzzle as HTTP client $influx->mark("tcp.test", ["mark" => "element"]); ``` +## Query InfluxDB + +You can query the time series database using the query method. + +```php +$influx->query("select * from mine"); +$influx->query("select * from mine", "s"); // with time_precision +``` + +You can query the database only if the adapter is queryable (implements `QueryableInterface`), +actually `GuzzleAdapter`. + +The adapter returns the json decoded body of the InfluxDB response, something like: + +``` +array(1) { + [0] => + class stdClass#1 (3) { + public $name => + string(8) "tcp.test" + public $columns => + array(3) { + [0] => + string(4) "time" + [1] => + string(15) "sequence_number" + [2] => + string(4) "mark" + } + public $points => + array(1) { + [0] => + array(3) { + [0] => + int(1410545635590) + [1] => + int(2390001) + [2] => + string(7) "element" + } + } + } +} +``` + ## Prepare lib dependencies Use your DiC or Service Locator in order to provide a configured client diff --git a/composer.json b/composer.json index 82c20d1f5..e6b7245c2 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ ], "autoload": { "psr-0": { - "InfluxDB": ["./src/", "./tests"] + "InfluxDB\\": ["./src/", "./tests"] } }, "suggests": { diff --git a/spec/InfluxDB/Adapter/GuzzleAdapterSpec.php b/spec/InfluxDB/Adapter/GuzzleAdapterSpec.php index 73f8f0ffa..3befd1b72 100644 --- a/spec/InfluxDB/Adapter/GuzzleAdapterSpec.php +++ b/spec/InfluxDB/Adapter/GuzzleAdapterSpec.php @@ -11,7 +11,9 @@ class GuzzleAdapterSpec extends ObjectBehavior { function let(Client $client, Options $options) { - $options->getTcpEndpoint()->willReturn("localhost"); + $options->getHttpSeriesEndpoint()->willReturn("localhost"); + $options->getUsername()->willReturn("one"); + $options->getPassword()->willReturn("two"); $this->beConstructedWith($client, $options); } @@ -22,8 +24,23 @@ class GuzzleAdapterSpec extends ObjectBehavior function it_should_send_data_via_post(Client $client, Options $options) { - $client->post("localhost", ['body' => json_encode(['pippo'])])->shouldBeCalledTimes(1); + $client->post("localhost", [ + 'auth' => ["one", "two"], + 'body' => json_encode(['pippo']) + ])->shouldBeCalledTimes(1); $this->send(["pippo"]); } + + function it_should_query_data(Client $client, Options $options) + { + $client->get("select * from tcp.test", [])->willReturn([]); + $this->query("select * from tcp.test")->shouldReturn([]); + } + + function it_should_query_data_with_time_precision(Client $client, Options $options) + { + $client->get("select * from tcp.test", ["time_precision" => "s"])->willReturn([]); + $this->query("select * from tcp.test", "s")->shouldReturn([]); + } } diff --git a/spec/InfluxDB/ClientSpec.php b/spec/InfluxDB/ClientSpec.php index ce1918e31..28a006d5d 100644 --- a/spec/InfluxDB/ClientSpec.php +++ b/spec/InfluxDB/ClientSpec.php @@ -4,6 +4,8 @@ namespace spec\InfluxDB; use PhpSpec\ObjectBehavior; use Prophecy\Argument; +use InfluxDB\Adapter\GuzzleAdapter; +use InfluxDB\Adapter\UdpAdapter; class ClientSpec extends ObjectBehavior { @@ -31,4 +33,35 @@ class ClientSpec extends ObjectBehavior "title" => "Autogrill" ]); } + + function it_should_query_on_querable_adapter(GuzzleAdapter $adapter) + { + $this->setAdapter($adapter); + $adapter->query("select * from tcp.test", false)->willReturn([]); + + $this->query("select * from tcp.test")->shouldReturn([]); + } + + function it_should_query_with_time_precision(GuzzleAdapter $adapter) + { + $this->setAdapter($adapter); + $adapter->query("select * from tcp.test", "s")->willReturn([]); + + $this->query("select * from tcp.test", "s")->shouldReturn([]); + } + + function it_should_query_but_skip_invalid_time_precision(GuzzleAdapter $adapter) + { + $this->setAdapter($adapter); + $adapter->query("select * from tcp.test", false)->willReturn([]); + + $this->query("select * from tcp.test", "r")->shouldReturn([]); + } + + function it_should_thrown_an_exception_on_unquerable_adapter(UdpAdapter $adapter) + { + $this->setAdapter($adapter); + + $this->shouldThrow("\\BadMethodCallException")->duringQuery("select * from tcp.test"); + } } diff --git a/spec/InfluxDB/OptionsSpec.php b/spec/InfluxDB/OptionsSpec.php index 44ea4fb4a..d926faf83 100644 --- a/spec/InfluxDB/OptionsSpec.php +++ b/spec/InfluxDB/OptionsSpec.php @@ -15,8 +15,8 @@ class OptionsSpec extends ObjectBehavior function it_should_create_a_valid_tcp_endpoint() { $this->setDatabase("mine"); - $this->getTcpEndpoint() - ->shouldReturn("http://localhost:8086/db/mine/series?u=root&p=root"); + $this->getHttpSeriesEndpoint() + ->shouldReturn("http://localhost:8086/db/mine/series"); } function it_should_allows_option_override_for_tcp_endpoint() @@ -26,15 +26,15 @@ class OptionsSpec extends ObjectBehavior $this->setUsername("walter"); $this->setPassword("walter"); $this->setDatabase("me"); - $this->getTcpEndpoint() - ->shouldReturn("http://influx.1.prod.tld:19385/db/me/series?u=walter&p=walter"); + $this->getHttpSeriesEndpoint() + ->shouldReturn("http://influx.1.prod.tld:19385/db/me/series"); } function it_should_allows_https_for_tcp_endpoint() { $this->setProtocol("https"); $this->setDatabase("me"); - $this->getTcpEndpoint() - ->shouldReturn("https://localhost:8086/db/me/series?u=root&p=root"); + $this->getHttpSeriesEndpoint() + ->shouldReturn("https://localhost:8086/db/me/series"); } } diff --git a/src/InfluxDB/Adapter/GuzzleAdapter.php b/src/InfluxDB/Adapter/GuzzleAdapter.php index fc7bf7fb7..da27104d0 100644 --- a/src/InfluxDB/Adapter/GuzzleAdapter.php +++ b/src/InfluxDB/Adapter/GuzzleAdapter.php @@ -5,7 +5,7 @@ namespace InfluxDB\Adapter; use GuzzleHttp\Client; use InfluxDB\Options; -class GuzzleAdapter implements AdapterInterface +class GuzzleAdapter implements AdapterInterface, QueryableInterface { private $httpClient; private $options; @@ -20,10 +20,27 @@ class GuzzleAdapter implements AdapterInterface public function send($message) { $httpMessage = [ + "auth" => [$this->options->getUsername(), $this->options->getPassword()], "body" => json_encode($message) ]; - $endpoint = $this->options->getTcpEndpoint(); + $endpoint = $this->options->getHttpSeriesEndpoint(); - $this->httpClient->post($endpoint, $httpMessage); + return $this->httpClient->post($endpoint, $httpMessage); + } + + public function query($query, $timePrecision = false) + { + $options = [ + "auth" => [$this->options->getUsername(), $this->options->getPassword()], + 'query' => [ + "q" => $query, + ] + ]; + if ($timePrecision) { + $options["query"]["time_precision"] = $timePrecision; + } + + $endpoint = $this->options->getHttpSeriesEndpoint(); + return $this->httpClient->get($endpoint, $options)->json(); } } diff --git a/src/InfluxDB/Adapter/QueryableInterface.php b/src/InfluxDB/Adapter/QueryableInterface.php new file mode 100644 index 000000000..fa604b069 --- /dev/null +++ b/src/InfluxDB/Adapter/QueryableInterface.php @@ -0,0 +1,7 @@ +getAdapter()->send([$data]); } + + public function query($query, $timePrecision = false) + { + if (!($this->getAdapter() instanceOf QueryableInterface)) { + throw new \BadMethodCallException("You can query the database only if the adapter supports it!"); + } + + $timePrecision = $this->clearTimePrecision($timePrecision); + + return $this->getAdapter()->query($query, $timePrecision); + } + + private function clearTimePrecision($timePrecision) + { + switch ($timePrecision) { + case 's': + case 'u': + case 'ms': + break; + default: + $timePrecision = false; + } + + return $timePrecision; + } } diff --git a/src/InfluxDB/Options.php b/src/InfluxDB/Options.php index e084a1848..02ff5ef44 100644 --- a/src/InfluxDB/Options.php +++ b/src/InfluxDB/Options.php @@ -87,16 +87,14 @@ class Options return $this; } - public function getTcpEndpoint() + public function getHttpSeriesEndpoint() { return sprintf( - "%s://%s:%d/db/%s/series?u=%s&p=%s", + "%s://%s:%d/db/%s/series", $this->getProtocol(), $this->getHost(), $this->getPort(), - $this->getDatabase(), - $this->getUsername(), - $this->getPassword() + $this->getDatabase() ); } } diff --git a/tests/InfluxDB/ClientTest.php b/tests/InfluxDB/ClientTest.php index 05690b741..ec6581510 100644 --- a/tests/InfluxDB/ClientTest.php +++ b/tests/InfluxDB/ClientTest.php @@ -16,9 +16,6 @@ class ClientTest extends \PHPUnit_Framework_TestCase public function setUp() { $options = include __DIR__ . '/../bootstrap.php'; - $this->options = $options; - - $this->object = new Client(); $client = new Crodas( $options["tcp"]["host"], @@ -34,11 +31,8 @@ class ClientTest extends \PHPUnit_Framework_TestCase $client->createDatabase($options["tcp"]["database"]); $this->anotherClient = $client; - } - public function testGuzzleHttpApiWorksCorrectly() - { - $tcpOptions = $this->options["tcp"]; + $tcpOptions = $options["tcp"]; $options = new Options(); $options->setHost($tcpOptions["host"]); @@ -47,17 +41,56 @@ class ClientTest extends \PHPUnit_Framework_TestCase $options->setPassword($tcpOptions["password"]); $options->setDatabase($tcpOptions["database"]); + $this->options = $options; + $guzzleHttp = new GuzzleHttpClient(); $adapter = new InfluxHttpAdapter($guzzleHttp, $options); $influx = new Client(); $influx->setAdapter($adapter); + $this->object = $influx; + } - $influx->mark("tcp.test", ["mark" => "element"]); + public function testGuzzleHttpApiWorksCorrectly() + { + $this->object->mark("tcp.test", ["mark" => "element"]); - $cursor = $this->anotherClient->getDatabase($tcpOptions["database"]) + $cursor = $this->anotherClient->getDatabase($this->options->getDatabase()) ->query("select * from tcp.test"); $this->assertCount(1, $cursor); $this->assertEquals("element", $cursor[0]->mark); } + + public function testGuzzleHttpQueryApiWorksCorrectly() + { + $this->object->mark("tcp.test", ["mark" => "element"]); + + $body = $this->object->query("select * from tcp.test"); + + $this->assertCount(1, $body); + $this->assertEquals("tcp.test", $body[0]["name"]); + $this->assertEquals("element", $body[0]["points"][0][2]); + } + + public function testGuzzleHttpQueryApiWithMultipleData() + { + $this->object->mark("tcp.test", ["mark" => "element"]); + $this->object->mark("tcp.test", ["mark" => "element2"]); + $this->object->mark("tcp.test", ["mark" => "element3"]); + + $body = $this->object->query("select mark from tcp.test", "s"); + + $this->assertCount(3, $body[0]["points"]); + $this->assertEquals("tcp.test", $body[0]["name"]); + } + + public function testGuzzleHttpQueryApiWithTimePrecision() + { + $this->object->mark("tcp.test", ["mark" => "element"]); + + $body = $this->object->query("select mark from tcp.test", "s"); + + $this->assertCount(1, $body[0]["points"]); + $this->assertEquals("tcp.test", $body[0]["name"]); + } }