Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parameter binding in InfluxQL (influxdata/influxdb-java#274) #429

Merged
merged 11 commits into from
Mar 21, 2018
101 changes: 101 additions & 0 deletions src/main/java/org/influxdb/dto/BoundParameterQuery.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package org.influxdb.dto;

import com.squareup.moshi.JsonWriter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.influxdb.InfluxDBIOException;

import okio.Buffer;

public final class BoundParameterQuery extends Query {

private final Map<String, Object> params = new HashMap<>();

private BoundParameterQuery(final String command, final String database) {
super(command, database, true);
}

public String getParameterJsonWithUrlEncoded() {
try {
String jsonParameterObject = createJsonObject(params);
String urlEncodedJsonParameterObject = encode(jsonParameterObject);
return urlEncodedJsonParameterObject;
} catch (IOException e) {
throw new InfluxDBIOException(e);
}
}

private String createJsonObject(final Map<String, Object> parameterMap) throws IOException {
Buffer b = new Buffer();
JsonWriter writer = JsonWriter.of(b);
writer.beginObject();
for (Entry<String, Object> pair : parameterMap.entrySet()) {
String name = pair.getKey();
Object value = pair.getValue();
if (value instanceof Number) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The moshi library has a specific behavior when deserializing numbers: #153 (comment)

I hope casting to Number is enough to keep the data type on the JSON object being created here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a look at the moshi code and tested it. This works as intended.

Number number = (Number) value;
writer.name(name).value(number);
} else if (value instanceof String) {
writer.name(name).value((String) value);
} else if (value instanceof Boolean) {
writer.name(name).value((Boolean) value);
} else {
writer.name(name).value(String.valueOf(value));
}
}
writer.endObject();
return b.readString(Charset.forName("utf-8"));
}

@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + params.hashCode();
return result;
}

@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
BoundParameterQuery other = (BoundParameterQuery) obj;
if (!params.equals(other.params)) {
return false;
}
return true;
}

public static class QueryBuilder {
private BoundParameterQuery query;
private String influxQL;

public static QueryBuilder newQuery(final String influxQL) {
QueryBuilder instance = new QueryBuilder();
instance.influxQL = influxQL;
return instance;
}

public QueryBuilder forDatabase(final String database) {
query = new BoundParameterQuery(influxQL, database);
return this;
}

public QueryBuilder bind(final String placeholder, final Object value) {
query.params.put(placeholder, value);
return this;
}

public BoundParameterQuery create() {
return query;
}
}
}
43 changes: 34 additions & 9 deletions src/main/java/org/influxdb/impl/InfluxDBImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.influxdb.InfluxDBException;
import org.influxdb.InfluxDBIOException;
import org.influxdb.dto.BatchPoints;
import org.influxdb.dto.BoundParameterQuery;
import org.influxdb.dto.Point;
import org.influxdb.dto.Pong;
import org.influxdb.dto.Query;
Expand Down Expand Up @@ -454,8 +455,16 @@ public void query(final Query query, final int chunkSize, final Consumer<QueryRe
throw new UnsupportedOperationException("chunking not supported");
}

Call<ResponseBody> call = this.influxDBService.query(this.username, this.password,
query.getDatabase(), query.getCommandWithUrlEncoded(), chunkSize);
Call<ResponseBody> call = null;
if (query instanceof BoundParameterQuery) {
BoundParameterQuery boundParameterQuery = (BoundParameterQuery) query;
call = this.influxDBService.query(this.username, this.password,
query.getDatabase(), query.getCommandWithUrlEncoded(), chunkSize,
boundParameterQuery.getParameterJsonWithUrlEncoded());
} else {
call = this.influxDBService.query(this.username, this.password,
query.getDatabase(), query.getCommandWithUrlEncoded(), chunkSize);
}

call.enqueue(new Callback<ResponseBody>() {
@Override
Expand Down Expand Up @@ -496,8 +505,17 @@ public void onFailure(final Call<ResponseBody> call, final Throwable t) {
*/
@Override
public QueryResult query(final Query query, final TimeUnit timeUnit) {
return execute(this.influxDBService.query(this.username, this.password, query.getDatabase(),
TimeUtil.toTimePrecision(timeUnit), query.getCommandWithUrlEncoded()));
Call<QueryResult> call = null;
if (query instanceof BoundParameterQuery) {
BoundParameterQuery boundParameterQuery = (BoundParameterQuery) query;
call = this.influxDBService.query(this.username, this.password, query.getDatabase(),
TimeUtil.toTimePrecision(timeUnit), query.getCommandWithUrlEncoded(),
boundParameterQuery.getParameterJsonWithUrlEncoded());
} else {
call = this.influxDBService.query(this.username, this.password, query.getDatabase(),
TimeUtil.toTimePrecision(timeUnit), query.getCommandWithUrlEncoded());
}
return execute(call);
}

/**
Expand Down Expand Up @@ -560,12 +578,19 @@ public boolean databaseExists(final String name) {
*/
private Call<QueryResult> callQuery(final Query query) {
Call<QueryResult> call;
if (query.requiresPost()) {
call = this.influxDBService.postQuery(this.username,
this.password, query.getDatabase(), query.getCommandWithUrlEncoded());
if (query instanceof BoundParameterQuery) {
BoundParameterQuery boundParameterQuery = (BoundParameterQuery) query;
call = this.influxDBService.postQuery(this.username,
this.password, query.getDatabase(), query.getCommandWithUrlEncoded(),
boundParameterQuery.getParameterJsonWithUrlEncoded());
} else {
call = this.influxDBService.query(this.username,
this.password, query.getDatabase(), query.getCommandWithUrlEncoded());
if (query.requiresPost()) {
call = this.influxDBService.postQuery(this.username,
this.password, query.getDatabase(), query.getCommandWithUrlEncoded());
} else {
call = this.influxDBService.query(this.username,
this.password, query.getDatabase(), query.getCommandWithUrlEncoded());
}
}
return call;
}
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/org/influxdb/impl/InfluxDBService.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface InfluxDBService {
public static final String Q = "q";
public static final String DB = "db";
public static final String RP = "rp";
public static final String PARAMS = "params";
public static final String PRECISION = "precision";
public static final String CONSISTENCY = "consistency";
public static final String EPOCH = "epoch";
Expand Down Expand Up @@ -47,6 +48,11 @@ public Call<ResponseBody> writePoints(@Query(U) String username,
public Call<QueryResult> query(@Query(U) String username, @Query(P) String password, @Query(DB) String db,
@Query(EPOCH) String epoch, @Query(value = Q, encoded = true) String query);

@POST("/query")
public Call<QueryResult> query(@Query(U) String username, @Query(P) String password, @Query(DB) String db,
@Query(EPOCH) String epoch, @Query(value = Q, encoded = true) String query,
@Query(value = PARAMS, encoded = true) String params);

@GET("/query")
public Call<QueryResult> query(@Query(U) String username, @Query(P) String password, @Query(DB) String db,
@Query(value = Q, encoded = true) String query);
Expand All @@ -55,6 +61,10 @@ public Call<QueryResult> query(@Query(U) String username, @Query(P) String passw
public Call<QueryResult> postQuery(@Query(U) String username, @Query(P) String password, @Query(DB) String db,
@Query(value = Q, encoded = true) String query);

@POST("/query")
public Call<QueryResult> postQuery(@Query(U) String username, @Query(P) String password, @Query(DB) String db,
@Query(value = Q, encoded = true) String query, @Query(value = PARAMS, encoded = true) String params);

@GET("/query")
public Call<QueryResult> query(@Query(U) String username, @Query(P) String password,
@Query(value = Q, encoded = true) String query);
Expand All @@ -68,4 +78,10 @@ public Call<QueryResult> postQuery(@Query(U) String username,
public Call<ResponseBody> query(@Query(U) String username,
@Query(P) String password, @Query(DB) String db, @Query(value = Q, encoded = true) String query,
@Query(CHUNK_SIZE) int chunkSize);

@Streaming
@POST("/query?chunked=true")
public Call<ResponseBody> query(@Query(U) String username,
@Query(P) String password, @Query(DB) String db, @Query(value = Q, encoded = true) String query,
@Query(CHUNK_SIZE) int chunkSize, @Query(value = PARAMS, encoded = true) String params);
}
45 changes: 45 additions & 0 deletions src/test/java/org/influxdb/InfluxDBTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import org.influxdb.InfluxDB.LogLevel;
import org.influxdb.dto.BatchPoints;
import org.influxdb.dto.BoundParameterQuery.QueryBuilder;
import org.influxdb.dto.Point;
import org.influxdb.dto.Pong;
import org.influxdb.dto.Query;
import org.influxdb.dto.QueryResult;
import org.influxdb.dto.QueryResult.Series;
import org.influxdb.impl.InfluxDBImpl;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
Expand Down Expand Up @@ -89,6 +91,49 @@ public void testQuery() {
this.influxDB.query(new Query("DROP DATABASE mydb2", "mydb"));
}

@Test
public void testBoundParameterQuery() throws InterruptedException {
// set up
Point point = Point
.measurement("cpu")
.tag("atag", "test")
.addField("idle", 90L)
.addField("usertime", 9L)
.addField("system", 1L)
.build();
this.influxDB.setDatabase(UDP_DATABASE);
this.influxDB.write(point);

// test
Query query = QueryBuilder.newQuery("SELECT * FROM cpu WHERE atag = $atag")
.forDatabase(UDP_DATABASE)
.bind("atag", "test")
.create();
QueryResult result = this.influxDB.query(query);
Assertions.assertTrue(result.getResults().get(0).getSeries().size() == 1);
Series series = result.getResults().get(0).getSeries().get(0);
Assertions.assertTrue(series.getValues().size() == 1);

result = this.influxDB.query(query, TimeUnit.SECONDS);
Assertions.assertTrue(result.getResults().get(0).getSeries().size() == 1);
series = result.getResults().get(0).getSeries().get(0);
Assertions.assertTrue(series.getValues().size() == 1);

Object waitForTestresults = new Object();
Consumer<QueryResult> check = (queryResult) -> {
Assertions.assertTrue(queryResult.getResults().get(0).getSeries().size() == 1);
Series s = queryResult.getResults().get(0).getSeries().get(0);
Assertions.assertTrue(s.getValues().size() == 1);
synchronized (waitForTestresults) {
waitForTestresults.notifyAll();
}
};
this.influxDB.query(query, 10, check);
synchronized (waitForTestresults) {
waitForTestresults.wait(2000);
}
}

/**
* Tests for callback query.
*/
Expand Down
91 changes: 91 additions & 0 deletions src/test/java/org/influxdb/dto/BoundParameterQueryTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package org.influxdb.dto;

import static org.assertj.core.api.Assertions.assertThat;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

import org.influxdb.dto.BoundParameterQuery.QueryBuilder;
import org.junit.Assert;
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;

import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi;

/**
* Test for the BoundParameterQuery DTO.
*/
@RunWith(JUnitPlatform.class)
public class BoundParameterQueryTest {

@Test
public void testGetParameterJsonWithUrlEncoded() throws IOException {
BoundParameterQuery query = QueryBuilder.newQuery("SELECT * FROM abc WHERE integer > $i"
+ "AND double = $d AND bool = $bool AND string = $string AND other = $object")
.forDatabase("foobar")
.bind("i", 0)
.bind("d", 1.0)
.bind("bool", true)
.bind("string", "test")
.bind("object", new Object())
.create();

Moshi moshi = new Moshi.Builder().build();
JsonAdapter<Point> adapter = moshi.adapter(Point.class);
Point point = adapter.fromJson(decode(query.getParameterJsonWithUrlEncoded()));
Assert.assertEquals(0, point.i);
Assert.assertEquals(1.0, point.d, 0.0);
Assert.assertEquals(true, point.bool);
Assert.assertEquals("test", point.string);
Assert.assertTrue(point.object.matches("java.lang.Object@[a-z0-9]+"));
}

@Test
public void testEqualsAndHashCode() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really necessary? Did you create your own hashcode/equals implementation or you are using the one provided by your IDE?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a test for hashcode and equals in Query. So I just did it for BoundParameterQuery, too.

String stringA0 = "SELECT * FROM foobar WHERE a = $a";
String stringA1 = "SELECT * FROM foobar WHERE a = $a";
String stringB0 = "SELECT * FROM foobar WHERE b = $b";

Query queryA0 = QueryBuilder.newQuery(stringA0)
.forDatabase(stringA0)
.bind("a", 0)
.create();
Query queryA1 = QueryBuilder.newQuery(stringA1)
.forDatabase(stringA1)
.bind("a", 0)
.create();
Query queryA2 = QueryBuilder.newQuery(stringA1)
.forDatabase(stringA1)
.bind("a", 10)
.create();
Query queryB0 = QueryBuilder.newQuery(stringB0)
.forDatabase(stringB0)
.bind("b", 10)
.create();

assertThat(queryA0).isEqualTo(queryA0);
assertThat(queryA0).isEqualTo(queryA1);
assertThat(queryA0).isNotEqualTo(queryA2);
assertThat(queryA0).isNotEqualTo(queryB0);
assertThat(queryA0).isNotEqualTo("foobar");

assertThat(queryA0.hashCode()).isEqualTo(queryA1.hashCode());
assertThat(queryA0.hashCode()).isNotEqualTo(queryB0.hashCode());
}

private static String decode(String str) throws UnsupportedEncodingException {
return URLDecoder.decode(str, StandardCharsets.UTF_8.toString());
}

private static class Point {
int i;
double d;
String string;
Boolean bool;
String object;
}
}