Skip to content

Commit 1e4d676

Browse files
committed
feat: Query params
1 parent bfcbcf6 commit 1e4d676

16 files changed

+740
-17
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
### Features
44
- [167](https://github.com/tobiasschuerg/InfluxDB-Client-for-Arduino/pull/167) - Added `InfluxDBClient::writeRecord(const char *record)`.
55
- [167](https://github.com/tobiasschuerg/InfluxDB-Client-for-Arduino/pull/167) - Added possibility to disable retrying by setting `maxRetryAttempts` to zero: `client.setWriteOptions(WriteOptions().maxRetryAttempts(0));`
6-
- [172](https://github.com/tobiasschuerg/InfluxDB-Client-for-Arduino/pull/170) - Added directly streaming batch for write. It can be enable by `InfluxDBClient::setStreamWrite(bool enable = true)`. Writing by streaming lines of batch saves RAM as it sends data without allocating a buffer. On the other hand, this way of writing is about half times slower than the classic way, when allocating the buffer for writing the whole batch.
7-
- [172](https://github.com/tobiasschuerg/InfluxDB-Client-for-Arduino/pull/170) - Allowing larger batch size, > 255.
6+
- [172](https://github.com/tobiasschuerg/InfluxDB-Client-for-Arduino/pull/172) - Added directly streaming batch for write. It can be enable by `InfluxDBClient::setStreamWrite(bool enable = true)`. Writing by streaming lines of batch saves RAM as it sends data without allocating a buffer. On the other hand, this way of writing is about half times slower than the classic way, when allocating the buffer for writing the whole batch.
7+
- [172](https://github.com/tobiasschuerg/InfluxDB-Client-for-Arduino/pull/172) - Allowing larger batch size, > 255.
8+
- [173](https://github.com/tobiasschuerg/InfluxDB-Client-for-Arduino/pull/173) - Added Flux query parameters. Now supported by InfluxDB Cloud only.
89

910
## 3.9.0 [2021-09-17]
1011
### Features

README.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ This library doesn't support using those devices as a peripheral.
3333
- [InfluxDb 1](#influxdb-1)
3434
- [Skipping certificate validation](#skipping-certificate-validation)
3535
- [Querying](#querying)
36+
- [Parametrized Queries](#parametrized-queries)
3637
- [Original API](#original-api)
3738
- [Initialization](#initialization)
3839
- [Sending a single measurement](#sending-a-single-measurement)
@@ -524,6 +525,87 @@ if(result.getError() != "") {
524525
```
525526
Complete source code is available in [QueryAggregated example](examples/QueryAggregated/QueryAggregated.ino).
526527

528+
### Parametrized Queries
529+
InfluxDB Cloud supports [Parameterized Queries](https://docs.influxdata.com/influxdb/cloud/query-data/parameterized-queries/)
530+
that let you dynamically change values in a query using the InfluxDB API. Parameterized queries make Flux queries more
531+
reusable and can also be used to help prevent injection attacks.
532+
533+
InfluxDB Cloud inserts the params object into the Flux query as a Flux record named `params`. Use dot or bracket
534+
notation to access parameters in the `params` record in your Flux query. Parameterized Flux queries support only `int`
535+
, `float`, and `string` data types. To convert the supported data types into
536+
other [Flux basic data types, use Flux type conversion functions](https://docs.influxdata.com/influxdb/cloud/query-data/parameterized-queries/#supported-parameter-data-types).
537+
538+
Parameterized query example:
539+
> :warning: Parameterized Queries are supported only in InfluxDB Cloud, currently there is no support in InfluxDB OSS.
540+
541+
```cpp
542+
// Prepare query parameters
543+
QueryParams params;
544+
params.add("bucket", INFLUXDB_BUCKET);
545+
params.add("since", "-5m");
546+
params.add("device", DEVICE);
547+
params.add("rssiThreshold", -50);
548+
549+
// Construct a Flux query using parameters
550+
// Parameters are accessed via the 'params' Flux object
551+
// Flux only supports only string, float and int as parameters. Duration can be converted from string.
552+
// Query will find RSSI less than defined threshold
553+
String query = "from(bucket: params.bucket) |> range(start: duration(v: params.since)) \
554+
|> filter(fn: (r) => r._measurement == \"wifi_status\") \
555+
|> filter(fn: (r) => r._field == \"rssi\") \
556+
|> filter(fn: (r) => r.device == params.device) \
557+
|> filter(fn: (r) => r._value < params.rssiThreshold)";
558+
559+
// Print ouput header
560+
// Print composed query
561+
Serial.print("Querying with: ");
562+
Serial.println(query);
563+
564+
// Send query to the server and get result
565+
FluxQueryResult result = client.query(query, params);
566+
567+
//Print header
568+
Serial.printf("%10s %20s %5s\n","Time","SSID","RSSI");
569+
570+
for(int i=0;i<37;i++) {
571+
Serial.print('-');
572+
}
573+
Serial.println();
574+
575+
// Iterate over rows. Even there is just one row, next() must be called at least once.
576+
int c = 0;
577+
while (result.next()) {
578+
// Get converted value for flux result column 'SSID'
579+
String ssid = result.getValueByName("SSID").getString();
580+
581+
// Get converted value for flux result column '_value' where there is RSSI value
582+
long rssi = result.getValueByName("_value").getLong();
583+
584+
// Get converted value for the _time column
585+
FluxDateTime time = result.getValueByName("_time").getDateTime();
586+
587+
// Format date-time for printing
588+
// Format string according to http://www.cplusplus.com/reference/ctime/strftime/
589+
String timeStr = time.format("%F %T");
590+
// Print formatted row
591+
Serial.printf("%20s %10s %5d\n", timeStr.c_str(), ssid.c_str() ,rssi);
592+
c++;
593+
}
594+
if(!c) {
595+
Serial.println(" No data found");
596+
}
597+
598+
// Check if there was an error
599+
if(result.getError() != "") {
600+
Serial.print("Query result error: ");
601+
Serial.println(result.getError());
602+
}
603+
604+
// Close the result
605+
result.close();
606+
```
607+
Complete source code is available in [QueryParams example](examples/QueryParams/QueryParams.ino).
608+
527609
## Original API
528610

529611
### Initialization

examples/QueryParams/QueryParams.ino

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/**
2+
* QueryParams Example code for InfluxDBClient library for Arduino.
3+
*
4+
* This example demonstrates querying using parameters inserted into the Flux query. We select WiFi signal level values bellow a certain threshold.
5+
* WiFi signal is measured and stored in BasicWrite and SecureWrite examples.
6+
*
7+
* Demonstrates connection to any InfluxDB instance accesible via:
8+
* - InfluxDB 2 Cloud at https://cloud2.influxdata.com/ (certificate is preconfigured)
9+
*
10+
* Enter WiFi and InfluxDB parameters below
11+
**/
12+
13+
#if defined(ESP32)
14+
#include <WiFiMulti.h>
15+
WiFiMulti wifiMulti;
16+
#define DEVICE "ESP32"
17+
#elif defined(ESP8266)
18+
#include <ESP8266WiFiMulti.h>
19+
ESP8266WiFiMulti wifiMulti;
20+
#define DEVICE "ESP8266"
21+
#endif
22+
23+
#include <InfluxDbClient.h>
24+
#include <InfluxDbCloud.h>
25+
26+
// WiFi AP SSID
27+
#define WIFI_SSID "SSID"
28+
// WiFi password
29+
#define WIFI_PASSWORD "PASSWORD"
30+
// InfluxDB v2 server url, e.g. https://eu-central-1-1.aws.cloud2.influxdata.com (Use: InfluxDB UI -> Load Data -> Client Libraries)
31+
// InfluxDB 1.8+ (v2 compatibility API) server url, e.g. http://192.168.1.48:8086
32+
#define INFLUXDB_URL "server-url"
33+
// InfluxDB v2 server or cloud API authentication token (Use: InfluxDB UI -> Load Data -> Tokens -> <select token>)
34+
// InfluxDB 1.8+ (v2 compatibility API) use form user:password, eg. admin:adminpass
35+
#define INFLUXDB_TOKEN "server token"
36+
// InfluxDB v2 organization name or id (Use: InfluxDB UI -> Settings -> Profile -> <name under tile> )
37+
// InfluxDB 1.8+ (v2 compatibility API) use any non empty string
38+
#define INFLUXDB_ORG "org name/id"
39+
// InfluxDB v2 bucket name (Use: InfluxDB UI -> Load Data -> Buckets)
40+
// InfluxDB 1.8+ (v2 compatibility API) use database name
41+
#define INFLUXDB_BUCKET "bucket name"
42+
43+
// Set timezone string according to https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
44+
// Examples:
45+
// Pacific Time: "PST8PDT"
46+
// Eastern: "EST5EDT"
47+
// Japanesse: "JST-9"
48+
// Central Europe: "CET-1CEST,M3.5.0,M10.5.0/3"
49+
#define TZ_INFO "CET-1CEST,M3.5.0,M10.5.0/3"
50+
51+
// InfluxDB client instance with preconfigured InfluxCloud certificate
52+
InfluxDBClient client(INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN, InfluxDbCloud2CACert);
53+
54+
void setup() {
55+
Serial.begin(115200);
56+
57+
// Setup wifi
58+
WiFi.mode(WIFI_STA);
59+
wifiMulti.addAP(WIFI_SSID, WIFI_PASSWORD);
60+
61+
Serial.print("Connecting to wifi");
62+
while (wifiMulti.run() != WL_CONNECTED) {
63+
Serial.print(".");
64+
delay(500);
65+
}
66+
Serial.println();
67+
68+
69+
// Accurate time is necessary for certificate validation
70+
// For the fastest time sync find NTP servers in your area: https://www.pool.ntp.org/zone/
71+
// Syncing progress and the time will be printed to Serial
72+
timeSync(TZ_INFO, "pool.ntp.org", "time.nis.gov");
73+
74+
// Check server connection
75+
if (client.validateConnection()) {
76+
Serial.print("Connected to InfluxDB: ");
77+
Serial.println(client.getServerUrl());
78+
} else {
79+
Serial.print("InfluxDB connection failed: ");
80+
Serial.println(client.getLastErrorMessage());
81+
}
82+
}
83+
84+
85+
// Queries WiFi signal level values bellow a certain threshold using parameters inserted into the Flux query
86+
// Prints composed query and the result values.
87+
void loop() {
88+
// Prepare query parameters
89+
QueryParams params;
90+
params.add("bucket", INFLUXDB_BUCKET);
91+
params.add("since", "-5m");
92+
params.add("device", DEVICE);
93+
params.add("rssiTreshold", -50);
94+
95+
// Construct a Flux query using parameters
96+
// Parameters are accessed via the 'params' Flux object
97+
// Flux only supports only string, float and int as parameters. Duration can be converted from string.
98+
// Query will find RSSI less than defined treshold
99+
String query = "from(bucket: params.bucket) |> range(start: duration(v: params.since)) \
100+
|> filter(fn: (r) => r._measurement == \"wifi_status\") \
101+
|> filter(fn: (r) => r._field == \"rssi\") \
102+
|> filter(fn: (r) => r.device == params.device) \
103+
|> filter(fn: (r) => r._value < params.rssiTreshold)";
104+
105+
// Print ouput header
106+
// Print composed query
107+
Serial.print("Querying with: ");
108+
Serial.println(query);
109+
110+
// Send query to the server and get result
111+
FluxQueryResult result = client.query(query, params);
112+
113+
//Print header
114+
Serial.printf("%10s %20s %5s\n","Time","SSID","RSSI");
115+
116+
for(int i=0;i<37;i++) {
117+
Serial.print('-');
118+
}
119+
Serial.println();
120+
121+
// Iterate over rows. Even there is just one row, next() must be called at least once.
122+
int c = 0;
123+
while (result.next()) {
124+
// Get converted value for flux result column 'SSID'
125+
String ssid = result.getValueByName("SSID").getString();
126+
127+
// Get converted value for flux result column '_value' where there is RSSI value
128+
long rssi = result.getValueByName("_value").getLong();
129+
130+
// Get converted value for the _time column
131+
FluxDateTime time = result.getValueByName("_time").getDateTime();
132+
133+
// Format date-time for printing
134+
// Format string according to http://www.cplusplus.com/reference/ctime/strftime/
135+
String timeStr = time.format("%F %T");
136+
// Print formatted row
137+
Serial.printf("%20s %10s %5d\n", timeStr.c_str(), ssid.c_str() ,rssi);
138+
c++;
139+
}
140+
if(!c) {
141+
Serial.println(" No data found");
142+
}
143+
144+
// Check if there was an error
145+
if(result.getError() != "") {
146+
Serial.print("Query result error: ");
147+
Serial.println(result.getError());
148+
}
149+
150+
// Close the result
151+
result.close();
152+
// Wait 15s
153+
delay(15000);
154+
}

src/InfluxDbClient.cpp

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -572,9 +572,16 @@ static const char QueryDialect[] PROGMEM = "\
572572
\"header\": true,\
573573
\"delimiter\": \",\",\
574574
\"commentPrefix\": \"#\"\
575-
}}";
575+
}";
576+
577+
static const char Params[] PROGMEM = ",\
578+
\"params\": {";
576579

577580
FluxQueryResult InfluxDBClient::query(String fluxQuery) {
581+
return query(fluxQuery, QueryParams());
582+
}
583+
584+
FluxQueryResult InfluxDBClient::query(String fluxQuery, QueryParams params) {
578585
uint32_t rwt = getRemainingRetryTime();
579586
if(rwt > 0) {
580587
INFLUXDB_CLIENT_DEBUG("[W] Cannot query yet, pause %ds, %ds yet\n", _retryTime, rwt);
@@ -590,12 +597,28 @@ FluxQueryResult InfluxDBClient::query(String fluxQuery) {
590597
INFLUXDB_CLIENT_DEBUG("[D] Query to %s\n", _queryUrl.c_str());
591598
INFLUXDB_CLIENT_DEBUG("[D] JSON query:\n%s\n", fluxQuery.c_str());
592599

593-
String body = F("{\"type\":\"flux\",\"query\":\"");
594-
body += escapeJSONString(fluxQuery) + "\",";
600+
String queryEsc = escapeJSONString(fluxQuery);
601+
String body;
602+
body.reserve(150 + queryEsc.length() + params.size()*30);
603+
body = F("{\"type\":\"flux\",\"query\":\"");
604+
body += queryEsc;
605+
body += "\",";
595606
body += FPSTR(QueryDialect);
596-
607+
if(params.size()) {
608+
body += FPSTR(Params);
609+
body += params.jsonString(0);
610+
for(int i=1;i<params.size();i++) {
611+
body +=",";
612+
char *js = params.jsonString(i);
613+
body += js;
614+
delete [] js;
615+
}
616+
body += '}';
617+
}
618+
body += '}';
597619
CsvReader *reader = nullptr;
598620
_retryTime = 0;
621+
INFLUXDB_CLIENT_DEBUG("[D] Query: %s\n", body.c_str());
599622
if(_service->doPOST(_queryUrl.c_str(), body.c_str(), PSTR("application/json"), 200, [&](HTTPClient *httpClient){
600623
bool chunked = false;
601624
if(httpClient->hasHeader(TransferEncoding)) {

src/InfluxDbClient.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "Point.h"
3333
#include "WritePrecision.h"
3434
#include "query/FluxParser.h"
35+
#include "query/Params.h"
3536
#include "util/helpers.h"
3637
#include "Options.h"
3738
#include "BucketsClient.h"
@@ -130,6 +131,10 @@ class InfluxDBClient {
130131
// Use FluxQueryResult::next() method to iterate over lines of the query result.
131132
// Always call of FluxQueryResult::close() when reading is finished. Check FluxQueryResult doc for more info.
132133
FluxQueryResult query(String fluxQuery);
134+
// Sends Flux query with params and returns FluxQueryResult object for subsequently reading flux query response.
135+
// Use FluxQueryResult::next() method to iterate over lines of the query result.
136+
// Always call of FluxQueryResult::close() when reading is finished. Check FluxQueryResult doc for more info.
137+
FluxQueryResult query(String fluxQuery, QueryParams params);
133138
// Forces writing of all points in buffer, even the batch is not full.
134139
// Returns true if successful, false in case of any error
135140
bool flushBuffer();

src/query/FluxParser.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ bool FluxQueryResult::next() {
202202
return true;
203203
}
204204

205-
FluxDateTime *FluxQueryResult::convertRfc3339(String value, const char *type) {
205+
FluxDateTime *FluxQueryResult::convertRfc3339(String &value, const char *type) {
206206
tm t = {0,0,0,0,0,0,0,0,0};
207207
// has the time part
208208
int zet = value.indexOf('Z');
@@ -240,7 +240,7 @@ FluxDateTime *FluxQueryResult::convertRfc3339(String value, const char *type) {
240240
return new FluxDateTime(value, type, t, fracts);
241241
}
242242

243-
FluxBase *FluxQueryResult::convertValue(String value, String dataType) {
243+
FluxBase *FluxQueryResult::convertValue(String &value, String &dataType) {
244244
FluxBase *ret = nullptr;
245245
if(dataType.equals(FluxDatatypeDatetimeRFC3339) || dataType.equals(FluxDatatypeDatetimeRFC3339Nano)) {
246246
const char *type = FluxDatatypeDatetimeRFC3339;

src/query/FluxParser.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,8 @@ class FluxQueryResult {
8686
// Descructor
8787
~FluxQueryResult();
8888
protected:
89-
FluxBase *convertValue(String value, String dataType);
90-
static FluxDateTime *convertRfc3339(String value, const char *type);
89+
FluxBase *convertValue(String &value, String &dataType);
90+
static FluxDateTime *convertRfc3339(String &value, const char *type);
9191
void clearValues();
9292
void clearColumns();
9393
private:

0 commit comments

Comments
 (0)