Merge commit '4430658932762ead3661231492b8074f144ea4b6' into influxdb-php

Conflicts:
	lib/influxdb-php/.gitignore
	lib/influxdb-php/README.md
This commit is contained in:
Dave Bell
2016-03-29 23:06:15 +01:00
29 changed files with 2085 additions and 348 deletions

View File

@ -1,14 +1,18 @@
language: php
sudo: false
php:
- 5.5
- 5.6
- 7.0
install:
- composer selfupdate
- composer install
addons:
code_climate:
repo_token: 5371d86d298d66eb9007cc8de462d7063e58f6dd85e430928834736edee479a9
repo_token: 303a289e61e8e11d8bcae115860c4fffc6e1e7fe2d504d20a773e69bd7641284
after_script:
- vendor/bin/test-reporter

View File

@ -1,12 +1,12 @@
# influxdb-php
## InfluxDB client library for PHP
[![Build Status](https://travis-ci.org/influxdb/influxdb-php.svg?branch=master)](https://travis-ci.org/influxdb/influxdb-php)
[![Code Climate](https://codeclimate.com/github/influxdb/influxdb-php/badges/gpa.svg)](https://codeclimate.com/github/influxdb/influxdb-php)
[![Test Coverage](https://codeclimate.com/github/influxdb/influxdb-php/badges/coverage.svg)](https://codeclimate.com/github/influxdb/influxdb-php/coverage)
[![Build Status](https://travis-ci.org/influxdata/influxdb-php.svg?branch=master)](https://travis-ci.org/influxdata/influxdb-php)
[![Code Climate](https://codeclimate.com/github/influxdata/influxdb-php/badges/gpa.svg)](https://codeclimate.com/github/influxdata/influxdb-php)
[![Test Coverage](https://codeclimate.com/github/influxdata/influxdb-php/badges/coverage.svg)](https://codeclimate.com/github/influxdata/influxdb-php/coverage)
### Overview
A easy to use library for using InfluxDB with PHP.
A easy to use library for using InfluxDB with PHP. Maintained by [@thecodeassassin](https://github.com/thecodeassassin), [@gianarb](https://github.com/gianarb).
The influxdb-php library was created to have php port of the python influxdb client.
This way there will be a common abstraction library between different programming languages.
@ -15,7 +15,9 @@ This way there will be a common abstraction library between different programmin
Installation can be done with composer:
composer require influxdb/influxdb-php:dev-master
``` bash
$ composer require influxdb/influxdb-php
```
### NOTE for PHP 5.3 and PHP 5.4 users
@ -27,10 +29,7 @@ The 0.1.x branch will work on PHP 5.3 and PHP 5.4 but doesn't contain all the fe
Initialize a new client object:
```php
$client = new InfluxDB\Client($host, $port);
```
This will create a new client object which you can use to read and write points to InfluxDB.
@ -38,13 +37,11 @@ This will create a new client object which you can use to read and write points
It's also possible to create a client from a DSN (Data Source Name):
```php
// directly get the database object
$database = InfluxDB\Client::fromDSN(sprintf('influxdb://user:pass@%s:%s/%s', $host, $port, $dbname));
// directly get the database object
$database = InfluxDB\Client::fromDSN(sprintf('influxdb://user:pass@%s:%s/%s', $host, $port, $dbname));
// get the client to retrieve other databases
$client = $database->getClient();
// get the client to retrieve other databases
$client = $database->getClient();
```
### Reading
@ -52,37 +49,45 @@ It's also possible to create a client from a DSN (Data Source Name):
To fetch records from InfluxDB you can do a query directly on a database:
```php
// fetch the database
$database = $client->selectDB('influx_test_db');
// fetch the database
$database = $client->selectDB('influx_test_db');
// executing a query will yield a resultset object
$result = $database->query('select * from test_metric LIMIT 5');
// get the points from the resultset yields an array
$points = $result->getPoints();
// executing a query will yield a resultset object
$result = $database->query('select * from test_metric LIMIT 5');
// get the points from the resultset yields an array
$points = $result->getPoints();
```
It's also possible to use the QueryBuilder object. This is a class that simplifies the process of building queries.
```php
// retrieve points with the query builder
$result = $database->getQueryBuilder()
// retrieve points with the query builder
$result = $database->getQueryBuilder()
->select('cpucount')
->from('test_metric')
->limit(2)
->getResultSet()
->getPoints();
// get the query from the QueryBuilder
$query = $database->getQueryBuilder()
// get the query from the QueryBuilder
$query = $database->getQueryBuilder()
->select('cpucount')
->from('test_metric')
->where(["region = 'us-west'"])
->getQuery();
```
Make sure that you enter single quotes when doing a where query on strings; otherwise InfluxDB will return an empty result.
You can get the last executed query from the client:
```php
// use the getLastQuery() method
$lastQuery = $client->getLastQuery();
// or access the static variable directly:
$lastQuery = Client::lastQuery;
```
### Writing data
@ -90,9 +95,8 @@ It's also possible to use the QueryBuilder object. This is a class that simplifi
Writing data is done by providing an array of points to the writePoints method on a database:
```php
// create an array of points
$points = array(
// create an array of points
$points = array(
new Point(
'test_metric', // name of the measurement
0.64, // the measurement value
@ -107,11 +111,10 @@ Writing data is done by providing an array of points to the writePoints method o
['cpucount' => 10], // optional additional fields
1435255849 // Time precision has to be set to seconds!
)
);
// we are writing unix timestamps, which have a second precision
$result = $database->writePoints($points, Database::PRECISION_SECONDS);
);
// we are writing unix timestamps, which have a second precision
$result = $database->writePoints($points, Database::PRECISION_SECONDS);
```
It's possible to add multiple [fields](https://influxdb.com/docs/v0.9/concepts/key_concepts.html) when writing
@ -123,7 +126,7 @@ InfluxDB takes the current time as the default timestamp.
You can also write multiple fields to a measurement without specifying a value:
```php
$points = [
$points = [
new Point(
'instance', // the name of the measurement
null, // measurement value
@ -138,7 +141,7 @@ You can also write multiple fields to a measurement without specifying a value:
['cpucount' => 10, 'free' => 2], // measurement fields
exec('date +%s%N') // timestamp in nanoseconds on Linux ONLY
)
];
];
```
@ -156,11 +159,10 @@ First, set your InfluxDB host to support incoming UDP sockets:
Then, configure the UDP driver in the client:
```php
// set the UDP driver in the client
$client->setDriver(new \InfluxDB\Driver\UDP($client->getHost(), 4444));
// set the UDP driver in the client
$client->setDriver(new \InfluxDB\Driver\UDP($client->getHost(), 4444));
$points = [
$points = [
new Point(
'test_metric',
0.84,
@ -168,21 +170,20 @@ Then, configure the UDP driver in the client:
['cpucount' => 10],
exec('date +%s%N') // this will produce a nanosecond timestamp on Linux ONLY
)
];
];
// now just write your points like you normally would
$result = $database->writePoints($points);
// now just write your points like you normally would
$result = $database->writePoints($points);
```
Or simply use a DSN (Data Source Name) to send metrics using UDP:
```php
// get a database object using a DSN (Data Source Name)
$database = \InfluxDB\Client::fromDSN('udp+influxdb://username:pass@localhost:4444/test123');
// get a database object using a DSN (Data Source Name)
$database = \InfluxDB\Client::fromDSN('udp+influxdb://username:pass@localhost:4444/test123');
// write your points
$result = $database->writePoints($points);
// write your points
$result = $database->writePoints($points);
```
*Note:* It is import to note that precision will be *ignored* when you use UDP. You should always use nanosecond
@ -194,21 +195,21 @@ It's important to provide the correct precision when adding a timestamp to a Poi
if you specify a timestamp in seconds and the default (nanosecond) precision is set; the entered timestamp will be invalid.
```php
// Points will require a nanosecond precision (this is default as per influxdb standard)
$newPoints = $database->writePoints($points);
// Points will require a nanosecond precision (this is default as per influxdb standard)
$newPoints = $database->writePoints($points);
// Points will require second precision
$newPoints = $database->writePoints($points, Database::PRECISION_SECONDS);
// Points will require second precision
$newPoints = $database->writePoints($points, Database::PRECISION_SECONDS);
// Points will require microsecond precision
$newPoints = $database->writePoints($points, Database::PRECISION_MICROSECONDS);
// Points will require microsecond precision
$newPoints = $database->writePoints($points, Database::PRECISION_MICROSECONDS);
```
Please note that `exec('date + %s%N')` does NOT work under MacOS; you can use PHP's `microtime` to get a timestamp with microsecond precision, like such:
```php
list( $usec, $sec ) = explode(' ', microtime() );
$timestamp = sprintf( '%d%06d', $sec, $usec*1000000 );
list($usec, $sec) = explode(' ', microtime());
$timestamp = sprintf('%d%06d', $sec, $usec*1000000);
```
### Creating databases
@ -219,41 +220,39 @@ so the data will be flushed with the memory.
This library makes it easy to provide a retention policy when creating a database:
```php
// create the client
$client = new \InfluxDB\Client($host, $port, '', '');
// create the client
$client = new \InfluxDB\Client($host, $port, '', '');
// select the database
$database = $client->selectDB('influx_test_db');
// select the database
$database = $client->selectDB('influx_test_db');
// create the database with a retention policy
$result = $database->create(new RetentionPolicy('test', '5d', 1, true));
// create the database with a retention policy
$result = $database->create(new RetentionPolicy('test', '5d', 1, true));
// check if a database exists then create it if it doesn't
$database = $client->selectDB('test_db');
// check if a database exists then create it if it doesn't
$database = $client->selectDB('test_db');
if (!$database->exists()) {
if (!$database->exists()) {
$database->create(new RetentionPolicy('test', '1d', 2, true));
}
}
```
You can also alter retention policies:
```php
$database->alterRetentionPolicy(new RetentionPolicy('test', '2d', 5, true));
$database->alterRetentionPolicy(new RetentionPolicy('test', '2d', 5, true));
```
and list them:
```php
$result = $database->listRetentionPolicies();
$result = $database->listRetentionPolicies();
```
You can add more retention policies to a database:
```php
$result = $database->createRetentionPolicy(new RetentionPolicy('test2', '30d', 1, true));
$result = $database->createRetentionPolicy(new RetentionPolicy('test2', '30d', 1, true));
```
### Client functions
@ -261,12 +260,11 @@ You can add more retention policies to a database:
Some functions are too general for a database. So these are available in the client:
```php
// list users
$result = $client->listUsers();
// list users
$result = $client->listUsers();
// list databases
$result = $client->listDatabases();
// list databases
$result = $client->listDatabases();
```
### Admin functionality
@ -274,24 +272,24 @@ Some functions are too general for a database. So these are available in the cli
You can use the client's $client->admin functionality to administer InfluxDB via the API.
```php
// add a new user without privileges
$client->admin->createUser('testuser123', 'testpassword');
// add a new user without privileges
$client->admin->createUser('testuser123', 'testpassword');
// add a new user with ALL cluster-wide privileges
$client->admin->createUser('admin_user', 'password', \InfluxDB\Client\Admin::PRIVILEGE_ALL);
// add a new user with ALL cluster-wide privileges
$client->admin->createUser('admin_user', 'password', \InfluxDB\Client\Admin::PRIVILEGE_ALL);
// drop user testuser123
$client->admin->dropUser('testuser123');
// drop user testuser123
$client->admin->dropUser('testuser123');
```
List all the users:
```php
// show a list of all users
$results = $client->admin->showUsers();
// show a list of all users
$results = $client->admin->showUsers();
// show users returns a ResultSet object
$users = $results->getPoints();
// show users returns a ResultSet object
$users = $results->getPoints();
```
#### Granting and revoking privileges
@ -300,23 +298,21 @@ Granting permissions can be done on both the database level and cluster-wide.
To grant a user specific privileges on a database, provide a database object or a database name.
```php
// grant permissions using a database object
$database = $client->selectDB('test_db');
$client->admin->grant(\InfluxDB\Client\Admin::PRIVILEGE_READ, 'testuser123', $database);
// grant permissions using a database object
$database = $client->selectDB('test_db');
$client->admin->grant(\InfluxDB\Client\Admin::PRIVILEGE_READ, 'testuser123', $database);
// give user testuser123 read privileges on database test_db
$client->admin->grant(\InfluxDB\Client\Admin::PRIVILEGE_READ, 'testuser123', 'test_db');
// give user testuser123 read privileges on database test_db
$client->admin->grant(\InfluxDB\Client\Admin::PRIVILEGE_READ, 'testuser123', 'test_db');
// revoke user testuser123's read privileges on database test_db
$client->admin->revoke(\InfluxDB\Client\Admin::PRIVILEGE_READ, 'testuser123', 'test_db');
// revoke user testuser123's read privileges on database test_db
$client->admin->revoke(\InfluxDB\Client\Admin::PRIVILEGE_READ, 'testuser123', 'test_db');
// grant a user cluster-wide privileges
$client->admin->grant(\InfluxDB\Client\Admin::PRIVILEGE_READ, 'testuser123');
// Revoke an admin's cluster-wide privileges
$client->admin->revoke(\InfluxDB\Client\Admin::PRIVILEGE_ALL, 'admin_user');
// grant a user cluster-wide privileges
$client->admin->grant(\InfluxDB\Client\Admin::PRIVILEGE_READ, 'testuser123');
// Revoke an admin's cluster-wide privileges
$client->admin->revoke(\InfluxDB\Client\Admin::PRIVILEGE_ALL, 'admin_user');
```
## Todo
@ -329,6 +325,37 @@ To grant a user specific privileges on a database, provide a database object or
## Changelog
###1.3.0
* Added quoting of dbname in queries
* Added orderBy to query builder
* Fixed wrong orderby tests
* Travis container-infra and php 7
###1.2.2
* Fixed issue with listUsers() method
* Added more unit tests
* Added getColumns method to \InfluxDB\ResultSet
###1.2.0
* Added support for 32 bit systems
* Added setters/getters for Point fields
###1.1.3
* Added support for symfony3
###1.1.2
* Fixed issue with authentication when writing data
###1.1.1
* Added support for 0.9.4
* Added if not exists support to database->create()
* Added getLastQuery method
###1.1.0
* Added support for 0.9.3 rc2
* Changed the way we handle the datatypes of values
* Changed list retention policies to reflect the changes in 0.9.3
####1.0.1
* Added support for authentication in the guzzle driver
* Added admin functionality

43
lib/influxdb-php/Vagrantfile vendored Normal file
View File

@ -0,0 +1,43 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
#system("
# if [ #{ARGV[0]} = 'up' ]; then
# ./requirements.sh
# fi
#")
Vagrant.configure(2) do |config|
# The most common configuration options are documented and commented below.
# For a complete reference, please see the online documentation at
# https://docs.vagrantup.com.
# Every Vagrant development environment requires a box. You can search for
# boxes at https://atlas.hashicorp.com/search.
config.vm.box = "ubuntu/trusty64"
config.vm.define :node1 do |node1|
#
# Networking
#
node1.vm.network 'private_network', ip: '192.168.33.10'
#
# VM Setup
#
# Set the hostname to something useful
node1.vm.hostname = 'influxdb-node1'
node1.vm.define :influxdb_node1, {}
node1.vm.provision 'ansible' do |ansible|
ansible.playbook = 'ansible/main.yml'
ansible.tags = ENV['ANSIBLE_TAGS'] unless ENV['ANSIBLE_TAGS'].to_s.empty?
ansible.sudo = true
end
end
end

View File

@ -0,0 +1,10 @@
---
- hosts: all
sudo: yes
vars:
influxdb_udp_enabled: "true"
influxdb_udp_bind_address: "0.0.0.0:8090"
roles:
- { role: mtchavez.influxdb }

View File

@ -0,0 +1 @@
mtchavez.influxdb

View File

@ -19,14 +19,19 @@
{
"name": "Daniel Martinez",
"email": "danimartcas@hotmail.com"
},
{
"name": "Gianluca Arbezzano",
"email": "gianarb92@gmail.com"
}
],
"require": {
"php": ">=5.5",
"php": "^5.5 | ^7.0",
"guzzlehttp/guzzle": "6.*",
"symfony/event-dispatcher": "2.*"
"symfony/event-dispatcher": "2.*|3.*"
},
"require-dev": {
"phpunit/phpunit": "^4.0",
"codeclimate/php-test-reporter": "0.*",
"symfony/config": "~2.8",
"symfony/console": "~2.8",

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,14 @@
#!/bin/bash
#!/bin/bash
GLEXEC=$(which ansible-galaxy)
if [ $? = 1 ]; then
echo
echo "Ansible-galaxy not found, please make sure you have ansible 1.9+ installed."
echo
exit 1
fi
$GLEXEC install -f -r ansible/requirements.txt

View File

@ -88,6 +88,14 @@ class Client
*/
protected $driver;
/**
* Stores the last query that ran
*
* @var null
*/
public static $lastQuery = null;
/**
* @param string $host
* @param int $port
@ -103,7 +111,7 @@ class Client
$username = '',
$password = '',
$ssl = false,
$verifySSL = true,
$verifySSL = false,
$timeout = 0
) {
$this->host = (string) $host;
@ -183,6 +191,9 @@ class Client
$driver->setParameters($parameters);
try {
// store the last query sent
static::$lastQuery = $query;
// perform the query and return the resultset
return $driver->query();
@ -191,6 +202,33 @@ class Client
}
}
/**
* Write data
*
* @param array $parameters
* @param string $payload
*
* @return bool
*/
public function write(array $parameters, $payload)
{
// retrive the driver
$driver = $this->getDriver();
// add authentication to the driver if needed
if (!empty($this->username) && !empty($this->password)) {
$parameters += ['auth' => [$this->username, $this->password]];
}
// set the given parameters
$driver->setParameters($parameters);
// send the points to influxDB
$driver->write(implode("\n", $payload));
return $driver->isSuccess();
}
/**
* List all the databases
*/
@ -209,9 +247,9 @@ class Client
*/
public function listUsers()
{
$result = $this->query(null, 'SHOW USERS')->getPoints();
$result = $this->query(null, 'SHOW USERS')->getColumns();
return $this->pointsToArray($result);
return (array) $result;
}
/**
@ -306,6 +344,16 @@ class Client
return $this->host;
}
/**
* Returns the last executed query
*
* @return null|string
*/
public function getLastQuery()
{
return static::$lastQuery;
}
/**
* @param Point[] $points
* @return array

View File

@ -77,14 +77,21 @@ class Database
* Create this database
*
* @param RetentionPolicy $retentionPolicy
* @param bool $createIfNotExists Only create the database if it does not yet exist
*
* @return ResultSet
* @throws DatabaseException
* @throws Exception
*/
public function create(RetentionPolicy $retentionPolicy = null)
public function create(RetentionPolicy $retentionPolicy = null, $createIfNotExists = true)
{
try {
$this->query(sprintf('CREATE DATABASE %s', $this->name));
$query = sprintf(
'CREATE DATABASE %s"%s"',
($createIfNotExists ? 'IF NOT EXISTS ' : ''),
$this->name
);
$this->query($query);
if ($retentionPolicy) {
$this->createRetentionPolicy($retentionPolicy);
@ -125,25 +132,13 @@ class Database
);
try {
$driver = $this->client->getDriver();
$parameters = [
'url' => sprintf('write?db=%s&precision=%s', $this->name, $precision),
'database' => $this->name,
'method' => 'post'
];
// add authentication to the driver if needed
if (!empty($this->username) && !empty($this->password)) {
$parameters += ['auth' => [$this->username, $this->password]];
}
$driver->setParameters($parameters);
// send the points to influxDB
$driver->write(implode(PHP_EOL, $payload));
return $driver->isSuccess();
return $this->client->write($parameters, $payload);
} catch (\Exception $e) {
throw new Exception($e->getMessage(), $e->getCode());
@ -174,7 +169,7 @@ class Database
*/
public function listRetentionPolicies()
{
return $this->query(sprintf('SHOW RETENTION POLICIES %s', $this->name))->getPoints();
return $this->query(sprintf('SHOW RETENTION POLICIES ON "%s"', $this->name))->getPoints();
}
/**
@ -182,7 +177,7 @@ class Database
*/
public function drop()
{
$this->query(sprintf('DROP DATABASE %s', $this->name));
$this->query(sprintf('DROP DATABASE "%s"', $this->name));
}
/**
@ -210,12 +205,8 @@ class Database
*/
protected function getRetentionPolicyQuery($method, RetentionPolicy $retentionPolicy)
{
if (!in_array($method, ['CREATE', 'ALTER'])) {
throw new \InvalidArgumentException(sprintf('%s is not a valid method'));
}
$query = sprintf(
'%s RETENTION POLICY %s ON %s DURATION %s REPLICATION %s',
'%s RETENTION POLICY "%s" ON "%s" DURATION %s REPLICATION %s',
$method,
$retentionPolicy->name,
$this->name,

View File

@ -30,6 +30,11 @@ interface DriverInterface
*/
public function setParameters(array $parameters);
/**
* @return array
*/
public function getParameters();
/**
* Send the data
*

View File

@ -68,6 +68,14 @@ class Guzzle implements DriverInterface, QueryDriverInterface
$this->parameters = $parameters;
}
/**
* @return array
*/
public function getParameters()
{
return $this->parameters;
}
/**
* Send the data
*
@ -87,17 +95,10 @@ class Guzzle implements DriverInterface, QueryDriverInterface
*/
public function query()
{
$response = $this->httpClient->get($this->parameters['url'], $this->getRequestParameters());
$raw = (string) $response->getBody();
$responseJson = json_encode($raw);
if (isset($responseJson->error)) {
throw new Exception($responseJson->error);
}
return new ResultSet($raw);
}
@ -109,7 +110,14 @@ class Guzzle implements DriverInterface, QueryDriverInterface
*/
public function isSuccess()
{
return in_array($this->response->getStatusCode(), ['200', '204']);
$statuscode = $this->response->getStatusCode();
if(!in_array($statuscode, ['200', '204']))
{
throw new Exception('HTTP Code ' . $statuscode . ' ' . $this->response->getBody());
}
return true;
}
/**

View File

@ -58,6 +58,14 @@ class UDP implements DriverInterface
$this->parameters = $parameters;
}
/**
* @return array
*/
public function getParameters()
{
return $this->parameters;
}
/**
* Send the data
*

View File

@ -14,22 +14,22 @@ class Point
/**
* @var string
*/
private $measurement;
protected $measurement;
/**
* @var array
*/
private $tags = [];
protected $tags = [];
/**
* @var array
*/
private $fields = [];
protected $fields = [];
/**
* @var string
*/
private $timestamp;
protected $timestamp;
/**
* The timestamp is optional. If you do not specify a timestamp the servers
@ -55,12 +55,14 @@ class Point
$this->measurement = (string) $measurement;
$this->tags = $tags;
$this->fields = $additionalFields;
$fields = $additionalFields;
if ($value) {
$this->fields['value'] = $value;
if ($value !== null) {
$fields['value'] = $value;
}
$this->setFields($fields);
if ($timestamp && !$this->isValidTimeStamp($timestamp)) {
throw new DatabaseException(sprintf('%s is not a valid timestamp', $timestamp));
}
@ -80,10 +82,10 @@ class Point
$string = $this->measurement;
if (count($this->tags) > 0) {
$string .= ',' . $this->arrayToString($this->tags);
$string .= ',' . $this->arrayToString($this->escapeCharacters($this->tags));
}
$string .= ' ' . $this->arrayToString($this->fields);
$string .= ' ' . $this->arrayToString($this->escapeCharacters($this->fields));
if ($this->timestamp) {
$string .= ' '.$this->timestamp;
@ -92,6 +94,116 @@ class Point
return $string;
}
/**
* @return string
*/
public function getMeasurement()
{
return $this->measurement;
}
/**
* @param string $measurement
*/
public function setMeasurement($measurement)
{
$this->measurement = $measurement;
}
/**
* @return array
*/
public function getTags()
{
return $this->tags;
}
/**
* @param array $tags
*/
public function setTags($tags)
{
$this->tags = $tags;
}
/**
* @return array
*/
public function getFields()
{
return $this->fields;
}
/**
* @param array $fields
*/
public function setFields($fields)
{
foreach ($fields as &$field) {
if (is_integer($field)) {
$field = sprintf('%di', $field);
} elseif (is_string($field)) {
$field = sprintf("\"%s\"", $field);
} elseif (is_bool($field)) {
$field = ($field ? "true" : "false");
}
}
$this->fields = $fields;
}
/**
* @return string
*/
public function getTimestamp()
{
return $this->timestamp;
}
/**
* @param string $timestamp
*/
public function setTimestamp($timestamp)
{
$this->timestamp = $timestamp;
}
/**
* Escapes invalid characters in both the array key and array value
*
* @param array $arr
* @return array
*/
private function escapeCharacters(array $arr)
{
$returnArr = [];
foreach ($arr as $key => $value) {
$returnArr[$this->addSlashes($key)] = $this->addSlashes($value);
}
return $returnArr;
}
/**
* Returns strings with space, comma, or equals sign characters backslashed per Influx write protocol syntax
*
* @param string $value
* @return string
*/
private function addSlashes($value)
{
return str_replace([
' ',
',',
'='
],[
'\ ',
'\,',
'\='
], $value);
}
/**
* @param array $arr
* @return string
@ -113,14 +225,26 @@ class Point
*/
private function isValidTimeStamp($timestamp)
{
if ((int) $timestamp === $timestamp) {
return true;
}
if ($timestamp <= PHP_INT_MAX && $timestamp >= ~PHP_INT_MAX) {
// if the code is run on a 32bit system, loosely check if the timestamp is a valid numeric
if (PHP_INT_SIZE == 4 && is_numeric($timestamp)) {
return true;
}
if (!is_numeric($timestamp)) {
return false;
}
if (intval($timestamp) != $timestamp) {
return false;
}
if (!($timestamp <= PHP_INT_MAX && $timestamp >= ~PHP_INT_MAX)) {
return false;
}
return true;
}
}

View File

@ -60,6 +60,16 @@ class Builder
*/
protected $limitClause = '';
/**
* @var array
*/
protected $groupBy;
/**
* @var array
*/
protected $orderBy;
/**
* @param Database $db
*/
@ -178,6 +188,18 @@ class Builder
return $this;
}
public function groupBy($field = 'type') {
$this->groupBy[] = $field;
return $this;
}
public function orderBy($field = 'type', $order = 'ASC') {
$this->orderBy[] = "$field $order";
return $this;
}
/**
* Set's the time range to select data from
*
@ -244,7 +266,7 @@ class Builder
*/
protected function parseQuery()
{
$query = sprintf("SELECT %s FROM %s", $this->selection, $this->metric);
$query = sprintf('SELECT %s FROM "%s"', $this->selection, $this->metric);
if (! $this->metric) {
throw new \InvalidArgumentException('No metric provided to from()');
@ -262,6 +284,14 @@ class Builder
}
if (!empty($this->groupBy)) {
$query .= ' GROUP BY ' . implode(',', $this->groupBy);
}
if (!empty($this->orderBy)) {
$query .= ' ORDER BY ' . implode(',', $this->orderBy);
}
if ($this->limitClause) {
$query .= $this->limitClause;
}

View File

@ -89,6 +89,14 @@ class ResultSet
return array_shift($series);
}
/**
* @return mixed
*/
public function getColumns()
{
return $this->getSeries()[0]['columns'];
}
/**
* @param array $serie
* @return array

View File

@ -0,0 +1,68 @@
<?php
/**
* @author Stephen "TheCodeAssassin" Hoogendijk
*/
require 'vendor/autoload.php';
// vagrant ip
$host = '192.168.33.10';
function randFloat($min, $max)
{
$range = $max-$min;
$num = $min + $range * mt_rand(0, 32767)/32767;
$num = round($num, 4);
return ((float) $num);
}
$client = new \InfluxDB\Client($host);
$database = $client->selectDB('test');
if ($database->exists()) {
$database->drop();
}
$database->create(new \InfluxDB\Database\RetentionPolicy('test', '12w', 1, true));
$start = microtime(true);
$countries = ['nl', 'gb', 'us', 'be', 'th', 'jp', 'nl', 'sa'];
$colors = ['orange', 'black', 'yellow', 'white', 'red', 'purple'];
$points = [];
for ($i=0; $i < 1000; $i++) {
$points[] = new \InfluxDB\Point(
'flags',
randFloat(1, 999),
['country' => $countries[array_rand($countries)]],
['color' => $colors[array_rand($colors)]],
(int) shell_exec('date +%s%N')+mt_rand(1,1000)
);
};
// insert the points
$database->writePoints($points);
$end = microtime(true);
$country = $countries[array_rand($countries)];
$color = $colors[array_rand($colors)];
$results = $database->query("SELECT * FROM flags WHERE country = '$country' LIMIT 5")->getPoints();
$results2 = $database->query("SELECT * FROM flags WHERE color = '$color' LIMIT 5")->getPoints();
echo "Showing top 5 flags from country $country:" . PHP_EOL;
print_r($results);
echo PHP_EOL;
echo "Showing top 5 flags with color $color:" . PHP_EOL;
print_r($results2);
echo PHP_EOL;
echo sprintf('Executed 1000 inserts in %.2f seconds', $end - $start);

View File

@ -10,7 +10,7 @@ use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use InfluxDB\Client;
use InfluxDB\Database;
use InfluxDB\Driver\Guzzle;
use InfluxDB\Driver\Guzzle as GuzzleDriver;
use InfluxDB\ResultSet;
use PHPUnit_Framework_MockObject_MockObject;
use GuzzleHttp\Client as GuzzleClient;
@ -44,7 +44,7 @@ abstract class AbstractTest extends \PHPUnit_Framework_TestCase
->disableOriginalConstructor()
->getMock();
$this->resultData = file_get_contents(dirname(__FILE__) . '/result.example.json');
$this->resultData = file_get_contents(dirname(__FILE__) . '/json/result.example.json');
$this->mockClient->expects($this->any())
->method('getBaseURI')
@ -52,9 +52,17 @@ abstract class AbstractTest extends \PHPUnit_Framework_TestCase
$this->mockClient->expects($this->any())
->method('query')
->will($this->returnValue(new ResultSet($this->resultData)));
->will($this->returnCallback(function ($db, $query) {
Client::$lastQuery = $query;
$httpMockClient = new Guzzle($this->buildHttpMockClient(''));
return new ResultSet($this->resultData);
}));
$this->mockClient->expects($this->any())
->method('write')
->will($this->returnValue(true));
$httpMockClient = new GuzzleDriver($this->buildHttpMockClient(''));
// make sure the client has a valid driver
$this->mockClient->expects($this->any())
@ -88,7 +96,13 @@ abstract class AbstractTest extends \PHPUnit_Framework_TestCase
public function buildHttpMockClient($body)
{
// Create a mock and queue two responses.
$mock = new MockHandler([new Response(200, array(), $body)]);
$mock = new MockHandler([
new Response(200, array(), $body),
new Response(200, array(), $body),
new Response(400, array(), 'fault{'),
new Response(400, array(), $body),
new Response(400, array(), $body),
]);
$handler = HandlerStack::create($mock);
return new GuzzleClient(['handler' => $handler]);
@ -114,7 +128,7 @@ abstract class AbstractTest extends \PHPUnit_Framework_TestCase
->getMock();
if ($emptyResult) {
$mockClient->expects($this->once())
$mockClient->expects($this->any())
->method('query')
->will($this->returnValue(new ResultSet($this->getEmptyResult())));
}

View File

@ -47,7 +47,7 @@ class AdminTest extends AbstractTest
public function testShowUsers()
{
$testJson = file_get_contents(dirname(__FILE__) . '/result-test-users.example.json');
$testJson = file_get_contents(dirname(__FILE__) . '/json/result-test-users.example.json');
$clientMock = $this->getClientMock();
$testResult = new ResultSet($testJson);

View File

@ -4,6 +4,7 @@ namespace InfluxDB\Test;
use InfluxDB\Client;
use InfluxDB\Driver\Guzzle;
use InfluxDB\Point;
class ClientTest extends AbstractTest
{
@ -19,16 +20,26 @@ class ClientTest extends AbstractTest
/** @var Client $client */
protected $client = null;
public function testGetters()
{
$client = $this->getClient();
$this->assertEquals('http://localhost:8086', $client->getBaseURI());
$this->assertInstanceOf('InfluxDB\Driver\Guzzle', $client->getDriver());
$this->assertEquals('localhost', $client->getHost());
$this->assertEquals('0', $client->getTimeout());
}
public function testBaseURl()
{
$client = new Client('localhost', 8086);
$client = $this->getClient();
$this->assertEquals($client->getBaseURI(), 'http://localhost:8086');
}
public function testSelectDbShouldReturnDatabaseInstance()
{
$client = new Client('localhost', 8086);
$client = $this->getClient();
$dbName = 'test-database';
$database = $client->selectDB($dbName);
@ -38,23 +49,110 @@ class ClientTest extends AbstractTest
$this->assertEquals($dbName, $database->getName());
}
public function testSecureInstance()
{
$client = $this->getClient('test', 'test', true);
$urlParts = parse_url($client->getBaseURI());
$this->assertEquals('https', $urlParts['scheme']);
}
/**
*/
public function testGuzzleQuery()
{
$client = new Client('localhost', 8086);
$client = $this->getClient('test', 'test');
$query = "some-bad-query";
$bodyResponse = file_get_contents(dirname(__FILE__) . '/result.example.json');
$bodyResponse = file_get_contents(dirname(__FILE__) . '/json/result.example.json');
$httpMockClient = $this->buildHttpMockClient($bodyResponse);
$client->setDriver(new Guzzle($httpMockClient));
/** @var \InfluxDB\ResultSet $result */
$result = $client->query(null, $query);
$result = $client->query('somedb', $query);
$parameters = $client->getDriver()->getParameters();
$this->assertEquals(['test', 'test'], $parameters['auth']);
$this->assertEquals('somedb', $parameters['database']);
$this->assertInstanceOf('\InfluxDB\ResultSet', $result);
$this->assertEquals(
true,
$client->write(
[
'url' => 'http://localhost',
'database' => 'influx_test_db',
'method' => 'post'
],
[new Point('test', 1.0)]
)
);
$this->setExpectedException('\InvalidArgumentException');
$client->query('test', 'bad-query');
$this->setExpectedException('\InfluxDB\Driver\Exception');
$client->query('test', 'bad-query');
}
public function testGetLastQuery()
{
$this->mockClient->query('test', 'SELECT * from test_metric');
$this->assertEquals($this->getClient()->getLastQuery(), 'SELECT * from test_metric');
}
public function testListDatabases()
{
$this->doTestResponse('databases.example.json', ['test', 'test1', 'test2'], 'listDatabases');
}
public function testListUsers()
{
$this->doTestResponse('users.example.json', ['user', 'admin'], 'listUsers');
}
public function testFactoryMethod()
{
$client = $this->getClient('test', 'test', true);
$staticClient = \InfluxDB\Client::fromDSN('https+influxdb://test:test@localhost:8086/');
$this->assertEquals($client, $staticClient);
$db = $client->selectDB('testdb');
$staticDB = \InfluxDB\Client::fromDSN('https+influxdb://test:test@localhost:8086/testdb');
$this->assertEquals($db, $staticDB);
}
/**
* @param string $responseFile
* @param array $result
* @param string $method
*/
protected function doTestResponse($responseFile, array $result, $method)
{
$client = $this->getClient();
$bodyResponse = file_get_contents(dirname(__FILE__) . '/json/'. $responseFile);
$httpMockClient = $this->buildHttpMockClient($bodyResponse);
$client->setDriver(new Guzzle($httpMockClient));
$this->assertEquals($result, $client->$method());
}
/**
* @param string $username
* @param string $password
* @param bool|false $ssl
*
* @return Client
*/
protected function getClient($username = '', $password = '', $ssl = false)
{
return new Client('localhost', 8086, $username, $password, $ssl);
}
}

View File

@ -27,34 +27,54 @@ class DatabaseTest extends AbstractTest
{
parent::setUp();
$this->resultData = file_get_contents(dirname(__FILE__) . '/result.example.json');
$this->resultData = file_get_contents(dirname(__FILE__) . '/json/result.example.json');
$this->mockClient->expects($this->any())
->method('listDatabases')
->will($this->returnValue(array('test123', 'test')));
$this->dataToInsert = file_get_contents(dirname(__FILE__) . '/input.example.json');
$this->dataToInsert = file_get_contents(dirname(__FILE__) . '/json/input.example.json');
}
public function testGetters()
{
$this->assertInstanceOf('InfluxDB\Client', $this->database->getClient());
$this->assertInstanceOf('InfluxDB\Query\Builder', $this->database->getQueryBuilder());
}
/**
*
*/
public function testQuery()
public function testQueries()
{
$testResultSet = new ResultSet($this->resultData);
$this->assertEquals($this->database->query('SELECT * FROM test_metric'), $testResultSet);
$this->database->drop();
$this->assertEquals('DROP DATABASE "influx_test_db"', Client::$lastQuery);
}
public function testCreateRetentionPolicy()
public function testRetentionPolicyQueries()
{
$retentionPolicy = new Database\RetentionPolicy('test', '1d', 1, true);
$retentionPolicy = $this->getTestRetentionPolicy();
$mockClient = $this->getClientMock(true);
$this->assertEquals(
$this->getTestDatabase()->createRetentionPolicy($retentionPolicy),
new ResultSet($this->getEmptyResult())
);
$database = new Database('test', $mockClient);
$this->database->listRetentionPolicies();
$this->assertEquals('SHOW RETENTION POLICIES ON "influx_test_db"', Client::$lastQuery);
$this->assertEquals($database->createRetentionPolicy($retentionPolicy), new ResultSet($this->getEmptyResult()));
$this->database->alterRetentionPolicy($this->getTestRetentionPolicy());
$this->assertEquals(
'ALTER RETENTION POLICY "test" ON "influx_test_db" DURATION 1d REPLICATION 1 DEFAULT',
Client::$lastQuery
);
}
/**
@ -65,6 +85,38 @@ class DatabaseTest extends AbstractTest
new Database(null, $this->mockClient);
}
public function testCreate()
{
// test create with retention policy
$this->database->create($this->getTestRetentionPolicy('influx_test_db'), true);
$this->assertEquals(
'CREATE RETENTION POLICY "influx_test_db" ON "influx_test_db" DURATION 1d REPLICATION 1 DEFAULT',
Client::$lastQuery
);
// test creating a database without create if not exists
$this->database->create(null, true);
$this->assertEquals('CREATE DATABASE IF NOT EXISTS "influx_test_db"', Client::$lastQuery);
// test creating a database without create if not exists
$this->database->create(null, false);
$this->assertEquals('CREATE DATABASE "influx_test_db"', Client::$lastQuery);
$this->mockClient->expects($this->any())
->method('query')
->will($this->returnCallback(function () {
throw new \Exception('test exception');
}));
// test an exception being handled correctly
$this->setExpectedException('\InfluxDB\Database\Exception');
$this->database->create($this->getTestRetentionPolicy('influx_test_db'), false);
}
public function testExists()
{
$database = new Database('test', $this->mockClient);
@ -96,5 +148,76 @@ class DatabaseTest extends AbstractTest
);
$this->assertEquals(true, $this->database->writePoints(array($point1, $point2)));
$this->mockClient->expects($this->once())
->method('write')
->will($this->throwException(new \Exception('Test exception')));
$this->setExpectedException('InfluxDB\Exception');
$this->database->writePoints(array($point1, $point2));
}
public function testQueryBuilderOrderBy()
{
$this->assertEquals(
$this->database->getQueryBuilder()
->from('test_metric')
->orderBy('time', 'DESC')->getQuery(),
'SELECT * FROM "test_metric" ORDER BY time DESC');
$this->assertEquals(
$this->database->getQueryBuilder()
->from('test_metric')
->orderBy('time', 'DESC')
->orderBy('some_field', 'ASC')
->getQuery(),
'SELECT * FROM "test_metric" ORDER BY time DESC,some_field ASC');
}
/**
* @see https://github.com/influxdata/influxdb-php/pull/36
*/
public function testReservedNames()
{
$database = new Database('stats', $this->mockClient);
// test handling of reserved keywords in database name
$database->create();
$this->assertEquals('CREATE DATABASE IF NOT EXISTS "stats"', Client::$lastQuery);
$database->listRetentionPolicies();
$this->assertEquals('SHOW RETENTION POLICIES ON "stats"', Client::$lastQuery);
// test handling of reserved keywords in retention policy names
$database->create($this->getTestRetentionPolicy('default'));
$this->assertEquals(
'CREATE RETENTION POLICY "default" ON "stats" DURATION 1d REPLICATION 1 DEFAULT',
Client::$lastQuery
);
// test handling of reserved keywords in measurement names
$this->assertEquals($database->getQueryBuilder()->from('server')->getQuery(), 'SELECT * FROM "server"');
}
/**
* @param string $name
*
* @return Database
*/
protected function getTestDatabase($name = 'test')
{
return new Database($name, $this->getClientMock(true));
}
/**
* @param string $name
*
* @return Database\RetentionPolicy
*/
protected function getTestRetentionPolicy($name = 'test')
{
return new Database\RetentionPolicy($name, '1d', 1, true);
}
}

View File

@ -1,10 +1,4 @@
<?php
/**
* Created by PhpStorm.
* User: dmartinez
* Date: 18-6-15
* Time: 17:39
*/
namespace InfluxDB\Test;
@ -15,16 +9,119 @@ class PointTest extends \PHPUnit_Framework_TestCase
{
public function testPointStringRepresentation()
{
$expected = 'cpu_load_short,host=server01,region=us-west cpucount=10,value=0.64 1435222310';
$expected = 'instance,host=server01,region=us-west cpucount=10i,free=1i,test="string",bool=false,value=1.11 1440494531376778481';
$point = new Point(
'cpu_load_short',
0.64,
array('host' => 'server01', 'region' => 'us-west'),
array('cpucount' => 10),
1435222310
);
$point = $this->getPoint('1440494531376778481');
$this->assertEquals($expected, (string) $point);
}
/**
* Check if the Point class throw an exception when invalid timestamp are given.
*
* @dataProvider wrongTimestampProvider
* @expectedException \InfluxDB\Database\Exception
*/
public function testPointWrongTimestamp($timestamp)
{
$this->getPoint($timestamp);
}
/**
* Check if the Point class accept all valid timestamp given.
*
* @dataProvider validTimestampProvider
*/
public function testPointValidTimestamp($timestamp)
{
$expected = 'instance,host=server01,region=us-west cpucount=10i,free=1i,test="string",bool=false,value=1.11' . (($timestamp) ? ' ' . $timestamp : '');
$point = $this->getPoint($timestamp);
$this->assertEquals($expected, (string) $point);
}
public function testGettersAndSetters()
{
$timestamp = time();
$timestamp2 = time() + 3600;
$point = $this->getPoint($timestamp);
$this->assertEquals($timestamp, $point->getTimestamp());
$point->setTimestamp($timestamp2);
$this->assertEquals($timestamp2, $point->getTimestamp());
$this->assertEquals('instance', $point->getMeasurement());
$point->setMeasurement('test');
$this->assertEquals('test', $point->getMeasurement());
$fields = $point->getFields();
$this->assertEquals(1.11, $fields['value']);
$this->assertEquals([
'cpucount' => '10i',
'free' => '1i',
'test' => "\"string\"",
'bool' => 'false',
'value' => '1.1100000000000001'
], $fields);
$point->setFields(['cpucount' => 11]);
$this->assertEquals(['cpucount' => '11i'], $point->getFields());
$this->assertEquals(['host' => 'server01', 'region' => 'us-west'], $point->getTags());
$point->setTags(['test' => 'value']);
$this->assertEquals(['test' => 'value'], $point->getTags());
}
/**
* Provide wrong timestamp value for testing.
*/
public function wrongTimestampProvider()
{
return [
['2015-10-27 14:17:40'],
['INVALID'],
['aa778481'],
['1477aee'],
['15.258'],
['15,258'],
[15.258],
[true]
];
}
/**
* Provide valid timestamp value for testing.
*/
public function validTimestampProvider()
{
return [
[time()], // Current time returned by the PHP time function.
[0], // Day 0
[~PHP_INT_MAX], // Minimum value integer
[PHP_INT_MAX], // Maximum value integer
['1440494531376778481'] // Text timestamp
];
}
/**
* Returns an instance of \InfluxDB\Point
*
* @param int $timestamp
*
* @return Point
*/
private function getPoint($timestamp)
{
return new Point(
'instance', // the name of the measurement
1.11, // measurement value
['host' => 'server01', 'region' => 'us-west'],
['cpucount' => 10, 'free' => 1, 'test' => "string", 'bool' => false],
$timestamp
);
}
}

View File

@ -10,7 +10,7 @@ class ResultSetTest extends \PHPUnit_Framework_TestCase
public function setUp()
{
$resultJsonExample = file_get_contents(dirname(__FILE__) . '/result.example.json');
$resultJsonExample = file_get_contents(dirname(__FILE__) . '/json/result.example.json');
$this->resultSet = new ResultSet($resultJsonExample);
}
@ -65,7 +65,7 @@ EOD;
*/
public function testGetPointsFromNameWithoudTags()
{
$resultJsonExample = file_get_contents(dirname(__FILE__) . '/result-no-tags.example.json');
$resultJsonExample = file_get_contents(dirname(__FILE__) . '/json/result-no-tags.example.json');
$this->resultSet = new ResultSet($resultJsonExample);
$measurementName = 'cpu_load_short';
@ -95,6 +95,11 @@ EOD;
}
public function testGetSeries()
{
$this->assertEquals(['time', 'value'], $this->resultSet->getColumns());
}
/**
* We can get points from measurement
*/

View File

@ -0,0 +1,25 @@
{
"results":[
{
"series":[
{
"name":"databases",
"columns":[
"name"
],
"values":[
[
"test"
],
[
"test1"
],
[
"test2"
]
]
}
]
}
]
}

View File

@ -0,0 +1,14 @@
{
"results":[
{
"series":[
{
"columns":[
"user",
"admin"
]
}
]
}
]
}