diff --git a/CHANGELOG.md b/CHANGELOG.md index 50769a30..df16256c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ### Features 1. [#360](https://github.com/InfluxCommunity/influxdb3-java/pull/360): Support passing interceptors to the Flight client. +1. [#363](https://github.com/InfluxCommunity/influxdb3-java/pull/363): Support custom tag order via `tagOrder` write option. + See [Sort tags by priority](https://docs.influxdata.com/influxdb3/enterprise/write-data/best-practices/schema-design/#sort-tags-by-query-priority) for more. ## 1.8.0 [2026-02-19] diff --git a/README.md b/README.md index aa7ce6ed..7c7c6c1e 100644 --- a/README.md +++ b/README.md @@ -69,11 +69,13 @@ To start with the client, import the `com.influxdb.v3.client` package and create package com.influxdb.v3; import java.time.Instant; +import java.util.List; import java.util.stream.Stream; import com.influxdb.v3.client.InfluxDBClient; import com.influxdb.v3.client.query.QueryOptions; import com.influxdb.v3.client.Point; +import com.influxdb.v3.client.write.WriteOptions; public class IOxExample { public static void main(String[] args) throws Exception { @@ -100,6 +102,17 @@ Point point = Point.measurement("temperature") .setTimestamp(Instant.now().minusSeconds(-10)); client.writePoint(point); +WriteOptions orderedTagWrite = new WriteOptions.Builder() + .tagOrder(List.of("region", "host")) + .build(); +client.writePoint( + Point.measurement("temperature") + .setTag("host", "server-1") + .setTag("region", "eu-west") + .setField("value", 60.25), + orderedTagWrite +); + // // Write by LineProtocol // diff --git a/src/main/java/com/influxdb/v3/client/Point.java b/src/main/java/com/influxdb/v3/client/Point.java index e0c0e26c..bc907774 100644 --- a/src/main/java/com/influxdb/v3/client/Point.java +++ b/src/main/java/com/influxdb/v3/client/Point.java @@ -25,8 +25,12 @@ import java.math.BigInteger; import java.text.NumberFormat; import java.time.Instant; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; +import java.util.TreeSet; import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -530,7 +534,7 @@ public Point copy() { */ @Nonnull public String toLineProtocol() { - return toLineProtocol(null); + return toLineProtocol(null, null, null); } /** @@ -541,11 +545,26 @@ public String toLineProtocol() { */ @Nonnull public String toLineProtocol(@Nullable final WritePrecision precision) { + return toLineProtocol(precision, null, null); + } + + /** + * Transform to Line Protocol. + * + * @param precision required precision + * @param defaultTags default tags to include in point serialization + * @param tagOrder preferred order of tags in point serialization + * @return Line Protocol + */ + @Nonnull + public String toLineProtocol(@Nullable final WritePrecision precision, + @Nullable final Map defaultTags, + @Nullable final List tagOrder) { StringBuilder sb = new StringBuilder(); escapeKey(sb, getMeasurement(), false); - appendTags(sb); + appendTags(sb, defaultTags, tagOrder); boolean appendedFields = appendFields(sb); if (!appendedFields) { return ""; @@ -564,24 +583,62 @@ private Point putField(@Nonnull final String field, @Nullable final Object value return this; } - private void appendTags(@Nonnull final StringBuilder sb) { - - for (String name : values.getTagNames()) { + private void appendTags(@Nonnull final StringBuilder sb, + @Nullable final Map defaultTags, + @Nullable final List tagOrder) { + if ((defaultTags == null || defaultTags.isEmpty()) && (tagOrder == null || tagOrder.isEmpty())) { + values.forEachTag((name, value) -> appendTag(sb, name, value)); + sb.append(' '); + return; + } - String value = values.getTag(name); + Set remaining = new TreeSet<>(); + values.forEachTagName(pointTag -> { + if (!pointTag.isEmpty()) { + remaining.add(pointTag); + } + }); + if (defaultTags != null) { + for (String defaultTag : defaultTags.keySet()) { + if (defaultTag != null && !defaultTag.isEmpty()) { + remaining.add(defaultTag); + } + } + } - if (name.isEmpty() || value == null || value.isEmpty()) { - continue; + List orderedTagNames = new ArrayList<>(); + if (tagOrder != null && !tagOrder.isEmpty()) { + for (String preferredTag : tagOrder) { + if (preferredTag == null || preferredTag.isEmpty()) { + continue; + } + if (remaining.remove(preferredTag)) { + orderedTagNames.add(preferredTag); + } } + } + orderedTagNames.addAll(remaining); - sb.append(','); - escapeKey(sb, name); - sb.append('='); - escapeKey(sb, value); + for (String name : orderedTagNames) { + String value = values.getTag(name); + if (defaultTags != null && defaultTags.containsKey(name)) { + value = defaultTags.get(name); + } + appendTag(sb, name, value); } sb.append(' '); } + private void appendTag(@Nonnull final StringBuilder sb, @Nullable final String name, @Nullable final String value) { + if (name == null || name.isEmpty() || value == null || value.isEmpty()) { + return; + } + sb.append(','); + escapeKey(sb, name); + sb.append('='); + escapeKey(sb, value); + } + private boolean appendFields(@Nonnull final StringBuilder sb) { boolean appended = false; diff --git a/src/main/java/com/influxdb/v3/client/PointValues.java b/src/main/java/com/influxdb/v3/client/PointValues.java index 4b706dda..e7da4034 100644 --- a/src/main/java/com/influxdb/v3/client/PointValues.java +++ b/src/main/java/com/influxdb/v3/client/PointValues.java @@ -25,6 +25,8 @@ import java.time.Instant; import java.util.Map; import java.util.TreeMap; +import java.util.function.BiConsumer; +import java.util.function.Consumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.NotThreadSafe; @@ -226,6 +228,16 @@ public String[] getTagNames() { return tags.keySet().toArray(new String[0]); } + void forEachTag(@Nonnull final BiConsumer consumer) { + Arguments.checkNotNull(consumer, "consumer"); + tags.forEach(consumer); + } + + void forEachTagName(@Nonnull final Consumer consumer) { + Arguments.checkNotNull(consumer, "consumer"); + tags.keySet().forEach(consumer); + } + /** * Gets the float field value associated with the specified name. * If the field is not present, returns null. diff --git a/src/main/java/com/influxdb/v3/client/internal/InfluxDBClientImpl.java b/src/main/java/com/influxdb/v3/client/internal/InfluxDBClientImpl.java index 50db570a..8c2ce466 100644 --- a/src/main/java/com/influxdb/v3/client/internal/InfluxDBClientImpl.java +++ b/src/main/java/com/influxdb/v3/client/internal/InfluxDBClientImpl.java @@ -32,7 +32,6 @@ import java.util.concurrent.TimeUnit; import java.util.function.BiFunction; import java.util.logging.Logger; -import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; import java.util.zip.GZIPOutputStream; @@ -330,21 +329,29 @@ private void writeData(@Nonnull final List data, @Nonnull final WriteOpti } Map defaultTags = options.defaultTagsSafe(config); + List tagOrder = options.tagOrderSafe(); + + StringBuilder lineProtocolBuilder = new StringBuilder(); + for (T item : data) { + String line; + if (item == null) { + line = null; + } else if (item instanceof Point) { + line = ((Point) item).toLineProtocol(null, defaultTags, tagOrder); + } else { + line = item.toString(); + } + + if (line == null || line.isEmpty()) { + continue; + } - String lineProtocol = data.stream().map(item -> { - if (item == null) { - return null; - } else if (item instanceof Point) { - for (String key : defaultTags.keySet()) { - ((Point) item).setTag(key, defaultTags.get(key)); - } - return ((Point) item).toLineProtocol(); - } else { - return item.toString(); - } - }) - .filter(it -> it != null && !it.isEmpty()) - .collect(Collectors.joining("\n")); + if (lineProtocolBuilder.length() > 0) { + lineProtocolBuilder.append('\n'); + } + lineProtocolBuilder.append(line); + } + String lineProtocol = lineProtocolBuilder.toString(); if (lineProtocol.isEmpty()) { LOG.warning("No data to write, please check your input data."); @@ -353,9 +360,9 @@ private void writeData(@Nonnull final List data, @Nonnull final WriteOpti Map headers = new HashMap<>(Map.of("Content-Type", "text/plain; charset=utf-8")); byte[] body = lineProtocol.getBytes(StandardCharsets.UTF_8); - if (lineProtocol.length() >= options.gzipThresholdSafe(config)) { + if (body.length >= options.gzipThresholdSafe(config)) { try { - body = gzipData(lineProtocol.getBytes(StandardCharsets.UTF_8)); + body = gzipData(body); headers.put("Content-Encoding", "gzip"); } catch (IOException e) { throw new InfluxDBApiException(e); diff --git a/src/main/java/com/influxdb/v3/client/write/WriteOptions.java b/src/main/java/com/influxdb/v3/client/write/WriteOptions.java index ca491f1d..e9b4ca1b 100644 --- a/src/main/java/com/influxdb/v3/client/write/WriteOptions.java +++ b/src/main/java/com/influxdb/v3/client/write/WriteOptions.java @@ -22,8 +22,10 @@ package com.influxdb.v3.client.write; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; @@ -40,6 +42,7 @@ *
  • organization - specifies the organization to be used for InfluxDB operations
  • *
  • precision - specifies the precision to use for the timestamp of points
  • *
  • defaultTags - specifies tags to be added by default to all write operations using points.
  • + *
  • tagOrder - specifies preferred tag order for point serialization.
  • *
  • headers - specifies the headers to be added to write request
  • * *

    @@ -78,13 +81,14 @@ public final class WriteOptions { @Deprecated(forRemoval = true) public static final WriteOptions DEFAULTS = new WriteOptions( - null, DEFAULT_WRITE_PRECISION, DEFAULT_GZIP_THRESHOLD, DEFAULT_NO_SYNC, null, null); + null, DEFAULT_WRITE_PRECISION, DEFAULT_GZIP_THRESHOLD, DEFAULT_NO_SYNC, null, null, null); private final String database; private final WritePrecision precision; private final Integer gzipThreshold; private final Boolean noSync; private final Map defaultTags; + private final List tagOrder; private final Map headers; /** @@ -94,7 +98,8 @@ public final class WriteOptions { * compression threshold, and no specified database. */ public static WriteOptions defaultWriteOptions() { - return new WriteOptions(null, DEFAULT_WRITE_PRECISION, DEFAULT_GZIP_THRESHOLD, DEFAULT_NO_SYNC, null, null); + return new WriteOptions(null, DEFAULT_WRITE_PRECISION, DEFAULT_GZIP_THRESHOLD, DEFAULT_NO_SYNC, + null, null, null); } /** @@ -204,11 +209,40 @@ public WriteOptions(@Nullable final String database, @Nullable final Boolean noSync, @Nullable final Map defaultTags, @Nullable final Map headers) { + this(database, precision, gzipThreshold, noSync, defaultTags, headers, null); + } + + /** + * Construct WriteAPI options. + * + * @param database The database to be used for InfluxDB operations. + * If it is not specified then use {@link ClientConfig#getDatabase()}. + * @param precision The precision to use for the timestamp of points. + * If it is not specified then use {@link ClientConfig#getWritePrecision()}. + * @param gzipThreshold The threshold for compressing request body. + * If it is not specified then use {@link WriteOptions#DEFAULT_GZIP_THRESHOLD}. + * @param noSync Skip waiting for WAL persistence on write. + * If it is not specified then use {@link WriteOptions#DEFAULT_NO_SYNC}. + * @param defaultTags Default tags to be added when writing points. + * @param headers The headers to be added to write request. + * The headers specified here are preferred over the headers + * specified in the client configuration. + * @param tagOrder Preferred order of tags in line protocol serialization. + * Null or empty tag names are ignored. + */ + public WriteOptions(@Nullable final String database, + @Nullable final WritePrecision precision, + @Nullable final Integer gzipThreshold, + @Nullable final Boolean noSync, + @Nullable final Map defaultTags, + @Nullable final Map headers, + @Nullable final List tagOrder) { this.database = database; this.precision = precision; this.gzipThreshold = gzipThreshold; this.noSync = noSync; this.defaultTags = defaultTags == null ? Map.of() : defaultTags; + this.tagOrder = sanitizeTagOrder(tagOrder); this.headers = headers == null ? Map.of() : headers; } @@ -277,6 +311,14 @@ public Map headersSafe() { return headers; } + /** + * @return preferred order of tags in line protocol serialization. + */ + @Nonnull + public List tagOrderSafe() { + return tagOrder; + } + @Override public boolean equals(final Object o) { if (this == o) { @@ -291,18 +333,31 @@ public boolean equals(final Object o) { && Objects.equals(gzipThreshold, that.gzipThreshold) && Objects.equals(noSync, that.noSync) && defaultTags.equals(that.defaultTags) + && tagOrder.equals(that.tagOrder) && headers.equals(that.headers); } @Override public int hashCode() { - return Objects.hash(database, precision, gzipThreshold, noSync, defaultTags, headers); + return Objects.hash(database, precision, gzipThreshold, noSync, defaultTags, tagOrder, headers); } private boolean isNotDefined(final String option) { return option == null || option.isEmpty(); } + @Nonnull + private static List sanitizeTagOrder(@Nullable final List tagOrder) { + if (tagOrder == null || tagOrder.isEmpty()) { + return List.of(); + } + return tagOrder.stream() + .filter(Objects::nonNull) + .filter(tag -> !tag.isEmpty()) + .distinct() + .collect(Collectors.toUnmodifiableList()); + } + /** * A builder for {@code WriteOptions}. *

    @@ -314,6 +369,7 @@ public static final class Builder { private Integer gzipThreshold; private Boolean noSync; private Map defaultTags = new HashMap<>(); + private List tagOrder = List.of(); private Map headers = new HashMap<>(); /** @@ -380,6 +436,18 @@ public Builder defaultTags(@Nonnull final Map defaultTags) { return this; } + /** + * Sets preferred tag order for line protocol serialization. + * + * @param tagOrder tag order preference. Null or empty tag names are ignored. + * @return this + */ + @Nonnull + public Builder tagOrder(@Nonnull final List tagOrder) { + this.tagOrder = sanitizeTagOrder(tagOrder); + return this; + } + /** * Sets the headers. * @@ -406,6 +474,6 @@ public WriteOptions build() { private WriteOptions(@Nonnull final Builder builder) { this(builder.database, builder.precision, builder.gzipThreshold, builder.noSync, builder.defaultTags, - builder.headers); + builder.headers, builder.tagOrder); } } diff --git a/src/test/java/com/influxdb/v3/client/InfluxDBClientWriteTest.java b/src/test/java/com/influxdb/v3/client/InfluxDBClientWriteTest.java index cd4242d7..a7d31240 100644 --- a/src/test/java/com/influxdb/v3/client/InfluxDBClientWriteTest.java +++ b/src/test/java/com/influxdb/v3/client/InfluxDBClientWriteTest.java @@ -512,6 +512,25 @@ void defaultTags() throws InterruptedException { // assertThat(request.getBody().readUtf8()).isEqualTo("mem,model=M5,tag=one,unit=U2 value=1.0"); assertThat(request.getBody().utf8()).isEqualTo("mem,model=M5,tag=one,unit=U2 value=1.0"); + mockServer.enqueue(createResponse(200)); + + Point orderedPoint = Point.measurement("mem") + .setTag("host", "h1") + .setTag("unit", "point-unit") + .setField("value", 1.0); + + WriteOptions optionsWithTagOrder = new WriteOptions.Builder() + .defaultTags(defaultTags) + .tagOrder(List.of("unit", "host")) + .build(); + + client.writePoint(orderedPoint, optionsWithTagOrder); + + assertThat(mockServer.getRequestCount()).isEqualTo(2); + RecordedRequest orderedRequest = mockServer.takeRequest(); + assertThat(orderedRequest).isNotNull(); + assertThat(orderedRequest.getBody().utf8()).isEqualTo("mem,unit=U2,host=h1,model=M5 value=1.0"); + } @Test diff --git a/src/test/java/com/influxdb/v3/client/PointTest.java b/src/test/java/com/influxdb/v3/client/PointTest.java index 01d79440..254169bf 100644 --- a/src/test/java/com/influxdb/v3/client/PointTest.java +++ b/src/test/java/com/influxdb/v3/client/PointTest.java @@ -21,10 +21,12 @@ */ package com.influxdb.v3.client; -import java.math.BigInteger; -import java.time.Instant; -import java.util.HashMap; -import java.util.Map; +import java.math.BigInteger; +import java.time.Instant; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; @@ -188,14 +190,98 @@ void getFieldNames() { } @Test - void toLineProtocol() { - Point point = Point.measurement("measurement") - .setTag("tag1", "value1") - .setField("field1", 42); - - String lineProtocol = point.toLineProtocol(WritePrecision.NS); - Assertions.assertThat("measurement,tag1=value1 field1=42i").isEqualTo(lineProtocol); - } + void toLineProtocol() { + Point point = Point.measurement("measurement") + .setTag("tag1", "value1") + .setField("field1", 42); + + String lineProtocol = point.toLineProtocol(WritePrecision.NS); + Assertions.assertThat("measurement,tag1=value1 field1=42i").isEqualTo(lineProtocol); + } + + @Test + void toLineProtocolWithTagOrder() { + Point point = Point.measurement("measurement") + .setTag("host", "h1") + .setTag("region", "point-region") + .setField("field1", 42); + + String lineProtocolFastPathWithEmptyDefaults = point.toLineProtocol( + WritePrecision.NS, + Map.of(), + List.of() + ); + Assertions.assertThat("measurement,host=h1,region=point-region field1=42i") + .isEqualTo(lineProtocolFastPathWithEmptyDefaults); + + String lineProtocol = point.toLineProtocol( + WritePrecision.NS, + Map.of("region", "default-region", "rack", "r1"), + List.of("region", "host") + ); + + Assertions.assertThat("measurement,region=default-region,host=h1,rack=r1 field1=42i") + .isEqualTo(lineProtocol); + + String lineProtocolWithTagOrderOnly = point.toLineProtocol( + WritePrecision.NS, + null, + List.of("region") + ); + Assertions.assertThat("measurement,region=point-region,host=h1 field1=42i") + .isEqualTo(lineProtocolWithTagOrderOnly); + + String lineProtocolWithDefaultTagsOnly = point.toLineProtocol( + WritePrecision.NS, + Map.of("rack", "r1"), + List.of() + ); + Assertions.assertThat("measurement,host=h1,rack=r1,region=point-region field1=42i") + .isEqualTo(lineProtocolWithDefaultTagsOnly); + + Point pointWithIgnoredTags = Point.measurement("measurement") + .setTag("", "ignored") + .setTag("host", "h1") + .setTag("region", "point-region") + .setField("field1", 42); + + Map defaultTags = new HashMap<>(); + defaultTags.put("", "ignored"); + defaultTags.put(null, "ignored"); + defaultTags.put("rack", "r1"); + defaultTags.put("zone", "z1"); + + String lineProtocolWithIgnoredTagOrderEntries = pointWithIgnoredTags.toLineProtocol( + WritePrecision.NS, + defaultTags, + Arrays.asList("region", "", null, "region", "missing", "host") + ); + + Assertions.assertThat("measurement,region=point-region,host=h1,rack=r1,zone=z1 field1=42i") + .isEqualTo(lineProtocolWithIgnoredTagOrderEntries); + + Point pointWithEmptyTagValue = Point.measurement("measurement") + .setTag("host", "") + .setField("field1", 42); + String lineProtocolWithEmptyTagValue = pointWithEmptyTagValue.toLineProtocol( + WritePrecision.NS, + Map.of("rack", "r1"), + List.of("host") + ); + Assertions.assertThat("measurement,rack=r1 field1=42i") + .isEqualTo(lineProtocolWithEmptyTagValue); + + Point pointWithEmptyTagNameFastPath = Point.measurement("measurement") + .setTag("", "ignored") + .setField("field1", 42); + String lineProtocolWithEmptyTagNameFastPath = pointWithEmptyTagNameFastPath.toLineProtocol( + WritePrecision.NS, + Map.of(), + List.of() + ); + Assertions.assertThat("measurement field1=42i") + .isEqualTo(lineProtocolWithEmptyTagNameFastPath); + } @Test void copy() { diff --git a/src/test/java/com/influxdb/v3/client/write/WriteOptionsTest.java b/src/test/java/com/influxdb/v3/client/write/WriteOptionsTest.java index b63d98b4..a1b7974c 100644 --- a/src/test/java/com/influxdb/v3/client/write/WriteOptionsTest.java +++ b/src/test/java/com/influxdb/v3/client/write/WriteOptionsTest.java @@ -22,7 +22,9 @@ package com.influxdb.v3.client.write; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.assertj.core.api.Assertions; @@ -60,6 +62,26 @@ void optionsEqualAll() { .database("my-database").precision(WritePrecision.S).gzipThreshold(512).noSync(true).build(); Assertions.assertThat(options).isEqualTo(optionsViaBuilder); + + WriteOptions gzipMismatch = new WriteOptions.Builder() + .database("my-database").precision(WritePrecision.S).gzipThreshold(1024).noSync(true).build(); + WriteOptions noSyncMismatch = new WriteOptions.Builder() + .database("my-database").precision(WritePrecision.S).gzipThreshold(512).noSync(false).build(); + WriteOptions defaultTagsMismatch = new WriteOptions.Builder() + .database("my-database").precision(WritePrecision.S).gzipThreshold(512).noSync(true) + .defaultTags(Map.of("region", "west")).build(); + WriteOptions tagOrderMismatch = new WriteOptions.Builder() + .database("my-database").precision(WritePrecision.S).gzipThreshold(512).noSync(true) + .tagOrder(List.of("host", "region")).build(); + WriteOptions headersMismatch = new WriteOptions.Builder() + .database("my-database").precision(WritePrecision.S).gzipThreshold(512).noSync(true) + .headers(Map.of("X-Trace-Id", "123")).build(); + + Assertions.assertThat(options).isNotEqualTo(gzipMismatch); + Assertions.assertThat(options).isNotEqualTo(noSyncMismatch); + Assertions.assertThat(options).isNotEqualTo(defaultTagsMismatch); + Assertions.assertThat(options).isNotEqualTo(tagOrderMismatch); + Assertions.assertThat(options).isNotEqualTo(headersMismatch); } @Test @@ -92,6 +114,25 @@ void optionsWithHeaders() { Assertions.assertThat(options).isEqualTo(optionsViaBuilder); } + @Test + void optionsWithTagOrder() { + List tagOrder = List.of("region", "host"); + + WriteOptions options = new WriteOptions.Builder().tagOrder(tagOrder).build(); + + Assertions.assertThat(options.tagOrderSafe()).containsExactly("region", "host"); + + WriteOptions optionsWithIgnoredEntries = new WriteOptions.Builder() + .tagOrder(Arrays.asList("region", null, "", "host")) + .build(); + Assertions.assertThat(optionsWithIgnoredEntries.tagOrderSafe()).containsExactly("region", "host"); + + WriteOptions ctorOptionsWithIgnoredEntries = new WriteOptions( + "my-database", WritePrecision.NS, 512, false, Map.of(), Map.of(), + Arrays.asList("region", null, "", "host")); + Assertions.assertThat(ctorOptionsWithIgnoredEntries.tagOrderSafe()).containsExactly("region", "host"); + } + @Test void optionsEmpty() { ClientConfig config = configBuilder @@ -101,11 +142,17 @@ void optionsEmpty() { .gzipThreshold(512) .build(); - WriteOptions options = new WriteOptions.Builder().build(); + WriteOptions options = WriteOptions.defaultWriteOptions(); Assertions.assertThat(options.databaseSafe(config)).isEqualTo("my-database"); - Assertions.assertThat(options.precisionSafe(config)).isEqualTo(WritePrecision.S); - Assertions.assertThat(options.gzipThresholdSafe(config)).isEqualTo(512); + Assertions.assertThat(options.precisionSafe(config)).isEqualTo(WriteOptions.DEFAULT_WRITE_PRECISION); + Assertions.assertThat(options.gzipThresholdSafe(config)).isEqualTo(WriteOptions.DEFAULT_GZIP_THRESHOLD); + Assertions.assertThat(options.tagOrderSafe()).isEmpty(); + + WriteOptions builderOptions = new WriteOptions.Builder().build(); + Assertions.assertThat(builderOptions.databaseSafe(config)).isEqualTo("my-database"); + Assertions.assertThat(builderOptions.precisionSafe(config)).isEqualTo(WritePrecision.S); + Assertions.assertThat(builderOptions.gzipThresholdSafe(config)).isEqualTo(512); } @Test @@ -256,5 +303,7 @@ void optionsHashCode() { .isNotEqualTo(builder.database("my-database").build().hashCode()); Assertions.assertThat(baseOptions.hashCode()) .isNotEqualTo(builder.defaultTags(defaultTags).build().hashCode()); + Assertions.assertThat(baseOptions.hashCode()) + .isNotEqualTo(builder.tagOrder(List.of("region", "host")).build().hashCode()); } }