From cc92edd394529abdabfdd47699c40871ba6aa118 Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Tue, 13 Oct 2026 03:48:10 +0000 Subject: [PATCH] perf(bigquery): [T1-17] eliminate unconditional HashMap allocations and dead STRUCT pass in QueryParameterValue --- .../cloud/bigquery/QueryParameterValue.java | 102 +++++++++++------- .../java/com/google/cloud/bigquery/Range.java | 10 +- .../bigquery/QueryParameterValueTest.java | 7 ++ 3 files changed, 77 insertions(+), 42 deletions(-) diff --git a/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/QueryParameterValue.java b/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/QueryParameterValue.java index 89e7ae85bfa1..871cecb39bbb 100644 --- a/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/QueryParameterValue.java +++ b/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/QueryParameterValue.java @@ -146,8 +146,10 @@ public Builder setArrayValues(List arrayValues) { /** Sets struct values. The type must set to STRUCT. */ public Builder setStructValues(Map structValues) { - setStructTypes(ImmutableMap.copyOf(structValues)); - return setStructValuesInner(ImmutableMap.copyOf(structValues)); + ImmutableMap copiedStructValues = + ImmutableMap.copyOf(structValues); + setStructTypes(copiedStructValues); + return setStructValuesInner(copiedStructValues); } abstract Builder setStructValuesInner(Map structValues); @@ -398,7 +400,7 @@ public static QueryParameterValue array(T[] array, Class clazz) { * Creates a {@code QueryParameterValue} object with a type of ARRAY the given array element type. */ public static QueryParameterValue array(T[] array, StandardSQLTypeName type) { - List listValues = new ArrayList<>(); + List listValues = new ArrayList<>(array.length); for (T obj : array) { if (type == StandardSQLTypeName.STRUCT) { listValues.add((QueryParameterValue) obj); @@ -551,31 +553,48 @@ private static String valueToStringOrNull(T value, StandardSQLTypeName type) */ @VisibleForTesting static void validateTimestamp(String timestamp) { - // Check if the string has greater than nanosecond precision (>9 digits in fractional second) - Matcher matcher = ISO8601_TIMESTAMP_HIGH_PRECISION_PATTERN.matcher(timestamp); - if (matcher.find()) { - // Group 1 is the fractional second part of the ISO8601 string - String fraction = matcher.group(1); - // Pos 10-12 of the fractional second are guaranteed to be digits. The regex only - // matches the fraction section as long as they are digits. - if (fraction.length() > 12) { - throw new IllegalArgumentException( - "Fractional second portion of ISO8601 only supports up to picosecond (12 digits) in BigQuery"); - } + // Check if the string has greater than nanosecond precision (>9 digits in fractional second). + // Guard the regex Matcher allocation with a fast check for at least 10 digits after the dot. + if (hasHighPrecisionFraction(timestamp)) { + Matcher matcher = ISO8601_TIMESTAMP_HIGH_PRECISION_PATTERN.matcher(timestamp); + if (matcher.find()) { + // Group 1 is the fractional second part of the ISO8601 string + String fraction = matcher.group(1); + // Pos 10-12 of the fractional second are guaranteed to be digits. The regex only + // matches the fraction section as long as they are digits. + if (fraction.length() > 12) { + throw new IllegalArgumentException( + "Fractional second portion of ISO8601 only supports up to picosecond (12 digits) in BigQuery"); + } - // Replace the entire fractional second portion with just the nanosecond portion. - // The new timestamp will be validated against the JDK's DateTimeFormatter - String truncatedFraction = fraction.substring(0, 9); - timestamp = - new StringBuilder(timestamp) - .replace(matcher.start(1), matcher.end(1), truncatedFraction) - .toString(); + // Replace the entire fractional second portion with just the nanosecond portion. + // The new timestamp will be validated against the JDK's DateTimeFormatter + String truncatedFraction = fraction.substring(0, 9); + timestamp = + new StringBuilder(timestamp) + .replace(matcher.start(1), matcher.end(1), truncatedFraction) + .toString(); + } } // It is valid as long as DateTimeFormatter doesn't throw an exception checkFormat(timestamp, TIMESTAMP_VALIDATOR); } + private static boolean hasHighPrecisionFraction(String timestamp) { + int dotIndex = timestamp.indexOf('.'); + if (dotIndex < 0 || timestamp.length() - dotIndex <= 10) { + return false; + } + for (int i = dotIndex + 1; i <= dotIndex + 10; i++) { + char c = timestamp.charAt(i); + if (c < '0' || c > '9') { + return false; + } + } + return true; + } + private static void checkFormat(Object value, DateTimeFormatter formatter) { try { formatter.parse((String) value); @@ -610,16 +629,18 @@ com.google.api.services.bigquery.model.QueryParameterValue toValuePb() { } if (getType() == StandardSQLTypeName.RANGE) { RangeValue rangeValue = new RangeValue(); - if (!getRangeValues().getStart().isNull()) { + String start = getRangeValues().getStartInner(); + if (start != null) { com.google.api.services.bigquery.model.QueryParameterValue startValue = new com.google.api.services.bigquery.model.QueryParameterValue(); - startValue.setValue(getRangeValues().getStart().getStringValue()); + startValue.setValue(start); rangeValue.setStart(startValue); } - if (!getRangeValues().getEnd().isNull()) { + String end = getRangeValues().getEndInner(); + if (end != null) { com.google.api.services.bigquery.model.QueryParameterValue endValue = new com.google.api.services.bigquery.model.QueryParameterValue(); - endValue.setValue(getRangeValues().getEnd().getStringValue()); + endValue.setValue(end); rangeValue.setEnd(endValue); } valuePb.setRangeValue(rangeValue); @@ -642,7 +663,8 @@ QueryParameterType toTypePb() { } } if (getStructTypes() != null) { - List structTypes = new ArrayList<>(); + List structTypes = + new ArrayList<>(getStructTypes().size()); for (Map.Entry entry : getStructTypes().entrySet()) { QueryParameterType.StructTypes structType = new QueryParameterType.StructTypes(); structType.setName(entry.getKey()); @@ -665,7 +687,6 @@ static QueryParameterValue fromPb( com.google.api.services.bigquery.model.QueryParameterValue valuePb, QueryParameterType typePb) { Builder valueBuilder = newBuilder(); - Map parameterTypes = new HashMap<>(); StandardSQLTypeName type = StandardSQLTypeName.valueOf(typePb.getType()); valueBuilder.setType(type); if (type == StandardSQLTypeName.ARRAY) { @@ -673,7 +694,8 @@ static QueryParameterValue fromPb( if (valuePb == null || valuePb.getArrayValues() == null) { valueBuilder.setArrayValues(ImmutableList.of()); } else { - ImmutableList.Builder arrayValues = ImmutableList.builder(); + ImmutableList.Builder arrayValues = + ImmutableList.builderWithExpectedSize(valuePb.getArrayValues().size()); for (com.google.api.services.bigquery.model.QueryParameterValue elementValuePb : valuePb.getArrayValues()) { arrayValues.add(fromPb(elementValuePb, typePb.getArrayType())); @@ -681,22 +703,26 @@ static QueryParameterValue fromPb( valueBuilder.setArrayValues(arrayValues.build()); } } else if (type == StandardSQLTypeName.STRUCT) { - Map structTypes = new HashMap<>(); - for (QueryParameterType.StructTypes types : typePb.getStructTypes()) { - structTypes.put( - types.getName(), - QueryParameterValue.newBuilder() - .setType(StandardSQLTypeName.valueOf(types.getType().getType())) - .build()); - } - valueBuilder.setStructTypes(structTypes); if (valuePb == null || valuePb.getStructValues() == null) { + Map structTypes = + new HashMap<>(typePb.getStructTypes().size()); + for (QueryParameterType.StructTypes types : typePb.getStructTypes()) { + structTypes.put( + types.getName(), + QueryParameterValue.newBuilder() + .setType(StandardSQLTypeName.valueOf(types.getType().getType())) + .build()); + } + valueBuilder.setStructTypes(structTypes); valueBuilder.setStructValues(ImmutableMap.of()); } else { - Map structValues = new HashMap<>(); + Map parameterTypes = + new HashMap<>(typePb.getStructTypes().size()); for (QueryParameterType.StructTypes structType : typePb.getStructTypes()) { parameterTypes.put(structType.getName(), structType.getType()); } + Map structValues = + new HashMap<>(valuePb.getStructValues().size()); for (Map.Entry structValue : valuePb.getStructValues().entrySet()) { structValues.put( diff --git a/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Range.java b/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Range.java index 8d244fbeb069..c5af02faf839 100644 --- a/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Range.java +++ b/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Range.java @@ -48,11 +48,13 @@ public FieldValue getEnd() { /** Returns the start and end values of this range. */ public ImmutableMap getValues() { ImmutableMap.Builder result = ImmutableMap.builder(); - if (!getStart().isNull()) { - result.put("start", getStart().getStringValue()); + String start = getStartInner(); + if (start != null) { + result.put("start", start); } - if (!getEnd().isNull()) { - result.put("end", getEnd().getStringValue()); + String end = getEndInner(); + if (end != null) { + result.put("end", end); } return result.build(); } diff --git a/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/QueryParameterValueTest.java b/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/QueryParameterValueTest.java index 276234246e8a..2faf978ae364 100644 --- a/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/QueryParameterValueTest.java +++ b/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/QueryParameterValueTest.java @@ -535,6 +535,13 @@ public void testStruct() { assertThat(recordField.getType()).isEqualTo(StandardSQLTypeName.STRUCT); assertThat(recordField.getStructTypes()).isNotNull(); assertThat(recordField.getStructValues()).isNotNull(); + assertThat(recordField.getStructTypes()).isSameInstanceAs(recordField.getStructValues()); + assertThat(queryParameterValue.getStructTypes()) + .isSameInstanceAs(queryParameterValue.getStructValues()); + + QueryParameterValue nullStructValue = QueryParameterValue.fromPb(null, parameterType); + assertThat(nullStructValue.getType()).isEqualTo(StandardSQLTypeName.STRUCT); + assertThat(nullStructValue.getStructValues()).isEmpty(); } @Test