From a07348c55a16fbbb021a62b268500055355a6e7b Mon Sep 17 00:00:00 2001 From: eranl <1707552+eranl@users.noreply.github.com> Date: Fri, 14 Mar 2025 21:26:08 +0200 Subject: [PATCH 1/3] Pulled up bound parameter support from BoundParameterQuery to Query, so that they can be used with built queries, and added support for that. Decoupled requiresPost from parameters, as they are unrelated. Made InfluxDBService more consistent, by making all query methods use GET, and all postQuery methods use POST. --- .../org/influxdb/dto/BoundParameterQuery.java | 72 +------------- src/main/java/org/influxdb/dto/Query.java | 98 +++++++++++++------ .../java/org/influxdb/impl/InfluxDBImpl.java | 42 +++++--- .../org/influxdb/impl/InfluxDBService.java | 29 ++++-- .../org/influxdb/querybuilder/Appender.java | 2 + .../querybuilder/FunctionFactory.java | 4 + .../influxdb/querybuilder/Placeholder.java | 14 +++ .../querybuilder/api/BuiltQueryTest.java | 12 +++ 8 files changed, 152 insertions(+), 121 deletions(-) create mode 100644 src/main/java/org/influxdb/querybuilder/Placeholder.java diff --git a/src/main/java/org/influxdb/dto/BoundParameterQuery.java b/src/main/java/org/influxdb/dto/BoundParameterQuery.java index 0c7b08b90..1f197289e 100644 --- a/src/main/java/org/influxdb/dto/BoundParameterQuery.java +++ b/src/main/java/org/influxdb/dto/BoundParameterQuery.java @@ -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 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 parameterMap) throws IOException { - Buffer b = new Buffer(); - JsonWriter writer = JsonWriter.of(b); - writer.beginObject(); - for (Entry 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 { @@ -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; } diff --git a/src/main/java/org/influxdb/dto/Query.java b/src/main/java/org/influxdb/dto/Query.java index 5c4921b8c..ebed08e7e 100644 --- a/src/main/java/org/influxdb/dto/Query.java +++ b/src/main/java/org/influxdb/dto/Query.java @@ -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. @@ -15,6 +25,7 @@ public class Query { private final String command; private final String database; private final boolean requiresPost; + protected final Map params = new HashMap<>(); /** * @param command the query command @@ -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; } /** @@ -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 parameterMap) throws IOException { + Buffer b = new Buffer(); + JsonWriter writer = JsonWriter.of(b); + writer.beginObject(); + for (Map.Entry 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")); + } } diff --git a/src/main/java/org/influxdb/impl/InfluxDBImpl.java b/src/main/java/org/influxdb/impl/InfluxDBImpl.java index 825e0708a..95e8a8d5b 100644 --- a/src/main/java/org/influxdb/impl/InfluxDBImpl.java +++ b/src/main/java/org/influxdb/impl/InfluxDBImpl.java @@ -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; @@ -637,13 +636,17 @@ public void query(final Query query, final int chunkSize, final BiConsumer onNext, final Runnable onComplete, final Consumer onFailure) { Call 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); } @@ -716,15 +719,18 @@ public void onFailure(final Call call, final Throwable t) { @Override public QueryResult query(final Query query, final TimeUnit timeUnit) { Call 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()); @@ -788,10 +794,14 @@ public boolean databaseExists(final String name) { */ private Call callQuery(final Query query) { Call 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), query.getCommandWithUrlEncoded(), + query.getParameterJsonWithUrlEncoded()); + } } else { if (query.requiresPost()) { call = this.influxDBService.postQuery(getDatabase(query), query.getCommandWithUrlEncoded()); diff --git a/src/main/java/org/influxdb/impl/InfluxDBService.java b/src/main/java/org/influxdb/impl/InfluxDBService.java index ce7a811b4..badb7b284 100644 --- a/src/main/java/org/influxdb/impl/InfluxDBService.java +++ b/src/main/java/org/influxdb/impl/InfluxDBService.java @@ -49,10 +49,9 @@ public Call writePoints(@Query(DB) String database, public Call query(@Query(DB) String db, @Query(EPOCH) String epoch, @Query(value = Q, encoded = true) String query); - @POST("query") - @FormUrlEncoded + @GET("query") public Call 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") @@ -66,9 +65,26 @@ public Call postQuery(@Query(DB) String db, @POST("query") @FormUrlEncoded - public Call postQuery(@Query(DB) String db, + public Call postQuery(@Query(DB) String db, @Query(EPOCH) String epoch, + @Field(value = Q, encoded = true) String query); + + @POST("query") + @FormUrlEncoded + public Call 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 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 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 postQuery(@Field(value = Q, encoded = true) String query); @@ -79,8 +95,7 @@ public Call query(@Query(DB) String db, @Query(value = Q, encoded @Query(CHUNK_SIZE) int chunkSize); @Streaming - @POST("query?chunked=true") - @FormUrlEncoded - public Call query(@Query(DB) String db, @Field(value = Q, encoded = true) String query, + @GET("query?chunked=true") + public Call query(@Query(DB) String db, @Query(value = Q, encoded = true) String query, @Query(CHUNK_SIZE) int chunkSize, @Query(value = PARAMS, encoded = true) String params); } diff --git a/src/main/java/org/influxdb/querybuilder/Appender.java b/src/main/java/org/influxdb/querybuilder/Appender.java index 3dab5c02f..8c7e34bfd 100644 --- a/src/main/java/org/influxdb/querybuilder/Appender.java +++ b/src/main/java/org/influxdb/querybuilder/Appender.java @@ -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) { diff --git a/src/main/java/org/influxdb/querybuilder/FunctionFactory.java b/src/main/java/org/influxdb/querybuilder/FunctionFactory.java index 19541c46a..ba5bfaba3 100644 --- a/src/main/java/org/influxdb/querybuilder/FunctionFactory.java +++ b/src/main/java/org/influxdb/querybuilder/FunctionFactory.java @@ -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]); diff --git a/src/main/java/org/influxdb/querybuilder/Placeholder.java b/src/main/java/org/influxdb/querybuilder/Placeholder.java new file mode 100644 index 000000000..8b21cd880 --- /dev/null +++ b/src/main/java/org/influxdb/querybuilder/Placeholder.java @@ -0,0 +1,14 @@ +package org.influxdb.querybuilder; + +public class Placeholder { + + private final String name; + + Placeholder(final String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/src/test/java/org/influxdb/querybuilder/api/BuiltQueryTest.java b/src/test/java/org/influxdb/querybuilder/api/BuiltQueryTest.java index 2f9565add..dfbb83062 100644 --- a/src/test/java/org/influxdb/querybuilder/api/BuiltQueryTest.java +++ b/src/test/java/org/influxdb/querybuilder/api/BuiltQueryTest.java @@ -10,10 +10,14 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.influxdb.dto.Query; +import org.influxdb.querybuilder.FunctionFactory; import org.influxdb.querybuilder.RawText; import org.influxdb.querybuilder.Where; import org.junit.jupiter.api.Test; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; +@RunWith(JUnitPlatform.class) public class BuiltQueryTest { private static final String DATABASE = "testdb"; @@ -973,4 +977,12 @@ public void multipleDatabaseBackReferenceing() { assertEquals(query.getDatabase(), select.getDatabase()); } + @Test + public void testBoundParameters() { + Query query = new Query("SELECT a FROM b WHERE c = $d;", DATABASE); + Query select = select().column("a").from(DATABASE, "b") + .where(eq("c", FunctionFactory.placeholder("d"))); + assertEquals(query.getCommand(), select.getCommand()); + assertEquals(query.getDatabase(), select.getDatabase()); + } } From 7fb8f62398caab24f566f8ecdf2d7845622c3e3b Mon Sep 17 00:00:00 2001 From: eranl <1707552+eranl@users.noreply.github.com> Date: Mon, 17 Mar 2025 01:29:25 +0200 Subject: [PATCH 2/3] Fix overloading bug --- src/main/java/org/influxdb/impl/InfluxDBImpl.java | 4 ++-- src/main/java/org/influxdb/impl/InfluxDBService.java | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/influxdb/impl/InfluxDBImpl.java b/src/main/java/org/influxdb/impl/InfluxDBImpl.java index 95e8a8d5b..23427a23d 100644 --- a/src/main/java/org/influxdb/impl/InfluxDBImpl.java +++ b/src/main/java/org/influxdb/impl/InfluxDBImpl.java @@ -733,7 +733,7 @@ public QueryResult query(final Query query, final TimeUnit timeUnit) { 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); @@ -799,7 +799,7 @@ private Call callQuery(final Query query) { call = this.influxDBService.postQuery(getDatabase(query), query.getCommandWithUrlEncoded(), query.getParameterJsonWithUrlEncoded()); } else { - call = this.influxDBService.query(getDatabase(query), query.getCommandWithUrlEncoded(), + call = this.influxDBService.query(getDatabase(query), null, query.getCommandWithUrlEncoded(), query.getParameterJsonWithUrlEncoded()); } } else { diff --git a/src/main/java/org/influxdb/impl/InfluxDBService.java b/src/main/java/org/influxdb/impl/InfluxDBService.java index badb7b284..061a76615 100644 --- a/src/main/java/org/influxdb/impl/InfluxDBService.java +++ b/src/main/java/org/influxdb/impl/InfluxDBService.java @@ -45,10 +45,6 @@ public Call writePoints(@Query(DB) String database, @Query(RP) String retentionPolicy, @Query(PRECISION) String precision, @Query(CONSISTENCY) String consistency, @Body RequestBody batchPoints); - @GET("query") - public Call query(@Query(DB) String db, - @Query(EPOCH) String epoch, @Query(value = Q, encoded = true) String query); - @GET("query") public Call query(@Query(DB) String db, @Query(EPOCH) String epoch, @Query(value = Q, encoded = true) String query, From 412b6ec472f2a4d2e79c628fd778a393b06d1604 Mon Sep 17 00:00:00 2001 From: eranl <1707552+eranl@users.noreply.github.com> Date: Mon, 17 Mar 2025 18:34:31 +0200 Subject: [PATCH 3/3] Update doc Improve test --- QUERY_BUILDER.md | 16 ++++++++++++++++ .../querybuilder/api/BuiltQueryTest.java | 10 +++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/QUERY_BUILDER.md b/QUERY_BUILDER.md index 5f500b8e4..d84e6d255 100644 --- a/QUERY_BUILDER.md +++ b/QUERY_BUILDER.md @@ -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`). diff --git a/src/test/java/org/influxdb/querybuilder/api/BuiltQueryTest.java b/src/test/java/org/influxdb/querybuilder/api/BuiltQueryTest.java index dfbb83062..1fcadfd74 100644 --- a/src/test/java/org/influxdb/querybuilder/api/BuiltQueryTest.java +++ b/src/test/java/org/influxdb/querybuilder/api/BuiltQueryTest.java @@ -979,10 +979,10 @@ public void multipleDatabaseBackReferenceing() { @Test public void testBoundParameters() { - Query query = new Query("SELECT a FROM b WHERE c = $d;", DATABASE); - Query select = select().column("a").from(DATABASE, "b") - .where(eq("c", FunctionFactory.placeholder("d"))); - assertEquals(query.getCommand(), select.getCommand()); - assertEquals(query.getDatabase(), select.getDatabase()); + Query query = select().column("a").from(DATABASE, "b") + .where(eq("c", FunctionFactory.placeholder("d"))).bindParameter("d", 3); + assertEquals("SELECT a FROM b WHERE c = $d;", query.getCommand()); + assertEquals(Query.encode("{\"d\":3}"), query.getParameterJsonWithUrlEncoded()); + assertEquals(DATABASE, query.getDatabase()); } }