diff --git a/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/Mutation.java b/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/Mutation.java index dc55756241e9..a1866189c688 100644 --- a/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/Mutation.java +++ b/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/Mutation.java @@ -23,6 +23,7 @@ import com.google.bigtable.v2.Mutation.DeleteFromRow; import com.google.bigtable.v2.Mutation.MergeToCell; import com.google.bigtable.v2.Mutation.SetCell; +import com.google.bigtable.v2.Mutation.TimestampOrigin; import com.google.cloud.bigtable.data.v2.models.Range.TimestampRange; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; @@ -32,6 +33,8 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.List; import javax.annotation.Nonnull; @@ -154,9 +157,9 @@ public Mutation setCell( @Override public Mutation setCell( @Nonnull String familyName, @Nonnull ByteString qualifier, @Nonnull ByteString value) { - long timestamp = System.currentTimeMillis() * 1_000; + long timestamp = Instant.EPOCH.until(Instant.now(), ChronoUnit.MICROS); - return setCell(familyName, qualifier, timestamp, value); + return setCell(familyName, qualifier, timestamp, value, TimestampOrigin.CLIENT_AUTO_GENERATED); } @Override @@ -165,6 +168,15 @@ public Mutation setCell( @Nonnull ByteString qualifier, long timestamp, @Nonnull ByteString value) { + return setCell(familyName, qualifier, timestamp, value, TimestampOrigin.USER_SPECIFIED); + } + + private Mutation setCell( + @Nonnull String familyName, + @Nonnull ByteString qualifier, + long timestamp, + @Nonnull ByteString value, + TimestampOrigin timestampOrigin) { Validations.validateFamily(familyName); Preconditions.checkNotNull(qualifier, "qualifier can't be null."); Preconditions.checkNotNull(value, "value can't be null."); @@ -182,6 +194,7 @@ public Mutation setCell( .setTimestampMicros(timestamp) .setValue(value) .build()) + .setTimestampOrigin(timestampOrigin) .build()); return this; diff --git a/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java b/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java index 1b3d4f2dee14..4b0e91724777 100644 --- a/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java +++ b/java-bigtable/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java @@ -660,7 +660,8 @@ private Builder() { .setDirectAccessRequested(isDirectPathRequested) .setTrafficDirectorEnabled(isDirectPathRequested) .setPeerInfo(true) - .setSessionsCompatible(true); + .setSessionsCompatible(true) + .setMicrosecondTimestamp(true); } private Builder(EnhancedBigtableStubSettings settings) { @@ -1008,6 +1009,7 @@ public EnhancedBigtableStubSettings build() { featureFlags.setRoutingCookie(true); featureFlags.setRetryInfo(true); + featureFlags.setMicrosecondTimestamp(true); // client_Side_metrics_enabled feature flag is only set when a user is running with a // DefaultMetricsProvider. This may cause false negatives when a user registered the // metrics on their CustomOpenTelemetryMetricsProvider. diff --git a/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/it/BulkMutateIT.java b/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/it/BulkMutateIT.java index aaf32decfe17..6b9d320486eb 100644 --- a/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/it/BulkMutateIT.java +++ b/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/it/BulkMutateIT.java @@ -53,6 +53,11 @@ public class BulkMutateIT { @Test(timeout = 60 * 1000) public void test() throws IOException, InterruptedException { + assume() + .withMessage("Emulator does not support microsecond timestamp granularity") + .that(testEnvRule.env()) + .isNotInstanceOf(EmulatorEnv.class); + BigtableDataSettings settings = testEnvRule.env().getDataClientSettings(); String rowPrefix = UUID.randomUUID().toString(); // Set target latency really low so it'll trigger adjusting thresholds diff --git a/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/it/CheckAndMutateIT.java b/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/it/CheckAndMutateIT.java index a3570f5e5bfb..1ca62c7db5bc 100644 --- a/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/it/CheckAndMutateIT.java +++ b/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/it/CheckAndMutateIT.java @@ -49,6 +49,11 @@ public class CheckAndMutateIT { @Test public void test() throws Exception { + assume() + .withMessage("Emulator does not support microsecond timestamp granularity") + .that(testEnvRule.env()) + .isNotInstanceOf(EmulatorEnv.class); + TableId tableId = testEnvRule.env().getTableId(); String familyId = testEnvRule.env().getFamilyId(); String rowKey = UUID.randomUUID().toString(); diff --git a/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/it/MutateRowIT.java b/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/it/MutateRowIT.java index a54c7268276f..ac96a10f72e0 100644 --- a/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/it/MutateRowIT.java +++ b/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/it/MutateRowIT.java @@ -44,6 +44,11 @@ public class MutateRowIT { @Test public void test() throws Exception { + assume() + .withMessage("Emulator does not support microsecond timestamp granularity") + .that(testEnvRule.env()) + .isNotInstanceOf(EmulatorEnv.class); + String rowKey = UUID.randomUUID().toString(); String familyId = testEnvRule.env().getFamilyId(); diff --git a/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/models/MutationTest.java b/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/models/MutationTest.java index 3ba1de67011e..347066d3815b 100644 --- a/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/models/MutationTest.java +++ b/java-bigtable/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/models/MutationTest.java @@ -22,6 +22,7 @@ import com.google.bigtable.v2.Mutation.DeleteFromFamily; import com.google.bigtable.v2.Mutation.DeleteFromRow; import com.google.bigtable.v2.Mutation.MergeToCell; +import com.google.bigtable.v2.Mutation.TimestampOrigin; import com.google.cloud.bigtable.data.v2.models.Range.TimestampRange; import com.google.common.primitives.Longs; import com.google.protobuf.ByteString; @@ -30,6 +31,8 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.List; import org.junit.Before; import org.junit.Test; @@ -48,7 +51,7 @@ public void setUp() { @Test public void setCellTest() { - long minTimestamp = System.currentTimeMillis() * 1_000; + long minTimestamp = Instant.EPOCH.until(Instant.now(), ChronoUnit.MICROS); mutation .setCell( @@ -65,7 +68,7 @@ public void setCellTest() { List actual = mutation.getMutations(); - long maxTimestamp = System.currentTimeMillis() * 1_000; + long maxTimestamp = Instant.EPOCH.until(Instant.now(), ChronoUnit.MICROS); com.google.common.collect.Range expectedTimestampRange = com.google.common.collect.Range.closed(minTimestamp, maxTimestamp); @@ -77,6 +80,7 @@ public void setCellTest() { assertThat(actual.get(0).getSetCell().getValue()) .isEqualTo(ByteString.copyFromUtf8("fake-value")); assertThat(actual.get(0).getSetCell().getTimestampMicros()).isEqualTo(1_000); + assertThat(actual.get(0).getTimestampOrigin()).isEqualTo(TimestampOrigin.USER_SPECIFIED); assertThat(actual.get(1).getSetCell().getFamilyName()).isEqualTo("fake-family"); assertThat(actual.get(1).getSetCell().getColumnQualifier()) @@ -84,6 +88,7 @@ public void setCellTest() { assertThat(actual.get(1).getSetCell().getValue()) .isEqualTo(ByteString.copyFromUtf8("fake-value")); assertThat(actual.get(1).getSetCell().getTimestampMicros()).isIn(expectedTimestampRange); + assertThat(actual.get(1).getTimestampOrigin()).isEqualTo(TimestampOrigin.CLIENT_AUTO_GENERATED); assertThat(actual.get(2).getSetCell().getFamilyName()).isEqualTo("fake-family2"); assertThat(actual.get(2).getSetCell().getColumnQualifier()) @@ -91,6 +96,7 @@ public void setCellTest() { assertThat(actual.get(2).getSetCell().getValue()) .isEqualTo(ByteString.copyFromUtf8("fake-value2")); assertThat(actual.get(2).getSetCell().getTimestampMicros()).isEqualTo(1_000); + assertThat(actual.get(2).getTimestampOrigin()).isEqualTo(TimestampOrigin.USER_SPECIFIED); assertThat(actual.get(3).getSetCell().getFamilyName()).isEqualTo("fake-family2"); assertThat(actual.get(3).getSetCell().getColumnQualifier()) @@ -98,6 +104,7 @@ public void setCellTest() { assertThat(actual.get(3).getSetCell().getValue()) .isEqualTo(ByteString.copyFromUtf8("fake-value2")); assertThat(actual.get(3).getSetCell().getTimestampMicros()).isIn(expectedTimestampRange); + assertThat(actual.get(3).getTimestampOrigin()).isEqualTo(TimestampOrigin.CLIENT_AUTO_GENERATED); assertThat(Mutation.fromProtoUnsafe(actual).getMutations()).isEqualTo(actual); } @@ -113,6 +120,7 @@ public void setCellWithServerSideTimestamp() { List actual = mutation.getMutations(); assertThat(actual.get(0).getSetCell().getTimestampMicros()) .isEqualTo(Mutation.SERVER_SIDE_TIMESTAMP); + assertThat(actual.get(0).getTimestampOrigin()).isEqualTo(TimestampOrigin.USER_SPECIFIED); } @Test @@ -276,6 +284,10 @@ public void testWithLongValue() { assertThat(actualMutation.getSetCell().getValue()) .isEqualTo(ByteString.copyFrom(Longs.toByteArray(100_000L))); + assertThat(mutations.get(0).getTimestampOrigin()) + .isEqualTo(TimestampOrigin.CLIENT_AUTO_GENERATED); + assertThat(mutations.get(1).getTimestampOrigin()) + .isEqualTo(TimestampOrigin.CLIENT_AUTO_GENERATED); assertThat(mutations.get(2).getSetCell()) .isEqualTo( @@ -285,6 +297,7 @@ public void testWithLongValue() { .setTimestampMicros(30_000L) .setValue(ByteString.copyFrom(Longs.toByteArray(20_000L))) .build()); + assertThat(mutations.get(2).getTimestampOrigin()).isEqualTo(TimestampOrigin.USER_SPECIFIED); } @Test diff --git a/java-bigtable/proto-google-cloud-bigtable-v2/src/main/java/com/google/bigtable/v2/FeatureFlags.java b/java-bigtable/proto-google-cloud-bigtable-v2/src/main/java/com/google/bigtable/v2/FeatureFlags.java index 2b8765cf13bb..4d922aec711c 100644 --- a/java-bigtable/proto-google-cloud-bigtable-v2/src/main/java/com/google/bigtable/v2/FeatureFlags.java +++ b/java-bigtable/proto-google-cloud-bigtable-v2/src/main/java/com/google/bigtable/v2/FeatureFlags.java @@ -1560,9 +1560,51 @@ public Builder clearSessionsRequired() { return this; } + + private boolean microsecondTimestamp_ ; + /** + * bool microsecond_timestamp = 14 [(.google.api.field_behavior) = OPTIONAL]; + * @return The microsecondTimestamp. + */ + @java.lang.Override + public boolean getMicrosecondTimestamp() { + return microsecondTimestamp_; + } + /** + * bool microsecond_timestamp = 14 [(.google.api.field_behavior) = OPTIONAL]; + * @param value The microsecondTimestamp to set. + * @return This builder for chaining. + */ + public Builder setMicrosecondTimestamp(boolean value) { + microsecondTimestamp_ = value; + bitField0_ |= 0x00001000; + onChanged(); + return this; + } + /** + * bool microsecond_timestamp = 14 [(.google.api.field_behavior) = OPTIONAL]; + * @return This builder for chaining. + */ + public Builder clearMicrosecondTimestamp() { + bitField0_ = (bitField0_ & ~0x00001000); + microsecondTimestamp_ = false; + onChanged(); + return this; + } // @@protoc_insertion_point(builder_scope:google.bigtable.v2.FeatureFlags) } + + public static final int MICROSECOND_TIMESTAMP_FIELD_NUMBER = 14; + private boolean microsecondTimestamp_ = false; + /** + * bool microsecond_timestamp = 14 [(.google.api.field_behavior) = OPTIONAL]; + * @return The microsecondTimestamp. + */ + @java.lang.Override + public boolean getMicrosecondTimestamp() { + return microsecondTimestamp_; + } // @@protoc_insertion_point(class_scope:google.bigtable.v2.FeatureFlags) private static final com.google.bigtable.v2.FeatureFlags DEFAULT_INSTANCE; diff --git a/java-bigtable/proto-google-cloud-bigtable-v2/src/main/java/com/google/bigtable/v2/FeatureFlagsOrBuilder.java b/java-bigtable/proto-google-cloud-bigtable-v2/src/main/java/com/google/bigtable/v2/FeatureFlagsOrBuilder.java index a967bd990564..eebdc23a1391 100644 --- a/java-bigtable/proto-google-cloud-bigtable-v2/src/main/java/com/google/bigtable/v2/FeatureFlagsOrBuilder.java +++ b/java-bigtable/proto-google-cloud-bigtable-v2/src/main/java/com/google/bigtable/v2/FeatureFlagsOrBuilder.java @@ -189,4 +189,10 @@ public interface FeatureFlagsOrBuilder * @return The sessionsRequired. */ boolean getSessionsRequired(); + + /** + * bool microsecond_timestamp = 14 [(.google.api.field_behavior) = OPTIONAL]; + * @return The microsecondTimestamp. + */ + boolean getMicrosecondTimestamp(); } diff --git a/java-bigtable/proto-google-cloud-bigtable-v2/src/main/proto/google/bigtable/v2/feature_flags.proto b/java-bigtable/proto-google-cloud-bigtable-v2/src/main/proto/google/bigtable/v2/feature_flags.proto index 6cf9ca5b81ac..b21fcd4391b5 100644 --- a/java-bigtable/proto-google-cloud-bigtable-v2/src/main/proto/google/bigtable/v2/feature_flags.proto +++ b/java-bigtable/proto-google-cloud-bigtable-v2/src/main/proto/google/bigtable/v2/feature_flags.proto @@ -75,5 +75,9 @@ message FeatureFlags { bool sessions_compatible = 12; // Internal flag to force sessions for internal projects. - bool sessions_required = 13; + bool sessions_required = 13;\n + // Optional. Indicates whether the client supports microsecond precision + // timestamps. + bool microsecond_timestamp = 14 [(google.api.field_behavior) = OPTIONAL]; + }