Skip to content

Add support for parameter binding to built queries #1010

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

Merged
merged 3 commits into from
Mar 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions QUERY_BUILDER.md
Original file line number Diff line number Diff line change
Expand Up @@ -588,3 +588,19 @@ Query select = select().raw("an expression on select").from(dbName, "cpu").where
```sqlite-psql
SELECT an expression on select FROM h2o_feet WHERE an expression as condition;
```

Binding parameters

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).
You can create queries with parameter binding:

```java
Query query = select().from(DATABASE,"h2o_feet").where(gt("water_level", FunctionFactory.placeholder("level")))
.bindParameter("level", 8);
```

```sqlite-psql
SELECT * FROM h2o_feet WHERE water_level > $level;
```

The values of bindParameter() calls are bound to the placeholders in the query (`level`).
72 changes: 2 additions & 70 deletions src/main/java/org/influxdb/dto/BoundParameterQuery.java
Original file line number Diff line number Diff line change
@@ -1,77 +1,9 @@
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) {
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;
super(command, database);
}

public static class QueryBuilder {
Expand All @@ -93,7 +25,7 @@ public QueryBuilder bind(final String placeholder, final Object value) {
if (query == null) {
query = new BoundParameterQuery(influxQL, null);
}
query.params.put(placeholder, value);
query.bindParameter(placeholder, value);
return this;
}

Expand Down
98 changes: 70 additions & 28 deletions src/main/java/org/influxdb/dto/Query.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
package org.influxdb.dto;

import com.squareup.moshi.JsonWriter;
import okio.Buffer;
import org.influxdb.InfluxDBIOException;
import org.influxdb.querybuilder.Appendable;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

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

/**
* @param command the query command
Expand Down Expand Up @@ -68,38 +79,43 @@ public boolean requiresPost() {
return requiresPost;
}

@SuppressWarnings("checkstyle:avoidinlineconditionals")
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((command == null) ? 0 : command.hashCode());
result = prime * result
+ ((database == null) ? 0 : database.hashCode());
return result;
public Query bindParameter(final String placeholder, final Object value) {
params.put(placeholder, value);
return this;
}

public boolean hasBoundParameters() {
return !params.isEmpty();
}

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

@SuppressWarnings("checkstyle:needbraces")
@Override
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Query other = (Query) obj;
if (command == null) {
if (other.command != null)
return false;
} else if (!command.equals(other.command))
public boolean equals(final Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
if (database == null) {
if (other.database != null)
return false;
} else if (!database.equals(other.database))
return false;
return true;
}

Query query = (Query) o;
return Objects.equals(command, query.command) && Objects.equals(database, query.database) && params.equals(
query.params);
}

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

/**
Expand All @@ -115,4 +131,30 @@ public static String encode(final String command) {
throw new IllegalStateException("Every JRE must support UTF-8", e);
}
}

private String createJsonObject(final Map<String, Object> parameterMap) throws IOException {
Buffer b = new Buffer();
JsonWriter writer = JsonWriter.of(b);
writer.beginObject();
for (Map.Entry<String, Object> pair : parameterMap.entrySet()) {
String name = pair.getKey();
Object value = pair.getValue();
if (value instanceof Number) {
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 if (value instanceof Appendable) {
StringBuilder stringBuilder = new StringBuilder();
((Appendable) value).appendTo(stringBuilder);
writer.name(name).value(stringBuilder.toString());
} else {
writer.name(name).value(String.valueOf(value));
}
}
writer.endObject();
return b.readString(Charset.forName("utf-8"));
}
}
44 changes: 27 additions & 17 deletions src/main/java/org/influxdb/impl/InfluxDBImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
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 @@ -637,13 +636,17 @@ public void query(final Query query, final int chunkSize, final BiConsumer<Cance
public void query(final Query query, final int chunkSize, final BiConsumer<Cancellable, QueryResult> onNext,
final Runnable onComplete, final Consumer<Throwable> onFailure) {
Call<ResponseBody> call;
if (query instanceof BoundParameterQuery) {
BoundParameterQuery boundParameterQuery = (BoundParameterQuery) query;
call = this.influxDBService.query(getDatabase(query), query.getCommandWithUrlEncoded(), chunkSize,
boundParameterQuery.getParameterJsonWithUrlEncoded());
if (query.hasBoundParameters()) {
if (query.requiresPost()) {
call = this.influxDBService.postQuery(getDatabase(query), query.getCommandWithUrlEncoded(), chunkSize,
query.getParameterJsonWithUrlEncoded());
} else {
call = this.influxDBService.query(getDatabase(query), query.getCommandWithUrlEncoded(), chunkSize,
query.getParameterJsonWithUrlEncoded());
}
} else {
if (query.requiresPost()) {
call = this.influxDBService.query(getDatabase(query), query.getCommandWithUrlEncoded(), chunkSize, null);
call = this.influxDBService.postQuery(getDatabase(query), query.getCommandWithUrlEncoded(), chunkSize);
} else {
call = this.influxDBService.query(getDatabase(query), query.getCommandWithUrlEncoded(), chunkSize);
}
Expand Down Expand Up @@ -716,18 +719,21 @@ public void onFailure(final Call<ResponseBody> call, final Throwable t) {
@Override
public QueryResult query(final Query query, final TimeUnit timeUnit) {
Call<QueryResult> call;
if (query instanceof BoundParameterQuery) {
BoundParameterQuery boundParameterQuery = (BoundParameterQuery) query;
call = this.influxDBService.query(getDatabase(query),
TimeUtil.toTimePrecision(timeUnit), query.getCommandWithUrlEncoded(),
boundParameterQuery.getParameterJsonWithUrlEncoded());
if (query.hasBoundParameters()) {
if (query.requiresPost()) {
call = this.influxDBService.postQuery(getDatabase(query), TimeUtil.toTimePrecision(timeUnit),
query.getCommandWithUrlEncoded(), query.getParameterJsonWithUrlEncoded());
} else {
call = this.influxDBService.query(getDatabase(query), TimeUtil.toTimePrecision(timeUnit),
query.getCommandWithUrlEncoded(), query.getParameterJsonWithUrlEncoded());
}
} else {
if (query.requiresPost()) {
call = this.influxDBService.query(getDatabase(query),
TimeUtil.toTimePrecision(timeUnit), query.getCommandWithUrlEncoded(), null);
call = this.influxDBService.postQuery(getDatabase(query),
TimeUtil.toTimePrecision(timeUnit), query.getCommandWithUrlEncoded());
} else {
call = this.influxDBService.query(getDatabase(query),
TimeUtil.toTimePrecision(timeUnit), query.getCommandWithUrlEncoded());
TimeUtil.toTimePrecision(timeUnit), query.getCommandWithUrlEncoded(), null);
}
}
return executeQuery(call);
Expand Down Expand Up @@ -788,10 +794,14 @@ public boolean databaseExists(final String name) {
*/
private Call<QueryResult> callQuery(final Query query) {
Call<QueryResult> call;
if (query instanceof BoundParameterQuery) {
BoundParameterQuery boundParameterQuery = (BoundParameterQuery) query;
if (query.hasBoundParameters()) {
if (query.requiresPost()) {
call = this.influxDBService.postQuery(getDatabase(query), query.getCommandWithUrlEncoded(),
boundParameterQuery.getParameterJsonWithUrlEncoded());
query.getParameterJsonWithUrlEncoded());
} else {
call = this.influxDBService.query(getDatabase(query), null, query.getCommandWithUrlEncoded(),
query.getParameterJsonWithUrlEncoded());
}
} else {
if (query.requiresPost()) {
call = this.influxDBService.postQuery(getDatabase(query), query.getCommandWithUrlEncoded());
Expand Down
31 changes: 21 additions & 10 deletions src/main/java/org/influxdb/impl/InfluxDBService.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,7 @@ public Call<ResponseBody> writePoints(@Query(DB) String database,

@GET("query")
public Call<QueryResult> query(@Query(DB) String db,
@Query(EPOCH) String epoch, @Query(value = Q, encoded = true) String query);

@POST("query")
@FormUrlEncoded
public Call<QueryResult> query(@Query(DB) String db,
@Query(EPOCH) String epoch, @Field(value = Q, encoded = true) String query,
@Query(EPOCH) String epoch, @Query(value = Q, encoded = true) String query,
@Query(value = PARAMS, encoded = true) String params);

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

@POST("query")
@FormUrlEncoded
public Call<QueryResult> postQuery(@Query(DB) String db,
public Call<QueryResult> postQuery(@Query(DB) String db, @Query(EPOCH) String epoch,
@Field(value = Q, encoded = true) String query);

@POST("query")
@FormUrlEncoded
public Call<QueryResult> postQuery(@Query(DB) String db, @Query(EPOCH) String epoch,
@Field(value = Q, encoded = true) String query, @Query(value = PARAMS, encoded = true) String params);

@Streaming
@POST("query?chunked=true")
@FormUrlEncoded
public Call<ResponseBody> postQuery(@Query(DB) String db, @Field(value = Q, encoded = true) String query,
@Query(CHUNK_SIZE) int chunkSize);

@Streaming
@POST("query?chunked=true")
@FormUrlEncoded
public Call<ResponseBody> postQuery(@Query(DB) String db, @Field(value = Q, encoded = true) String query,
@Query(CHUNK_SIZE) int chunkSize, @Query(value = PARAMS, encoded = true) String params);

@POST("query")
@FormUrlEncoded
public Call<QueryResult> postQuery(@Field(value = Q, encoded = true) String query);
Expand All @@ -79,8 +91,7 @@ public Call<ResponseBody> query(@Query(DB) String db, @Query(value = Q, encoded
@Query(CHUNK_SIZE) int chunkSize);

@Streaming
@POST("query?chunked=true")
@FormUrlEncoded
public Call<ResponseBody> query(@Query(DB) String db, @Field(value = Q, encoded = true) String query,
@GET("query?chunked=true")
public Call<ResponseBody> query(@Query(DB) String db, @Query(value = Q, encoded = true) String query,
@Query(CHUNK_SIZE) int chunkSize, @Query(value = PARAMS, encoded = true) String params);
}
2 changes: 2 additions & 0 deletions src/main/java/org/influxdb/querybuilder/Appender.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ public static StringBuilder appendValue(final Object value, final StringBuilder
stringBuilder.append(')');
} else if (value instanceof Column) {
appendName(((Column) value).getName(), stringBuilder);
} else if (value instanceof Placeholder) {
stringBuilder.append('$').append(((Placeholder) value).getName());
} else if (value instanceof String) {
stringBuilder.append("'").append(value).append("'");
} else if (value != null) {
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/org/influxdb/querybuilder/FunctionFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ public static Object column(final String name) {
return new Column(name);
}

public static Object placeholder(final String name) {
return new Placeholder(name);
}

private static void convertToColumns(final Object... arguments) {
for (int i = 0; i < arguments.length; i++) {
arguments[i] = convertToColumn(arguments[i]);
Expand Down
Loading
Loading