{@code
* LogicalView existingLogicalView = ...;
* CreateLogicalViewRequest logicalViewRequest = CreateLogicalViewRequest.of("my-instance", "my-new-logical-view")
- * .setQuery("...");
+ * .setQuery("...").setDeletionProtection(true);
* }
*
* @see LogicalView for more details
@@ -56,6 +56,13 @@ public CreateLogicalViewRequest setQuery(@Nonnull String query) {
return this;
}
+ /** Configures if the logical view is deletion protected. */
+ @SuppressWarnings("WeakerAccess")
+ public CreateLogicalViewRequest setDeletionProtection(boolean value) {
+ proto.getLogicalViewBuilder().setDeletionProtection(value);
+ return this;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/LogicalView.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/LogicalView.java
index c884d97730..48100c81de 100644
--- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/LogicalView.java
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/LogicalView.java
@@ -75,6 +75,11 @@ public String getQuery() {
return proto.getQuery();
}
+ /** Returns whether this logical view is deletion protected. */
+ public boolean isDeletionProtected() {
+ return proto.getDeletionProtection();
+ }
+
/**
* Creates the request protobuf. This method is considered an internal implementation detail and
* not meant to be used by applications.
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/UpdateLogicalViewRequest.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/UpdateLogicalViewRequest.java
index d24cfff30a..6cbc55a28d 100644
--- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/UpdateLogicalViewRequest.java
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/UpdateLogicalViewRequest.java
@@ -80,6 +80,13 @@ public UpdateLogicalViewRequest setQuery(String query) {
return this;
}
+ /** Changes the deletion protection of an existing logical view. */
+ public UpdateLogicalViewRequest setDeletionProtection(boolean deletionProtection) {
+ requestBuilder.getLogicalViewBuilder().setDeletionProtection(deletionProtection);
+ updateFieldMask(com.google.bigtable.admin.v2.LogicalView.DELETION_PROTECTION_FIELD_NUMBER);
+ return this;
+ }
+
private void updateFieldMask(int fieldNumber) {
FieldMask newMask =
FieldMaskUtil.fromFieldNumbers(com.google.bigtable.admin.v2.LogicalView.class, fieldNumber);
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/BigtableChannelPool.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/BigtableChannelPool.java
new file mode 100644
index 0000000000..aae154b7b5
--- /dev/null
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/BigtableChannelPool.java
@@ -0,0 +1,595 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.cloud.bigtable.gaxx.grpc;
+
+import com.google.api.core.InternalApi;
+import com.google.api.gax.grpc.ChannelFactory;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
+import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener;
+import io.grpc.ManagedChannel;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+/**
+ * A {@link ManagedChannel} that will send requests round-robin via a set of channels.
+ *
+ * In addition to spreading requests over a set of child connections, the pool will also actively + * manage the lifecycle of the channels. Currently lifecycle management is limited to pre-emptively + * replacing channels every hour. In the future it will dynamically size the pool based on number of + * outstanding requests. + * + *
Package-private for internal use.
+ */
+class BigtableChannelPool extends ManagedChannel {
+ @VisibleForTesting
+ static final Logger LOG = Logger.getLogger(BigtableChannelPool.class.getName());
+
+ private static final java.time.Duration REFRESH_PERIOD = java.time.Duration.ofMinutes(50);
+
+ private final BigtableChannelPoolSettings settings;
+ private final ChannelFactory channelFactory;
+ private final ScheduledExecutorService executor;
+
+ private final Object entryWriteLock = new Object();
+ @VisibleForTesting final AtomicReference This method is expected to be called on a fixed interval. On every invocation it will:
+ *
+ * Not threadsafe, must be called under the entryWriteLock monitor
+ */
+ @VisibleForTesting
+ void resize() {
+ List This is done on a best effort basis. If the replacement channel fails to construct, the old
+ * channel will continue to be used.
+ */
+ @InternalApi("Visible for testing")
+ void refresh() {
+ // Note: synchronization is necessary in case refresh is called concurrently:
+ // - thread1 fails to replace a single entry
+ // - thread2 succeeds replacing an entry
+ // - thread1 loses the race to replace the list
+ // - then thread2 will shut down channel that thread1 will put back into circulation (after it
+ // replaces the list)
+ synchronized (entryWriteLock) {
+ LOG.fine("Refreshing all channels");
+ ArrayList Hence, the increment of outstanding RPCs has to happen when the ClientCall is initialized,
+ * as part of Channel#newCall(), not after the ClientCall is started. The decrement of
+ * outstanding RPCs has to happen when the ClientCall is closed or the ClientCall failed to
+ * start.
+ */
+ @VisibleForTesting final AtomicInteger outstandingRpcs = new AtomicInteger(0);
+
+ private final AtomicInteger maxOutstanding = new AtomicInteger();
+
+ // Flag that the channel should be closed once all of the outstanding RPC complete.
+ private final AtomicBoolean shutdownRequested = new AtomicBoolean();
+ // Flag that the channel has been closed.
+ private final AtomicBoolean shutdownInitiated = new AtomicBoolean();
+
+ private Entry(ManagedChannel channel) {
+ this.channel = channel;
+ }
+
+ int getAndResetMaxOutstanding() {
+ return maxOutstanding.getAndSet(outstandingRpcs.get());
+ }
+
+ /**
+ * Try to increment the outstanding RPC count. The method will return false if the channel is
+ * closing and the caller should pick a different channel. If the method returned true, the
+ * channel has been successfully retained and it is the responsibility of the caller to release
+ * it.
+ */
+ private boolean retain() {
+ // register desire to start RPC
+ int currentOutstanding = outstandingRpcs.incrementAndGet();
+
+ // Rough book keeping
+ int prevMax = maxOutstanding.get();
+ if (currentOutstanding > prevMax) {
+ maxOutstanding.incrementAndGet();
+ }
+
+ // abort if the channel is closing
+ if (shutdownRequested.get()) {
+ release();
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Notify the channel that the number of outstanding RPCs has decreased. If shutdown has been
+ * previously requested, this method will shutdown the channel if its the last outstanding RPC.
+ */
+ private void release() {
+ int newCount = outstandingRpcs.decrementAndGet();
+ if (newCount < 0) {
+ LOG.log(Level.WARNING, "Bug! Reference count is negative (" + newCount + ")!");
+ }
+
+ // Must check outstandingRpcs after shutdownRequested (in reverse order of retain()) to ensure
+ // mutual exclusion.
+ if (shutdownRequested.get() && outstandingRpcs.get() == 0) {
+ shutdown();
+ }
+ }
+
+ /**
+ * Request a shutdown. The actual shutdown will be delayed until there are no more outstanding
+ * RPCs.
+ */
+ private void requestShutdown() {
+ shutdownRequested.set(true);
+ if (outstandingRpcs.get() == 0) {
+ shutdown();
+ }
+ }
+
+ /** Ensure that shutdown is only called once. */
+ private void shutdown() {
+ if (shutdownInitiated.compareAndSet(false, true)) {
+ channel.shutdown();
+ }
+ }
+ }
+
+ /** Thin wrapper to ensure that new calls are properly reference counted. */
+ private class AffinityChannel extends Channel {
+ private final int affinity;
+
+ public AffinityChannel(int affinity) {
+ this.affinity = affinity;
+ }
+
+ @Override
+ public String authority() {
+ return authority;
+ }
+
+ @Override
+ public To facilitate low latency/high throughout applications, gax provides a {@link
+ * BigtableChannelPool}. The pool is meant to facilitate high throughput/low latency clients. By
+ * splitting load across multiple gRPC channels the client can spread load across multiple frontends
+ * and overcome gRPC's limit of 100 concurrent RPCs per channel. However oversizing the {@link
+ * BigtableChannelPool} can lead to underutilized channels which will lead to high tail latency due
+ * to GFEs disconnecting idle channels.
+ *
+ * The {@link BigtableChannelPool} is designed to adapt to varying traffic patterns by tracking
+ * outstanding RPCs and resizing the pool size. This class configures the behavior. In general
+ * clients should aim to have less than 50 concurrent RPCs per channel and at least 1 outstanding
+ * per channel per minute.
+ *
+ * The settings in this class will be applied every minute.
+ */
+@BetaApi("surface for channel pool sizing is not yet stable")
+@AutoValue
+public abstract class BigtableChannelPoolSettings {
+ /** How often to check and possibly resize the {@link BigtableChannelPool}. */
+ static final Duration RESIZE_INTERVAL = Duration.ofMinutes(1);
+
+ /** The maximum number of channels that can be added or removed at a time. */
+ static final int MAX_RESIZE_DELTA = 2;
+
+ /**
+ * Threshold to start scaling down the channel pool.
+ *
+ * When the average of the maximum number of outstanding RPCs in a single minute drop below
+ * this threshold, channels will be removed from the pool.
+ */
+ public abstract int getMinRpcsPerChannel();
+
+ /**
+ * Threshold to start scaling up the channel pool.
+ *
+ * When the average of the maximum number of outstanding RPCs in a single minute surpass this
+ * threshold, channels will be added to the pool. For google services, gRPC channels will start
+ * locally queuing RPC when there are 100 concurrent RPCs.
+ */
+ public abstract int getMaxRpcsPerChannel();
+
+ /**
+ * The absolute minimum size of the channel pool.
+ *
+ * Regardless of the current throughput, the number of channels will not drop below this limit
+ */
+ public abstract int getMinChannelCount();
+
+ /**
+ * The absolute maximum size of the channel pool.
+ *
+ * Regardless of the current throughput, the number of channels will not exceed this limit
+ */
+ public abstract int getMaxChannelCount();
+
+ /**
+ * The initial size of the channel pool.
+ *
+ * During client construction the client open this many connections. This will be scaled up or
+ * down in the next period.
+ */
+ public abstract int getInitialChannelCount();
+
+ /**
+ * If all of the channels should be replaced on an hourly basis.
+ *
+ * The GFE will forcibly disconnect active channels after an hour. To minimize the cost of
+ * reconnects, this will create a new channel asynchronuously, prime it and then swap it with an
+ * old channel.
+ */
+ public abstract boolean isPreemptiveRefreshEnabled();
+
+ /**
+ * Helper to check if the {@link BigtableChannelPool} implementation can skip dynamic size logic
+ */
+ boolean isStaticSize() {
+ // When range is restricted to a single size
+ if (getMinChannelCount() == getMaxChannelCount()) {
+ return true;
+ }
+ // When the scaling threshold are not set
+ if (getMinRpcsPerChannel() == 0 && getMaxRpcsPerChannel() == Integer.MAX_VALUE) {
+ return true;
+ }
+
+ return false;
+ }
+
+ public abstract Builder toBuilder();
+
+ public static BigtableChannelPoolSettings staticallySized(int size) {
+ return builder()
+ .setInitialChannelCount(size)
+ .setMinRpcsPerChannel(0)
+ .setMaxRpcsPerChannel(Integer.MAX_VALUE)
+ .setMinChannelCount(size)
+ .setMaxChannelCount(size)
+ .build();
+ }
+
+ public static Builder builder() {
+ return new AutoValue_BigtableChannelPoolSettings.Builder()
+ .setInitialChannelCount(1)
+ .setMinChannelCount(1)
+ .setMaxChannelCount(200)
+ .setMinRpcsPerChannel(0)
+ .setMaxRpcsPerChannel(Integer.MAX_VALUE)
+ .setPreemptiveRefreshEnabled(false);
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Builder setMinRpcsPerChannel(int count);
+
+ public abstract Builder setMaxRpcsPerChannel(int count);
+
+ public abstract Builder setMinChannelCount(int count);
+
+ public abstract Builder setMaxChannelCount(int count);
+
+ public abstract Builder setInitialChannelCount(int count);
+
+ public abstract Builder setPreemptiveRefreshEnabled(boolean enabled);
+
+ abstract BigtableChannelPoolSettings autoBuild();
+
+ public BigtableChannelPoolSettings build() {
+ BigtableChannelPoolSettings s = autoBuild();
+
+ Preconditions.checkState(
+ s.getMinRpcsPerChannel() <= s.getMaxRpcsPerChannel(), "rpcsPerChannel range is invalid");
+ Preconditions.checkState(
+ s.getMinChannelCount() > 0, "Minimum channel count must be at least 1");
+ Preconditions.checkState(
+ s.getMinChannelCount() <= s.getMaxRpcsPerChannel(), "absolute channel range is invalid");
+ Preconditions.checkState(
+ s.getMinChannelCount() <= s.getInitialChannelCount(),
+ "initial channel count be at least minChannelCount");
+ Preconditions.checkState(
+ s.getInitialChannelCount() <= s.getMaxChannelCount(),
+ "initial channel count must be less than maxChannelCount");
+ Preconditions.checkState(
+ s.getInitialChannelCount() > 0, "Initial channel count must be greater than 0");
+ return s;
+ }
+ }
+}
diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/it/BigtableLogicalViewIT.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/it/BigtableLogicalViewIT.java
index 83ca4e4c09..d73d68d48f 100644
--- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/it/BigtableLogicalViewIT.java
+++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/it/BigtableLogicalViewIT.java
@@ -20,6 +20,7 @@
import static com.google.common.truth.TruthJUnit.assume;
import static org.junit.Assert.fail;
+import com.google.api.gax.rpc.FailedPreconditionException;
import com.google.api.gax.rpc.NotFoundException;
import com.google.cloud.bigtable.admin.v2.BigtableInstanceAdminClient;
import com.google.cloud.bigtable.admin.v2.BigtableTableAdminClient;
@@ -37,14 +38,12 @@
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
-@Ignore("Not fully working yet in production")
public class BigtableLogicalViewIT {
@ClassRule public static final TestEnvRule testEnvRule = new TestEnvRule();
@Rule public final PrefixGenerator prefixGenerator = new PrefixGenerator();
@@ -77,12 +76,17 @@ public void createLogicalViewAndGetLogicalViewTest() {
String logicalViewId = prefixGenerator.newPrefix();
CreateLogicalViewRequest request =
- CreateLogicalViewRequest.of(instanceId, logicalViewId).setQuery(getQuery());
+ CreateLogicalViewRequest.of(instanceId, logicalViewId)
+ .setQuery(getQuery())
+ .setDeletionProtection(false);
try {
LogicalView response = client.createLogicalView(request);
assertWithMessage("Got wrong logical view Id in CreateLogicalView")
.that(response.getId())
.isEqualTo(logicalViewId);
+ assertWithMessage("Got wrong deletion protection in CreateLogicalView")
+ .that(response.isDeletionProtected())
+ .isFalse();
assertWithMessage("Got wrong query in CreateLogicalView")
.that(response.getQuery())
.isEqualTo(getQuery());
@@ -91,6 +95,9 @@ public void createLogicalViewAndGetLogicalViewTest() {
assertWithMessage("Got wrong logical view Id in getLogicalView")
.that(response.getId())
.isEqualTo(logicalViewId);
+ assertWithMessage("Got wrong deletion protection in getLogicalView")
+ .that(response.isDeletionProtected())
+ .isFalse();
assertWithMessage("Got wrong query in getLogicalView")
.that(response.getQuery())
.isEqualTo(getQuery());
@@ -119,15 +126,33 @@ public void listLogicalViewsTest() {
public void updateLogicalViewAndDeleteLogicalViewTest() throws InterruptedException {
String logicalViewId = prefixGenerator.newPrefix();
- // Create a logical view.
- CreateLogicalViewRequest request = createLogicalViewRequest(logicalViewId);
+ // Create a deletion-protected logical view.
+ CreateLogicalViewRequest request =
+ createLogicalViewRequest(logicalViewId).setDeletionProtection(true);
LogicalView response = client.createLogicalView(request);
+ assertWithMessage("Got wrong deletion protection in CreateLogicalView")
+ .that(response.isDeletionProtected())
+ .isTrue();
+
+ // We should not be able to delete the logical view.
+ try {
+ client.deleteLogicalView(instanceId, logicalViewId);
+ fail("A delete-protected logical view should not have been able to be deleted");
+ } catch (FailedPreconditionException e) {
+ assertWithMessage("Incorrect exception type")
+ .that(e.getCause())
+ .isInstanceOf(StatusRuntimeException.class);
+ }
- // Update the query of the logical view.
+ // Update the deletion protection bit and query of the logical view.
String query = "SELECT 1 AS value";
- UpdateLogicalViewRequest updateRequest = UpdateLogicalViewRequest.of(response).setQuery(query);
+ UpdateLogicalViewRequest updateRequest =
+ UpdateLogicalViewRequest.of(response).setQuery(query).setDeletionProtection(false);
response = client.updateLogicalView(updateRequest);
+ assertWithMessage("Got wrong deletion protection in UpdateLogicalView")
+ .that(response.isDeletionProtected())
+ .isFalse();
assertWithMessage("Got wrong query in UpdateLogicalView")
.that(response.getQuery())
.isEqualTo(query);
diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/it/BigtableMaterializedViewIT.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/it/BigtableMaterializedViewIT.java
index e9670aeef7..62a2f794b0 100644
--- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/it/BigtableMaterializedViewIT.java
+++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/it/BigtableMaterializedViewIT.java
@@ -24,28 +24,31 @@
import com.google.api.gax.rpc.NotFoundException;
import com.google.cloud.bigtable.admin.v2.BigtableInstanceAdminClient;
import com.google.cloud.bigtable.admin.v2.BigtableTableAdminClient;
+import com.google.cloud.bigtable.admin.v2.models.CreateInstanceRequest;
import com.google.cloud.bigtable.admin.v2.models.CreateMaterializedViewRequest;
import com.google.cloud.bigtable.admin.v2.models.CreateTableRequest;
+import com.google.cloud.bigtable.admin.v2.models.Instance;
import com.google.cloud.bigtable.admin.v2.models.MaterializedView;
+import com.google.cloud.bigtable.admin.v2.models.StorageType;
import com.google.cloud.bigtable.admin.v2.models.Table;
import com.google.cloud.bigtable.admin.v2.models.UpdateMaterializedViewRequest;
import com.google.cloud.bigtable.test_helpers.env.EmulatorEnv;
import com.google.cloud.bigtable.test_helpers.env.PrefixGenerator;
import com.google.cloud.bigtable.test_helpers.env.TestEnvRule;
import io.grpc.StatusRuntimeException;
+import java.io.IOException;
import java.util.List;
import java.util.logging.Logger;
+import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
-@Ignore("Not fully working yet in production")
public class BigtableMaterializedViewIT {
@ClassRule public static final TestEnvRule testEnvRule = new TestEnvRule();
@Rule public final PrefixGenerator prefixGenerator = new PrefixGenerator();
@@ -53,24 +56,44 @@ public class BigtableMaterializedViewIT {
private static final int[] BACKOFF_DURATION = {2, 4, 8, 16, 32, 64, 128, 256, 512, 1024};
private static BigtableInstanceAdminClient client;
+ private static BigtableTableAdminClient tableAdminClient;
private static Table testTable;
-
- private String instanceId = testEnvRule.env().getInstanceId();
+ private static String instanceId = "";
// TODO: Update this test once emulator supports InstanceAdmin operation
// https://github.com/googleapis/google-cloud-go/issues/1069
@BeforeClass
- public static void validatePlatform() {
+ public static void validatePlatform() throws IOException {
assume()
.withMessage("BigtableInstanceAdminClient doesn't support on Emulator")
.that(testEnvRule.env())
.isNotInstanceOf(EmulatorEnv.class);
+
+ createInstance();
+ }
+
+ public static void createInstance() throws IOException {
+ client = testEnvRule.env().getInstanceAdminClient();
+
+ Instance instance =
+ client.createInstance(
+ CreateInstanceRequest.of(new PrefixGenerator().newPrefix())
+ .addCluster("my-cluster", "us-east1-c", 3, StorageType.SSD));
+ instanceId = instance.getId();
+ tableAdminClient =
+ BigtableTableAdminClient.create(testEnvRule.env().getProjectId(), instanceId);
+ }
+
+ @AfterClass
+ public static void deleteInstance() {
+ if (!instanceId.isEmpty()) {
+ client.deleteInstance(instanceId);
+ }
}
@Before
public void setUp() throws InterruptedException {
- client = testEnvRule.env().getInstanceAdminClient();
- testTable = createTestTable(testEnvRule.env().getTableAdminClient());
+ testTable = createTestTable(tableAdminClient);
}
@Test
diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/models/CreateLogicalViewRequestTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/models/CreateLogicalViewRequestTest.java
index ec5f6af14f..eededde65b 100644
--- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/models/CreateLogicalViewRequestTest.java
+++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/models/CreateLogicalViewRequestTest.java
@@ -33,13 +33,18 @@ public class CreateLogicalViewRequestTest {
public void testToProto() {
String query = "SELECT * FROM Table";
CreateLogicalViewRequest request =
- CreateLogicalViewRequest.of(INSTANCE_ID, LOGICAL_VIEW_ID).setQuery(query);
+ CreateLogicalViewRequest.of(INSTANCE_ID, LOGICAL_VIEW_ID)
+ .setQuery(query)
+ .setDeletionProtection(true);
com.google.bigtable.admin.v2.CreateLogicalViewRequest requestProto =
com.google.bigtable.admin.v2.CreateLogicalViewRequest.newBuilder()
.setParent(NameUtil.formatInstanceName(PROJECT_ID, INSTANCE_ID))
.setLogicalViewId(LOGICAL_VIEW_ID)
- .setLogicalView(com.google.bigtable.admin.v2.LogicalView.newBuilder().setQuery(query))
+ .setLogicalView(
+ com.google.bigtable.admin.v2.LogicalView.newBuilder()
+ .setQuery(query)
+ .setDeletionProtection(true))
.build();
assertThat(request.toProto(PROJECT_ID)).isEqualTo(requestProto);
}
@@ -47,30 +52,42 @@ public void testToProto() {
@Test
public void testEquality() {
CreateLogicalViewRequest request =
- CreateLogicalViewRequest.of(INSTANCE_ID, LOGICAL_VIEW_ID).setQuery("test 1");
+ CreateLogicalViewRequest.of(INSTANCE_ID, LOGICAL_VIEW_ID)
+ .setQuery("test 1")
+ .setDeletionProtection(true);
assertThat(request)
- .isEqualTo(CreateLogicalViewRequest.of(INSTANCE_ID, LOGICAL_VIEW_ID).setQuery("test 1"));
+ .isEqualTo(
+ CreateLogicalViewRequest.of(INSTANCE_ID, LOGICAL_VIEW_ID)
+ .setQuery("test 1")
+ .setDeletionProtection(true));
assertThat(request)
- .isNotEqualTo(CreateLogicalViewRequest.of(INSTANCE_ID, LOGICAL_VIEW_ID).setQuery("test 2"));
+ .isNotEqualTo(
+ CreateLogicalViewRequest.of(INSTANCE_ID, LOGICAL_VIEW_ID)
+ .setQuery("test 2")
+ .setDeletionProtection(true));
}
@Test
public void testHashCode() {
CreateLogicalViewRequest request =
- CreateLogicalViewRequest.of(INSTANCE_ID, LOGICAL_VIEW_ID).setQuery("test 1");
+ CreateLogicalViewRequest.of(INSTANCE_ID, LOGICAL_VIEW_ID)
+ .setQuery("test 1")
+ .setDeletionProtection(true);
assertThat(request.hashCode())
.isEqualTo(
CreateLogicalViewRequest.of(INSTANCE_ID, LOGICAL_VIEW_ID)
.setQuery("test 1")
+ .setDeletionProtection(true)
.hashCode());
assertThat(request.hashCode())
.isNotEqualTo(
CreateLogicalViewRequest.of(INSTANCE_ID, LOGICAL_VIEW_ID)
.setQuery("test 2")
+ .setDeletionProtection(true)
.hashCode());
}
}
diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/models/LogicalViewTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/models/LogicalViewTest.java
index 8b802ec8d7..7a17aaecf8 100644
--- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/models/LogicalViewTest.java
+++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/models/LogicalViewTest.java
@@ -37,12 +37,14 @@ public void testFromProto() {
com.google.bigtable.admin.v2.LogicalView.newBuilder()
.setName(logicalViewName.toString())
.setQuery("SELECT 1 from Table")
+ .setDeletionProtection(true)
.build();
LogicalView result = LogicalView.fromProto(logicalViewProto);
assertThat(result.getId()).isEqualTo(LOGICAL_VIEW_ID);
assertThat(result.getQuery()).isEqualTo("SELECT 1 from Table");
+ assertThat(result.isDeletionProtected()).isEqualTo(true);
}
@Test
@@ -70,6 +72,7 @@ public void testEquality() {
com.google.bigtable.admin.v2.LogicalView.newBuilder()
.setName(logicalViewName.toString())
.setQuery("SELECT 1 FROM Table")
+ .setDeletionProtection(true)
.build();
LogicalView logicalView = LogicalView.fromProto(proto);
@@ -80,6 +83,7 @@ public void testEquality() {
com.google.bigtable.admin.v2.LogicalView.newBuilder()
.setName(logicalViewName.toString())
.setQuery("SELECT 2 FROM Table")
+ .setDeletionProtection(true)
.build());
}
@@ -90,6 +94,7 @@ public void testHashCode() {
com.google.bigtable.admin.v2.LogicalView.newBuilder()
.setName(logicalViewName.toString())
.setQuery("SELECT 1 FROM Table")
+ .setDeletionProtection(true)
.build();
LogicalView logicalView = LogicalView.fromProto(proto);
@@ -100,6 +105,7 @@ public void testHashCode() {
com.google.bigtable.admin.v2.LogicalView.newBuilder()
.setName(logicalViewName.toString())
.setQuery("SELECT 2 FROM Table")
+ .setDeletionProtection(true)
.build()
.hashCode());
}
diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/models/UpdateLogicalViewRequestTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/models/UpdateLogicalViewRequestTest.java
index 6421d9cf56..da54bb5ac1 100644
--- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/models/UpdateLogicalViewRequestTest.java
+++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/models/UpdateLogicalViewRequestTest.java
@@ -33,16 +33,20 @@ public class UpdateLogicalViewRequestTest {
@Test
public void testToProto() {
UpdateLogicalViewRequest request =
- UpdateLogicalViewRequest.of(INSTANCE_ID, LOGICAL_VIEW_ID).setQuery("query 1");
+ UpdateLogicalViewRequest.of(INSTANCE_ID, LOGICAL_VIEW_ID)
+ .setQuery("query 1")
+ .setDeletionProtection(true);
com.google.bigtable.admin.v2.UpdateLogicalViewRequest requestProto =
com.google.bigtable.admin.v2.UpdateLogicalViewRequest.newBuilder()
.setLogicalView(
com.google.bigtable.admin.v2.LogicalView.newBuilder()
.setQuery("query 1")
+ .setDeletionProtection(true)
.setName(
NameUtil.formatLogicalViewName(PROJECT_ID, INSTANCE_ID, LOGICAL_VIEW_ID)))
- .setUpdateMask(FieldMask.newBuilder().addPaths("query").build())
+ .setUpdateMask(
+ FieldMask.newBuilder().addPaths("deletion_protection").addPaths("query").build())
.build();
assertThat(request.toProto(PROJECT_ID)).isEqualTo(requestProto);
}
@@ -50,31 +54,42 @@ public void testToProto() {
@Test
public void testEquality() {
UpdateLogicalViewRequest request =
- UpdateLogicalViewRequest.of(INSTANCE_ID, LOGICAL_VIEW_ID).setQuery("query 1");
+ UpdateLogicalViewRequest.of(INSTANCE_ID, LOGICAL_VIEW_ID)
+ .setQuery("query 1")
+ .setDeletionProtection(true);
assertThat(request)
- .isEqualTo(UpdateLogicalViewRequest.of(INSTANCE_ID, LOGICAL_VIEW_ID).setQuery("query 1"));
+ .isEqualTo(
+ UpdateLogicalViewRequest.of(INSTANCE_ID, LOGICAL_VIEW_ID)
+ .setQuery("query 1")
+ .setDeletionProtection(true));
assertThat(request)
.isNotEqualTo(
- UpdateLogicalViewRequest.of(INSTANCE_ID, LOGICAL_VIEW_ID).setQuery("query 2"));
+ UpdateLogicalViewRequest.of(INSTANCE_ID, LOGICAL_VIEW_ID)
+ .setQuery("query 2")
+ .setDeletionProtection(true));
}
@Test
public void testHashCode() {
UpdateLogicalViewRequest request =
- UpdateLogicalViewRequest.of(INSTANCE_ID, LOGICAL_VIEW_ID).setQuery("query 1");
+ UpdateLogicalViewRequest.of(INSTANCE_ID, LOGICAL_VIEW_ID)
+ .setQuery("query 1")
+ .setDeletionProtection(true);
assertThat(request.hashCode())
.isEqualTo(
UpdateLogicalViewRequest.of(INSTANCE_ID, LOGICAL_VIEW_ID)
.setQuery("query 1")
+ .setDeletionProtection(true)
.hashCode());
assertThat(request.hashCode())
.isNotEqualTo(
UpdateLogicalViewRequest.of(INSTANCE_ID, LOGICAL_VIEW_ID)
.setQuery("query 2")
+ .setDeletionProtection(true)
.hashCode());
}
}
diff --git a/grpc-google-cloud-bigtable-admin-v2/pom.xml b/grpc-google-cloud-bigtable-admin-v2/pom.xml
index 8fb7eabb51..d402413611 100644
--- a/grpc-google-cloud-bigtable-admin-v2/pom.xml
+++ b/grpc-google-cloud-bigtable-admin-v2/pom.xml
@@ -4,13 +4,13 @@
+ *
+ *
+ *