|
17 | 17 | */ |
18 | 18 | package org.apache.beam.sdk.io.gcp.bigtable.changestreams.dao; |
19 | 19 |
|
| 20 | +import static org.apache.beam.sdk.io.gcp.bigtable.changestreams.encoder.MetadataTableEncoder.parseInitialContinuationTokens; |
20 | 21 | import static org.apache.beam.sdk.io.gcp.bigtable.changestreams.encoder.MetadataTableEncoder.parseWatermarkFromRow; |
21 | 22 | import static org.hamcrest.MatcherAssert.assertThat; |
22 | 23 | import static org.hamcrest.Matchers.containsInAnyOrder; |
|
31 | 32 | import static org.mockito.Mockito.when; |
32 | 33 |
|
33 | 34 | import com.google.api.core.ApiFuture; |
| 35 | +import com.google.api.gax.rpc.ServerStream; |
34 | 36 | import com.google.cloud.bigtable.admin.v2.BigtableTableAdminClient; |
35 | 37 | import com.google.cloud.bigtable.admin.v2.BigtableTableAdminSettings; |
36 | 38 | import com.google.cloud.bigtable.data.v2.BigtableDataClient; |
|
39 | 41 | import com.google.cloud.bigtable.data.v2.models.Filters; |
40 | 42 | import com.google.cloud.bigtable.data.v2.models.Range.ByteStringRange; |
41 | 43 | import com.google.cloud.bigtable.data.v2.models.Row; |
| 44 | +import com.google.cloud.bigtable.data.v2.models.RowCell; |
42 | 45 | import com.google.cloud.bigtable.data.v2.models.RowMutation; |
43 | 46 | import com.google.cloud.bigtable.emulator.v2.BigtableEmulatorRule; |
44 | 47 | import com.google.protobuf.ByteString; |
|
49 | 52 | import java.util.Collections; |
50 | 53 | import java.util.HashMap; |
51 | 54 | import java.util.List; |
| 55 | +import java.util.Map; |
52 | 56 | import java.util.concurrent.ExecutionException; |
53 | 57 | import java.util.concurrent.TimeUnit; |
54 | 58 | import java.util.concurrent.TimeoutException; |
|
57 | 61 | import org.apache.beam.sdk.io.gcp.bigtable.changestreams.model.NewPartition; |
58 | 62 | import org.apache.beam.sdk.io.gcp.bigtable.changestreams.model.PartitionRecord; |
59 | 63 | import org.apache.beam.sdk.io.gcp.bigtable.changestreams.model.StreamPartitionWithWatermark; |
| 64 | +import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.primitives.Longs; |
60 | 65 | import org.joda.time.Duration; |
61 | 66 | import org.joda.time.Instant; |
62 | 67 | import org.junit.Before; |
@@ -774,4 +779,69 @@ public void mutateRowWithHardTimeoutErrorHandling() |
774 | 779 | RuntimeException.class, |
775 | 780 | () -> daoWithMock.mutateRowWithHardTimeout(RowMutation.create("test", "test").deleteRow())); |
776 | 781 | } |
| 782 | + |
| 783 | + @Test |
| 784 | + public void readAllStreamPartitionRowsOnlyReadsLatestVersion() |
| 785 | + throws InvalidProtocolBufferException { |
| 786 | + ByteStringRange partition1 = ByteStringRange.create("A", "B"); |
| 787 | + Instant watermark1 = Instant.now(); |
| 788 | + PartitionRecord partitionRecord1 = |
| 789 | + new PartitionRecord(partition1, watermark1, "1", watermark1, Collections.emptyList(), null); |
| 790 | + metadataTableDao.lockAndRecordPartition(partitionRecord1); |
| 791 | + |
| 792 | + ByteStringRange partition2 = ByteStringRange.create("B", "D"); |
| 793 | + ChangeStreamContinuationToken partition2Token1 = |
| 794 | + ChangeStreamContinuationToken.create(ByteStringRange.create("B", "C"), "tokenBC"); |
| 795 | + ChangeStreamContinuationToken partition2Token2 = |
| 796 | + ChangeStreamContinuationToken.create(ByteStringRange.create("C", "D"), "tokenCD"); |
| 797 | + Instant watermark2 = Instant.now(); |
| 798 | + PartitionRecord partitionRecord2 = |
| 799 | + new PartitionRecord( |
| 800 | + partition2, |
| 801 | + Arrays.asList(partition2Token1, partition2Token2), |
| 802 | + "2", |
| 803 | + watermark2, |
| 804 | + Collections.emptyList(), |
| 805 | + null); |
| 806 | + metadataTableDao.lockAndRecordPartition(partitionRecord2); |
| 807 | + |
| 808 | + // Update the watermark of partition1 |
| 809 | + Instant watermark3 = watermark2.plus(Duration.standardSeconds(10)); |
| 810 | + ChangeStreamContinuationToken partition1Token1 = |
| 811 | + ChangeStreamContinuationToken.create(ByteStringRange.create("A", "B"), "token1"); |
| 812 | + metadataTableDao.updateWatermark(partition1, watermark3, partition1Token1); |
| 813 | + Instant watermark4 = watermark3.plus(Duration.standardSeconds(10)); |
| 814 | + ChangeStreamContinuationToken partition1Token2 = |
| 815 | + ChangeStreamContinuationToken.create(ByteStringRange.create("A", "B"), "token2"); |
| 816 | + metadataTableDao.updateWatermark(partition1, watermark4, partition1Token2); |
| 817 | + |
| 818 | + ServerStream<Row> rows = metadataTableDao.readAllStreamPartitionRows(); |
| 819 | + Map<ByteString, Row> rowsByKey = new HashMap<>(); |
| 820 | + for (Row row : rows) { |
| 821 | + rowsByKey.put(row.getKey(), row); |
| 822 | + } |
| 823 | + Row partition1Row = |
| 824 | + rowsByKey.get(metadataTableDao.convertPartitionToStreamPartitionRowKey(partition1)); |
| 825 | + Row partition2Row = |
| 826 | + rowsByKey.get(metadataTableDao.convertPartitionToStreamPartitionRowKey(partition2)); |
| 827 | + |
| 828 | + List<ChangeStreamContinuationToken> initialTokens = |
| 829 | + parseInitialContinuationTokens(partition2Row); |
| 830 | + // Make sure we get all initial tokens back |
| 831 | + assertEquals(partition2Token1, initialTokens.get(0)); |
| 832 | + assertEquals(partition2Token2, initialTokens.get(1)); |
| 833 | + // check we only get one watermark and token version even though we've added multiple |
| 834 | + List<RowCell> watermarks = |
| 835 | + partition1Row.getCells( |
| 836 | + MetadataTableAdminDao.CF_WATERMARK, MetadataTableAdminDao.QUALIFIER_DEFAULT); |
| 837 | + assertEquals(1, watermarks.size()); |
| 838 | + Instant parsedWatermark = |
| 839 | + Instant.ofEpochMilli(Longs.fromByteArray(watermarks.get(0).getValue().toByteArray())); |
| 840 | + assertEquals(watermark4, parsedWatermark); |
| 841 | + List<RowCell> tokens = |
| 842 | + partition1Row.getCells( |
| 843 | + MetadataTableAdminDao.CF_CONTINUATION_TOKEN, MetadataTableAdminDao.QUALIFIER_DEFAULT); |
| 844 | + assertEquals(1, tokens.size()); |
| 845 | + assertEquals(partition1Token2.getToken(), tokens.get(0).getValue().toStringUtf8()); |
| 846 | + } |
777 | 847 | } |
0 commit comments