-
Notifications
You must be signed in to change notification settings - Fork 478
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
Conversation
I added a new query class BoundParameterQuery, which can be used for "prepared statements". The constructor accepts a InfluxQL expression with placeholders, a DB name and a varags list to bind the parameters to the placeholders. I also extended the InfluxDBService, so that the HTTP requests contain the "params" parameter. The InfluxDBImpl now differentiates between a Query and a BoundParameterQuery. This is not the cleanest solution, because this has to be done at a few locations, but I didn't want to change too much of the code.
Codecov Report
@@ Coverage Diff @@
## master #429 +/- ##
============================================
+ Coverage 84.98% 85.78% +0.79%
- Complexity 274 291 +17
============================================
Files 19 20 +1
Lines 1192 1259 +67
Branches 121 130 +9
============================================
+ Hits 1013 1080 +67
Misses 118 118
Partials 61 61
Continue to review full report at Codecov.
|
Hi @hampelratte thanks for the effort, we always appreciate contribution. I think this is a good starting Point, but i would like to have a fluent API without a very wide Constructor. I can think of something like: query = BoundParameterQuery("SELECT * FROM abc WHERE a > $a AND b < $b")
.with("a",0)
.with("b",10)
.create() |
This Builder avoid very long constructor calls and also makes it more obvious, which value is bound to which placeholder.
Removed bind method from BoundParameterQuery, because this functionality is now implemented in the QueryBuilder
Fixed checkstyle issues and formatted according to project standard (hopefully)
Added a test which executes a BoundParameterQuery against an actual database.
} | ||
|
||
private void assurePlaceholdersAreBound(final List<String> placeholders, final Map<String, Object> params) { | ||
if (placeholders.size() != params.size()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check will cause a valid query to fail. For example:
BoundParameterQuery query = QueryBuilder.newQuery("SELECT * FROM cpu WHERE atag = $mytag ; SELECT * FROM cpu WHERE atag != $mytag")
.forDatabase(dbName)
.bind("mytag", "test")
.create();
Is there any reason to have it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Relevant code has been removed
for (Entry<String, Object> pair : parameterMap.entrySet()) { | ||
String name = pair.getKey(); | ||
Object value = pair.getValue(); | ||
if (value instanceof Number) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
} else if (value instanceof Boolean) { | ||
writer.name(name).value((Boolean) value); | ||
} else { | ||
writer.name(name).value(value.toString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may cause a NullPointerException
.
I would change the logic here to:
if (... Number) {
//...
} else if (... Boolean) {
//...
} else {
writer.name(name).value(String.valueOf(value));
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
||
private List<String> parsePlaceHolders(final String command) { | ||
List<String> placeHolders = new ArrayList<>(); | ||
Pattern p = Pattern.compile("\\s+\\$(\\w+?)(?:\\s|$)"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pattern is immutable and should be declared as class scoped static final.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Relevant code has been removed
} | ||
|
||
@Test | ||
public void testEqualsAndHashCode() { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
.bind("b", 10) | ||
.create(); | ||
Map<String, Object> params = readObject(decode(query.getParameterJsonWithUrlEncoded())); | ||
Assert.assertEquals(2, params.size()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really appreciate your detailed and extensive test but I think it's too much: you are testing multiple times the same logic over and over.
Could you please test query.getParameterJsonWithUrlEncoded()
only once, covering all data types supported by InfluxDB?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Relevant code has been removed
|
||
public String getParameterJsonWithUrlEncoded() { | ||
try { | ||
List<String> placeholders = parsePlaceHolders(getCommand()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parsePlaceHolders
is only used to create a list for assurePlaceholdersAreBound
.
Could you please move parsePlaceHolders
to be called from inside assurePlaceholdersAreBound
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Relevant code has been removed
|
||
for (String placeholder : placeholders) { | ||
if (params.get(placeholder) == null) { | ||
throw new RuntimeException("Placeholder $" + placeholder + " is not bound"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally I don't like to throw RuntimeException
s (check why here https://wiki.sei.cmu.edu/confluence/display/java/ERR07-J.+Do+not+throw+RuntimeException%2C+Exception%2C+or+Throwable ).
Also, this exception will be thrown only when the query is about to be executed instead of the moment when I'm executing the .create()
from the Builder. Would be possible to move it to the builder?
Another question: what about throwing IllegalStateException
from the .create()
method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Relevant code has been removed
@hampelratte and before I forget, thanks for your contribution. :) |
@fmachado Good point. I was thinking, that a check for the placeholders might be a good thing, but obviously InfluxDB does that already and returns reasonable error messages. So there is no reason to do that in the connector. I'm going to remove that and as a result the majority of your annotations will get resolved, too. |
The checks in BoundParameterQuery have been removed, because InfluxDB does that already and returns appropriate error messages in case of an invalid request.
@majst01 do you want to take a look? |
@hampelratte could you please update README.md with an explanation and example of this new feature? Also, would be nice to have a link to the this PR on CHANGELOG.md under a new section "2.10 [unreleased]". |
Hi, |
Added short paragraph in README, which describes the parameter binding for queries. Also added an entry for the parameter binding pull request to the changelog.
Hi @majst01 ! We're eagerly awaiting the 2.10 release for this very feature. Do you have an estimated release date by chance? |
I just added this to our project -- Thanks! It did require that I include OKIO (https://github.com/square/okio) as a dependency. Trying to query against an influxDB instance threw a Buffer exception without it. Easy enough to resolve but it might be worth adding it on your side or updating your documentation. On the documentation topic, you still list 2.9 as your latest version for the maven dependency configuration in your docs. |
Hi, |
This is a simple implementation for "prepared statements" (#274) using regular expressions.
I added a new query class BoundParameterQuery, which can be used for
"prepared statements". The constructor accepts an InfluxQL expression
with placeholders, a DB name and a varags list to bind the parameters to
the placeholders. I also extended the InfluxDBService, so that the HTTP
requests contain the "params" parameter. The InfluxDBImpl now
differentiates between a Query and a BoundParameterQuery. This is not
the cleanest solution, because this had to be done at a few locations,
but I didn't want to change too much of the code.
It has limitations, because it does not use a parser/grammar for InfluxQL. So you could for example have a placeholder in a string value of a field/tag and this implementation would fail, but at least it is a start and better than risking injections.
Feel free to delete this pull request, if you don't like it at all ;)