@@ -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
0 commit comments