Skip to content

Commit 5b99621

Browse files
committed
chore: rewrite InfluxQLQueryApiImpl.parseTags to handle escaped equals and backslashes.
1 parent bab2453 commit 5b99621

2 files changed

Lines changed: 79 additions & 40 deletions

File tree

client/src/main/java/com/influxdb/client/internal/InfluxQLQueryApiImpl.java

Lines changed: 70 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -180,50 +180,82 @@ static InfluxQLQueryResult readInfluxQLResult(
180180
return new InfluxQLQueryResult(results);
181181
}
182182

183-
private static int indexOfUnescapedChar(@Nonnull final String str, final char ch) {
184-
char[] chars = str.toCharArray();
185-
for (int i = 1; i < chars.length; i++) { // ignore first value
186-
if (chars[i] == ch && chars[i - 1] != '\\') {
187-
return i;
188-
}
189-
}
190-
return -1;
191-
}
192-
193-
/*
194-
This works on the principle that the copula '=' is the _governing verb_ of any key to value
195-
expression. So parsing begins based on the verb ('=') not on the assumed expression termination
196-
character (','). The Left and right values of the split based on ('=') are collected and checked for
197-
the correct statement terminator (an unescaped ','). Any value on the left of an unescaped ',' is a
198-
value. Any value on the right is a key.
199-
*/
200183
private static Map<String, String> parseTags(@Nonnull final String value) {
201184
final Map<String, String> tags = new HashMap<>();
202-
if (!value.isEmpty()) {
203-
String[] chunks = value.split("=");
204-
String currentKey = "";
205-
String currentValue = "";
206-
String nextKey = "";
207-
for (int i = 0; i < chunks.length; i++) {
208-
if (i == 0) { // first element will be a key on its own.
209-
nextKey = chunks[i];
210-
} else if (i == chunks.length - 1) { // the last element will be a value on its own.
211-
currentValue = chunks[i];
212-
} else { // check for legitimate keys and values
213-
int commaIndex = indexOfUnescapedChar(chunks[i], ',');
214-
if (commaIndex != -1) {
215-
currentValue = chunks[i].substring(0, commaIndex);
216-
nextKey = chunks[i].substring(commaIndex + 1);
217-
}
185+
if (value.isEmpty()) {
186+
return tags;
187+
}
188+
189+
StringBuilder currentKey = new StringBuilder();
190+
StringBuilder currentValue = new StringBuilder();
191+
boolean inValue = false;
192+
boolean escaped = false;
193+
194+
for (int i = 0; i < value.length(); i++) {
195+
char c = value.charAt(i);
196+
197+
if (escaped) {
198+
// current character is escaped - treat it as a literal
199+
if (inValue) {
200+
currentValue.append(c);
201+
} else {
202+
currentKey.append(c);
218203
}
219-
if (i > 0) {
220-
// be sure to surround keys and values containing escapes with double quotes
204+
escaped = false;
205+
continue;
206+
}
207+
208+
if (c == '\\') {
209+
// start escape sequence
210+
// preserve escape character
211+
if (inValue) {
212+
currentValue.append(c);
213+
} else {
214+
currentKey.append(c);
215+
}
216+
escaped = true;
217+
continue;
218+
}
219+
220+
if (!inValue && c == '=') {
221+
// unescaped '=' marks copula
222+
inValue = true;
223+
continue;
224+
}
225+
226+
if (inValue && c == ',') {
227+
// unescaped comma separates key value pairs
228+
// finalize
229+
String key = currentKey.toString();
230+
String val = currentValue.toString();
231+
if (!key.isEmpty()) {
221232
tags.put(
222-
currentKey.contains("\\") ? "\"" + currentKey + "\"" : currentKey,
223-
currentValue.contains("\\") ? "\"" + currentValue + "\"" : currentValue
233+
key.contains("\\") ? "\"" + key + "\"" : key,
234+
val.contains("\\") ? "\"" + val + "\"" : val
224235
);
225236
}
226-
currentKey = nextKey;
237+
currentKey.setLength(0);
238+
currentValue.setLength(0);
239+
inValue = false;
240+
continue;
241+
}
242+
243+
if (inValue) {
244+
currentValue.append(c);
245+
} else {
246+
currentKey.append(c);
247+
}
248+
}
249+
250+
// finalize last key/value pair if any
251+
String key = currentKey.toString();
252+
String val = currentValue.toString();
253+
if (!key.isEmpty() || inValue) {
254+
if (!key.isEmpty()) {
255+
tags.put(
256+
key.contains("\\") ? "\"" + key + "\"" : key,
257+
val.contains("\\") ? "\"" + val + "\"" : val
258+
);
227259
}
228260
}
229261

client/src/test/java/com/influxdb/client/internal/InfluxQLQueryApiImplTest.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ void readInfluxQLResultWithTagCommas() throws IOException {
7272
"model\\,\\ uin=C3PO", // tag with comma space in key
7373
"model\\,\\ uin=Droid\\, C3PO", // tag with comma space in key and value
7474
"model\\,\\ uin=Droid\\,\\ C3PO,location=Cheb\\,\\ CZ,branch=Munchen\\,\\ DE", // comma space in key and val
75-
"silly\\,long\\,tag=a\\,b\\,\\ c\\,\\ d", // multiple commas in key and value
75+
"silly\\,\\=long\\,tag=a\\,b\\\\\\,\\ c\\,\\ d", // multi commas in k and v plus escaped reserved chars
7676
"region=us\\,\\ east-1,host\\,\\ name=ser\\,\\ ver1" // legacy broken tags
7777
);
7878

@@ -125,7 +125,7 @@ void readInfluxQLResultWithTagCommas() throws IOException {
125125
// 9. multiple commas in key and value
126126
new AbstractMap.SimpleImmutableEntry<>(testTags.get(8),
127127
new HashMap<String,String>() {{
128-
put("\"silly\\,long\\,tag\"", "\"a\\,b\\,\\ c\\,\\ d\"");
128+
put("\"silly\\,\\=long\\,tag\"", "\"a\\,b\\\\\\,\\ c\\,\\ d\"");
129129
}}),
130130
// legacy broken tags
131131
new AbstractMap.SimpleImmutableEntry<>(testTags.get(9),
@@ -161,6 +161,13 @@ void readInfluxQLResultWithTagCommas() throws IOException {
161161
InfluxQLQueryResult.Series.Record valRec = s.getValues().get(0);
162162
Assertions.assertThat(valRec.getValueByKey("first")).isEqualTo(Double.valueOf("42.0"));
163163
Assertions.assertThat(valRec.getValueByKey("time")).isEqualTo(Instant.ofEpochSecond(1483225200L));
164+
} else if (index == 10) {
165+
Assertions.assertThat(s.getColumns()).containsOnlyKeys("time", "usage_user", "usage_system");
166+
InfluxQLQueryResult.Series.Record valRec = s.getValues().get(0);
167+
// No value extractor created for "cpu" series
168+
Assertions.assertThat(valRec.getValueByKey("time")).isEqualTo("1483225200");
169+
Assertions.assertThat(valRec.getValueByKey("usage_user")).isEqualTo("13.57");
170+
Assertions.assertThat(valRec.getValueByKey("usage_system")).isEqualTo("1.4");
164171
}
165172
}
166173
}

0 commit comments

Comments
 (0)