Skip to content

Commit 99fcacc

Browse files
authored
Add support for parameter binding to built queries (#1010)
1 parent d204b8f commit 99fcacc

File tree

9 files changed

+168
-125
lines changed

9 files changed

+168
-125
lines changed

QUERY_BUILDER.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,3 +588,19 @@ Query select = select().raw("an expression on select").from(dbName, "cpu").where
588588
```sqlite-psql
589589
SELECT an expression on select FROM h2o_feet WHERE an expression as condition;
590590
```
591+
592+
Binding parameters
593+
594+
If your Query is based on user input, it is good practice to use parameter binding to avoid [injection attacks](https://en.wikipedia.org/wiki/SQL_injection).
595+
You can create queries with parameter binding:
596+
597+
```java
598+
Query query = select().from(DATABASE,"h2o_feet").where(gt("water_level", FunctionFactory.placeholder("level")))
599+
.bindParameter("level", 8);
600+
```
601+
602+
```sqlite-psql
603+
SELECT * FROM h2o_feet WHERE water_level > $level;
604+
```
605+
606+
The values of bindParameter() calls are bound to the placeholders in the query (`level`).

src/main/java/org/influxdb/dto/BoundParameterQuery.java

Lines changed: 2 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,9 @@
11
package org.influxdb.dto;
22

3-
import com.squareup.moshi.JsonWriter;
4-
import java.io.IOException;
5-
import java.nio.charset.Charset;
6-
import java.util.HashMap;
7-
import java.util.Map;
8-
import java.util.Map.Entry;
9-
10-
import org.influxdb.InfluxDBIOException;
11-
12-
import okio.Buffer;
13-
143
public final class BoundParameterQuery extends Query {
154

16-
private final Map<String, Object> params = new HashMap<>();
17-
185
private BoundParameterQuery(final String command, final String database) {
19-
super(command, database, true);
20-
}
21-
22-
public String getParameterJsonWithUrlEncoded() {
23-
try {
24-
String jsonParameterObject = createJsonObject(params);
25-
String urlEncodedJsonParameterObject = encode(jsonParameterObject);
26-
return urlEncodedJsonParameterObject;
27-
} catch (IOException e) {
28-
throw new InfluxDBIOException(e);
29-
}
30-
}
31-
32-
private String createJsonObject(final Map<String, Object> parameterMap) throws IOException {
33-
Buffer b = new Buffer();
34-
JsonWriter writer = JsonWriter.of(b);
35-
writer.beginObject();
36-
for (Entry<String, Object> pair : parameterMap.entrySet()) {
37-
String name = pair.getKey();
38-
Object value = pair.getValue();
39-
if (value instanceof Number) {
40-
Number number = (Number) value;
41-
writer.name(name).value(number);
42-
} else if (value instanceof String) {
43-
writer.name(name).value((String) value);
44-
} else if (value instanceof Boolean) {
45-
writer.name(name).value((Boolean) value);
46-
} else {
47-
writer.name(name).value(String.valueOf(value));
48-
}
49-
}
50-
writer.endObject();
51-
return b.readString(Charset.forName("utf-8"));
52-
}
53-
54-
@Override
55-
public int hashCode() {
56-
final int prime = 31;
57-
int result = super.hashCode();
58-
result = prime * result + params.hashCode();
59-
return result;
60-
}
61-
62-
@Override
63-
public boolean equals(final Object obj) {
64-
if (this == obj) {
65-
return true;
66-
}
67-
if (!super.equals(obj)) {
68-
return false;
69-
}
70-
BoundParameterQuery other = (BoundParameterQuery) obj;
71-
if (!params.equals(other.params)) {
72-
return false;
73-
}
74-
return true;
6+
super(command, database);
757
}
768

779
public static class QueryBuilder {
@@ -93,7 +25,7 @@ public QueryBuilder bind(final String placeholder, final Object value) {
9325
if (query == null) {
9426
query = new BoundParameterQuery(influxQL, null);
9527
}
96-
query.params.put(placeholder, value);
28+
query.bindParameter(placeholder, value);
9729
return this;
9830
}
9931

src/main/java/org/influxdb/dto/Query.java

Lines changed: 70 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
package org.influxdb.dto;
22

3+
import com.squareup.moshi.JsonWriter;
4+
import okio.Buffer;
5+
import org.influxdb.InfluxDBIOException;
6+
import org.influxdb.querybuilder.Appendable;
7+
8+
import java.io.IOException;
39
import java.io.UnsupportedEncodingException;
410
import java.net.URLEncoder;
11+
import java.nio.charset.Charset;
512
import java.nio.charset.StandardCharsets;
13+
import java.util.HashMap;
14+
import java.util.Map;
15+
import java.util.Objects;
616

717
/**
818
* Represents a Query against Influxdb.
@@ -15,6 +25,7 @@ public class Query {
1525
private final String command;
1626
private final String database;
1727
private final boolean requiresPost;
28+
protected final Map<String, Object> params = new HashMap<>();
1829

1930
/**
2031
* @param command the query command
@@ -68,38 +79,43 @@ public boolean requiresPost() {
6879
return requiresPost;
6980
}
7081

71-
@SuppressWarnings("checkstyle:avoidinlineconditionals")
72-
@Override
73-
public int hashCode() {
74-
final int prime = 31;
75-
int result = 1;
76-
result = prime * result + ((command == null) ? 0 : command.hashCode());
77-
result = prime * result
78-
+ ((database == null) ? 0 : database.hashCode());
79-
return result;
82+
public Query bindParameter(final String placeholder, final Object value) {
83+
params.put(placeholder, value);
84+
return this;
85+
}
86+
87+
public boolean hasBoundParameters() {
88+
return !params.isEmpty();
89+
}
90+
91+
public String getParameterJsonWithUrlEncoded() {
92+
try {
93+
String jsonParameterObject = createJsonObject(params);
94+
String urlEncodedJsonParameterObject = encode(jsonParameterObject);
95+
return urlEncodedJsonParameterObject;
96+
} catch (IOException e) {
97+
throw new InfluxDBIOException(e);
98+
}
8099
}
81100

82-
@SuppressWarnings("checkstyle:needbraces")
83101
@Override
84-
public boolean equals(final Object obj) {
85-
if (this == obj)
86-
return true;
87-
if (obj == null)
88-
return false;
89-
if (getClass() != obj.getClass())
90-
return false;
91-
Query other = (Query) obj;
92-
if (command == null) {
93-
if (other.command != null)
94-
return false;
95-
} else if (!command.equals(other.command))
102+
public boolean equals(final Object o) {
103+
if (o == null || getClass() != o.getClass()) {
96104
return false;
97-
if (database == null) {
98-
if (other.database != null)
99-
return false;
100-
} else if (!database.equals(other.database))
101-
return false;
102-
return true;
105+
}
106+
107+
Query query = (Query) o;
108+
return Objects.equals(command, query.command) && Objects.equals(database, query.database) && params.equals(
109+
query.params);
110+
}
111+
112+
@Override
113+
public int hashCode() {
114+
final int prime = 31;
115+
int result = Objects.hashCode(command);
116+
result = prime * result + Objects.hashCode(database);
117+
result = prime * result + params.hashCode();
118+
return result;
103119
}
104120

105121
/**
@@ -115,4 +131,30 @@ public static String encode(final String command) {
115131
throw new IllegalStateException("Every JRE must support UTF-8", e);
116132
}
117133
}
134+
135+
private String createJsonObject(final Map<String, Object> parameterMap) throws IOException {
136+
Buffer b = new Buffer();
137+
JsonWriter writer = JsonWriter.of(b);
138+
writer.beginObject();
139+
for (Map.Entry<String, Object> pair : parameterMap.entrySet()) {
140+
String name = pair.getKey();
141+
Object value = pair.getValue();
142+
if (value instanceof Number) {
143+
Number number = (Number) value;
144+
writer.name(name).value(number);
145+
} else if (value instanceof String) {
146+
writer.name(name).value((String) value);
147+
} else if (value instanceof Boolean) {
148+
writer.name(name).value((Boolean) value);
149+
} else if (value instanceof Appendable) {
150+
StringBuilder stringBuilder = new StringBuilder();
151+
((Appendable) value).appendTo(stringBuilder);
152+
writer.name(name).value(stringBuilder.toString());
153+
} else {
154+
writer.name(name).value(String.valueOf(value));
155+
}
156+
}
157+
writer.endObject();
158+
return b.readString(Charset.forName("utf-8"));
159+
}
118160
}

src/main/java/org/influxdb/impl/InfluxDBImpl.java

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import org.influxdb.InfluxDBException;
1717
import org.influxdb.InfluxDBIOException;
1818
import org.influxdb.dto.BatchPoints;
19-
import org.influxdb.dto.BoundParameterQuery;
2019
import org.influxdb.dto.Point;
2120
import org.influxdb.dto.Pong;
2221
import org.influxdb.dto.Query;
@@ -637,13 +636,17 @@ public void query(final Query query, final int chunkSize, final BiConsumer<Cance
637636
public void query(final Query query, final int chunkSize, final BiConsumer<Cancellable, QueryResult> onNext,
638637
final Runnable onComplete, final Consumer<Throwable> onFailure) {
639638
Call<ResponseBody> call;
640-
if (query instanceof BoundParameterQuery) {
641-
BoundParameterQuery boundParameterQuery = (BoundParameterQuery) query;
642-
call = this.influxDBService.query(getDatabase(query), query.getCommandWithUrlEncoded(), chunkSize,
643-
boundParameterQuery.getParameterJsonWithUrlEncoded());
639+
if (query.hasBoundParameters()) {
640+
if (query.requiresPost()) {
641+
call = this.influxDBService.postQuery(getDatabase(query), query.getCommandWithUrlEncoded(), chunkSize,
642+
query.getParameterJsonWithUrlEncoded());
643+
} else {
644+
call = this.influxDBService.query(getDatabase(query), query.getCommandWithUrlEncoded(), chunkSize,
645+
query.getParameterJsonWithUrlEncoded());
646+
}
644647
} else {
645648
if (query.requiresPost()) {
646-
call = this.influxDBService.query(getDatabase(query), query.getCommandWithUrlEncoded(), chunkSize, null);
649+
call = this.influxDBService.postQuery(getDatabase(query), query.getCommandWithUrlEncoded(), chunkSize);
647650
} else {
648651
call = this.influxDBService.query(getDatabase(query), query.getCommandWithUrlEncoded(), chunkSize);
649652
}
@@ -716,18 +719,21 @@ public void onFailure(final Call<ResponseBody> call, final Throwable t) {
716719
@Override
717720
public QueryResult query(final Query query, final TimeUnit timeUnit) {
718721
Call<QueryResult> call;
719-
if (query instanceof BoundParameterQuery) {
720-
BoundParameterQuery boundParameterQuery = (BoundParameterQuery) query;
721-
call = this.influxDBService.query(getDatabase(query),
722-
TimeUtil.toTimePrecision(timeUnit), query.getCommandWithUrlEncoded(),
723-
boundParameterQuery.getParameterJsonWithUrlEncoded());
722+
if (query.hasBoundParameters()) {
723+
if (query.requiresPost()) {
724+
call = this.influxDBService.postQuery(getDatabase(query), TimeUtil.toTimePrecision(timeUnit),
725+
query.getCommandWithUrlEncoded(), query.getParameterJsonWithUrlEncoded());
726+
} else {
727+
call = this.influxDBService.query(getDatabase(query), TimeUtil.toTimePrecision(timeUnit),
728+
query.getCommandWithUrlEncoded(), query.getParameterJsonWithUrlEncoded());
729+
}
724730
} else {
725731
if (query.requiresPost()) {
726-
call = this.influxDBService.query(getDatabase(query),
727-
TimeUtil.toTimePrecision(timeUnit), query.getCommandWithUrlEncoded(), null);
732+
call = this.influxDBService.postQuery(getDatabase(query),
733+
TimeUtil.toTimePrecision(timeUnit), query.getCommandWithUrlEncoded());
728734
} else {
729735
call = this.influxDBService.query(getDatabase(query),
730-
TimeUtil.toTimePrecision(timeUnit), query.getCommandWithUrlEncoded());
736+
TimeUtil.toTimePrecision(timeUnit), query.getCommandWithUrlEncoded(), null);
731737
}
732738
}
733739
return executeQuery(call);
@@ -788,10 +794,14 @@ public boolean databaseExists(final String name) {
788794
*/
789795
private Call<QueryResult> callQuery(final Query query) {
790796
Call<QueryResult> call;
791-
if (query instanceof BoundParameterQuery) {
792-
BoundParameterQuery boundParameterQuery = (BoundParameterQuery) query;
797+
if (query.hasBoundParameters()) {
798+
if (query.requiresPost()) {
793799
call = this.influxDBService.postQuery(getDatabase(query), query.getCommandWithUrlEncoded(),
794-
boundParameterQuery.getParameterJsonWithUrlEncoded());
800+
query.getParameterJsonWithUrlEncoded());
801+
} else {
802+
call = this.influxDBService.query(getDatabase(query), null, query.getCommandWithUrlEncoded(),
803+
query.getParameterJsonWithUrlEncoded());
804+
}
795805
} else {
796806
if (query.requiresPost()) {
797807
call = this.influxDBService.postQuery(getDatabase(query), query.getCommandWithUrlEncoded());

src/main/java/org/influxdb/impl/InfluxDBService.java

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,7 @@ public Call<ResponseBody> writePoints(@Query(DB) String database,
4747

4848
@GET("query")
4949
public Call<QueryResult> query(@Query(DB) String db,
50-
@Query(EPOCH) String epoch, @Query(value = Q, encoded = true) String query);
51-
52-
@POST("query")
53-
@FormUrlEncoded
54-
public Call<QueryResult> query(@Query(DB) String db,
55-
@Query(EPOCH) String epoch, @Field(value = Q, encoded = true) String query,
50+
@Query(EPOCH) String epoch, @Query(value = Q, encoded = true) String query,
5651
@Query(value = PARAMS, encoded = true) String params);
5752

5853
@GET("query")
@@ -66,9 +61,26 @@ public Call<QueryResult> postQuery(@Query(DB) String db,
6661

6762
@POST("query")
6863
@FormUrlEncoded
69-
public Call<QueryResult> postQuery(@Query(DB) String db,
64+
public Call<QueryResult> postQuery(@Query(DB) String db, @Query(EPOCH) String epoch,
65+
@Field(value = Q, encoded = true) String query);
66+
67+
@POST("query")
68+
@FormUrlEncoded
69+
public Call<QueryResult> postQuery(@Query(DB) String db, @Query(EPOCH) String epoch,
7070
@Field(value = Q, encoded = true) String query, @Query(value = PARAMS, encoded = true) String params);
7171

72+
@Streaming
73+
@POST("query?chunked=true")
74+
@FormUrlEncoded
75+
public Call<ResponseBody> postQuery(@Query(DB) String db, @Field(value = Q, encoded = true) String query,
76+
@Query(CHUNK_SIZE) int chunkSize);
77+
78+
@Streaming
79+
@POST("query?chunked=true")
80+
@FormUrlEncoded
81+
public Call<ResponseBody> postQuery(@Query(DB) String db, @Field(value = Q, encoded = true) String query,
82+
@Query(CHUNK_SIZE) int chunkSize, @Query(value = PARAMS, encoded = true) String params);
83+
7284
@POST("query")
7385
@FormUrlEncoded
7486
public Call<QueryResult> postQuery(@Field(value = Q, encoded = true) String query);
@@ -79,8 +91,7 @@ public Call<ResponseBody> query(@Query(DB) String db, @Query(value = Q, encoded
7991
@Query(CHUNK_SIZE) int chunkSize);
8092

8193
@Streaming
82-
@POST("query?chunked=true")
83-
@FormUrlEncoded
84-
public Call<ResponseBody> query(@Query(DB) String db, @Field(value = Q, encoded = true) String query,
94+
@GET("query?chunked=true")
95+
public Call<ResponseBody> query(@Query(DB) String db, @Query(value = Q, encoded = true) String query,
8596
@Query(CHUNK_SIZE) int chunkSize, @Query(value = PARAMS, encoded = true) String params);
8697
}

src/main/java/org/influxdb/querybuilder/Appender.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ public static StringBuilder appendValue(final Object value, final StringBuilder
6262
stringBuilder.append(')');
6363
} else if (value instanceof Column) {
6464
appendName(((Column) value).getName(), stringBuilder);
65+
} else if (value instanceof Placeholder) {
66+
stringBuilder.append('$').append(((Placeholder) value).getName());
6567
} else if (value instanceof String) {
6668
stringBuilder.append("'").append(value).append("'");
6769
} else if (value != null) {

src/main/java/org/influxdb/querybuilder/FunctionFactory.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ public static Object column(final String name) {
6161
return new Column(name);
6262
}
6363

64+
public static Object placeholder(final String name) {
65+
return new Placeholder(name);
66+
}
67+
6468
private static void convertToColumns(final Object... arguments) {
6569
for (int i = 0; i < arguments.length; i++) {
6670
arguments[i] = convertToColumn(arguments[i]);

0 commit comments

Comments
 (0)