|
20 | 20 | import com.google.common.base.Preconditions; |
21 | 21 | import org.apache.shardingsphere.infra.exception.core.ShardingSpherePreconditions; |
22 | 22 | import org.apache.shardingsphere.infra.exception.generic.UnsupportedSQLOperationException; |
| 23 | +import org.checkerframework.checker.nullness.qual.Nullable; |
| 24 | +import org.postgresql.core.Parser; |
23 | 25 |
|
| 26 | + |
| 27 | +import java.lang.reflect.Array; |
| 28 | +import java.math.BigDecimal; |
24 | 29 | import java.nio.charset.StandardCharsets; |
25 | | -import java.util.Arrays; |
26 | | -import java.util.Collection; |
| 30 | +import java.util.*; |
27 | 31 | import java.util.stream.Collectors; |
28 | 32 |
|
| 33 | +import static org.postgresql.util.internal.Nullness.castNonNull; |
| 34 | + |
29 | 35 | /** |
30 | 36 | * PostgreSQL array parameter decoder. |
31 | 37 | */ |
@@ -187,4 +193,202 @@ private static String decodeElementText(final String element) { |
187 | 193 | } |
188 | 194 | return result; |
189 | 195 | } |
| 196 | + |
| 197 | + |
| 198 | + |
| 199 | + public Object decodeNumberArray(String parameterValue) { |
| 200 | + |
| 201 | + PgDimensionsArrayList list = decodeFromString(parameterValue, ','); |
| 202 | + int dims = list.dimensionsCount; |
| 203 | + final int[] dimensionLengths = new int[dims]; |
| 204 | + dimensionLengths[0] = list.size(); |
| 205 | + |
| 206 | +// find first non-null list |
| 207 | + |
| 208 | + for (int i = 1; i < dims; i++) { |
| 209 | + List tmpList = (List) list.get(0); |
| 210 | + dimensionLengths[i] = castNonNull(tmpList, "first element of adjustedList is null").size(); |
| 211 | + if (i != dims - 1) { |
| 212 | + tmpList = (List) tmpList.get(0); |
| 213 | + } |
| 214 | + } |
| 215 | + Object[] array = (Object[]) Array.newInstance(Number.class, dimensionLengths); |
| 216 | + if (array instanceof Number[]){ |
| 217 | + parserNumber((Number[]) array,list); |
| 218 | + }else { |
| 219 | + storeStringValues(array,list,dimensionLengths,0); |
| 220 | + } |
| 221 | + return array; |
| 222 | + } |
| 223 | + |
| 224 | + private static void storeStringValues(Object[] array, List list, int [] dimensionLengths, |
| 225 | + int dim) { |
| 226 | + |
| 227 | + for (int i = 0; i < dimensionLengths[dim]; i++) { |
| 228 | + Object element = castNonNull(list.get(i), "list.get(i)"); |
| 229 | + if (dim == dimensionLengths.length - 2) { |
| 230 | + parserNumber((Number[]) array[i],(List) element); |
| 231 | + } else { |
| 232 | + storeStringValues((Object[]) array[i], (List) element, dimensionLengths, dim + 1); |
| 233 | + } |
| 234 | + } |
| 235 | + } |
| 236 | + |
| 237 | + private static void parserNumber(Number[] target, List source) { |
| 238 | + for (int i = 0; i < target.length; i++) { |
| 239 | + Object o = source.get(i); |
| 240 | + if (o== null){ |
| 241 | + continue; |
| 242 | + } |
| 243 | + target[i] = parseNumber(o.toString()); |
| 244 | + } |
| 245 | + } |
| 246 | + |
| 247 | + |
| 248 | + private static Number parseNumber(String each) { |
| 249 | + if (each.startsWith("\"") && each.endsWith("\"") && each.length() > 2) { |
| 250 | + each = each.substring(1, each.length() - 1); |
| 251 | + } |
| 252 | + if (Double.toString(Double.NaN).equals(each)) { |
| 253 | + return Double.NaN; |
| 254 | + } |
| 255 | + if (Double.toString(Double.POSITIVE_INFINITY).equals(each)) { |
| 256 | + return Double.POSITIVE_INFINITY; |
| 257 | + } |
| 258 | + if (Double.toString(Double.NEGATIVE_INFINITY).equals(each)) { |
| 259 | + return Double.POSITIVE_INFINITY; |
| 260 | + } |
| 261 | + return new BigDecimal(each); |
| 262 | + } |
| 263 | + |
| 264 | + static final class PgDimensionsArrayList extends ArrayList<@Nullable Object> { |
| 265 | + |
| 266 | + private static final long serialVersionUID = 1L; |
| 267 | + |
| 268 | + /** |
| 269 | + * How many dimensions. |
| 270 | + */ |
| 271 | + int dimensionsCount = 1; |
| 272 | + |
| 273 | + } |
| 274 | + |
| 275 | + public PgDimensionsArrayList decodeFromString(String fieldString, char delim) { |
| 276 | + |
| 277 | + final PgDimensionsArrayList arrayList = new PgDimensionsArrayList(); |
| 278 | + |
| 279 | + if (fieldString == null) { |
| 280 | + return arrayList; |
| 281 | + } |
| 282 | + |
| 283 | + final char[] chars = fieldString.toCharArray(); |
| 284 | + StringBuilder buffer = null; |
| 285 | + boolean insideString = false; |
| 286 | + |
| 287 | + // needed for checking if NULL value occurred |
| 288 | + boolean wasInsideString = false; |
| 289 | + |
| 290 | + // array dimension arrays |
| 291 | + final List<PgDimensionsArrayList> dims = new ArrayList<>(); |
| 292 | + |
| 293 | + // currently processed array |
| 294 | + PgDimensionsArrayList curArray = arrayList; |
| 295 | + |
| 296 | + // Starting with 8.0 non-standard (beginning index |
| 297 | + // isn't 1) bounds the dimensions are returned in the |
| 298 | + // data formatted like so "[0:3]={0,1,2,3,4}". |
| 299 | + // Older versions simply do not return the bounds. |
| 300 | + // |
| 301 | + // Right now we ignore these bounds, but we could |
| 302 | + // consider allowing these index values to be used |
| 303 | + // even though the JDBC spec says 1 is the first |
| 304 | + // index. I'm not sure what a client would like |
| 305 | + // to see, so we just retain the old behavior. |
| 306 | + int startOffset = 0; |
| 307 | + { |
| 308 | + if (chars[0] == '[') { |
| 309 | + while (chars[startOffset] != '=') { |
| 310 | + startOffset++; |
| 311 | + } |
| 312 | + startOffset++; // skip = |
| 313 | + } |
| 314 | + } |
| 315 | + |
| 316 | + for (int i = startOffset; i < chars.length; i++) { |
| 317 | + |
| 318 | + // escape character that we need to skip |
| 319 | + if (chars[i] == '\\') { |
| 320 | + i++; |
| 321 | + } else if (!insideString && chars[i] == '{') { |
| 322 | + // subarray start |
| 323 | + if (dims.isEmpty()) { |
| 324 | + dims.add(arrayList); |
| 325 | + } else { |
| 326 | + PgDimensionsArrayList a = new PgDimensionsArrayList(); |
| 327 | + PgDimensionsArrayList p = dims.get(dims.size() - 1); |
| 328 | + p.add(a); |
| 329 | + dims.add(a); |
| 330 | + } |
| 331 | + curArray = dims.get(dims.size() - 1); |
| 332 | + |
| 333 | + // number of dimensions |
| 334 | + { |
| 335 | + for (int t = i + 1; t < chars.length; t++) { |
| 336 | + if (Character.isWhitespace(chars[t])) { |
| 337 | + continue; |
| 338 | + } else if (chars[t] == '{') { |
| 339 | + curArray.dimensionsCount++; |
| 340 | + } else { |
| 341 | + break; |
| 342 | + } |
| 343 | + } |
| 344 | + } |
| 345 | + |
| 346 | + buffer = new StringBuilder(); |
| 347 | + continue; |
| 348 | + } else if (chars[i] == '"') { |
| 349 | + // quoted element |
| 350 | + insideString = !insideString; |
| 351 | + wasInsideString = true; |
| 352 | + continue; |
| 353 | + } else if (!insideString && Parser.isArrayWhiteSpace(chars[i])) { |
| 354 | + // white space |
| 355 | + continue; |
| 356 | + } else if ((!insideString && (chars[i] == delim || chars[i] == '}')) || i == chars.length - 1) { |
| 357 | + // array end or element end |
| 358 | + // when character that is a part of array element |
| 359 | + if (chars[i] != '"' && chars[i] != '}' && chars[i] != delim && buffer != null) { |
| 360 | + buffer.append(chars[i]); |
| 361 | + } |
| 362 | + |
| 363 | + String b = buffer == null ? null : buffer.toString(); |
| 364 | + |
| 365 | + // add element to current array |
| 366 | + if (b != null && (!b.isEmpty() || wasInsideString)) { |
| 367 | + curArray.add(!wasInsideString && "NULL".equals(b) ? null : b); |
| 368 | + } |
| 369 | + |
| 370 | + wasInsideString = false; |
| 371 | + buffer = new StringBuilder(); |
| 372 | + |
| 373 | + // when end of an array |
| 374 | + if (chars[i] == '}') { |
| 375 | + dims.remove(dims.size() - 1); |
| 376 | + |
| 377 | + // when multi-dimension |
| 378 | + if (!dims.isEmpty()) { |
| 379 | + curArray = dims.get(dims.size() - 1); |
| 380 | + } |
| 381 | + |
| 382 | + buffer = null; |
| 383 | + } |
| 384 | + |
| 385 | + continue; |
| 386 | + } |
| 387 | + |
| 388 | + if (buffer != null) { |
| 389 | + buffer.append(chars[i]); |
| 390 | + } |
| 391 | + } |
| 392 | + return arrayList; |
| 393 | + } |
190 | 394 | } |
0 commit comments