|
26 | 26 | import org.apache.paimon.fs.local.LocalFileIO; |
27 | 27 | import org.apache.paimon.manifest.PartitionEntry; |
28 | 28 | import org.apache.paimon.options.Options; |
| 29 | +import org.apache.paimon.predicate.Predicate; |
29 | 30 | import org.apache.paimon.predicate.PredicateBuilder; |
30 | 31 | import org.apache.paimon.schema.Schema; |
31 | 32 | import org.apache.paimon.schema.SchemaManager; |
|
34 | 35 | import org.apache.paimon.table.sink.StreamTableCommit; |
35 | 36 | import org.apache.paimon.table.sink.StreamTableWrite; |
36 | 37 | import org.apache.paimon.table.source.DataTableScan; |
| 38 | +import org.apache.paimon.table.source.InnerTableRead; |
37 | 39 | import org.apache.paimon.table.source.Split; |
| 40 | +import org.apache.paimon.table.source.TableRead; |
38 | 41 | import org.apache.paimon.types.DataType; |
39 | 42 | import org.apache.paimon.types.DataTypes; |
40 | 43 | import org.apache.paimon.types.RowType; |
41 | 44 | import org.apache.paimon.utils.Pair; |
42 | 45 | import org.apache.paimon.utils.TraceableFileIO; |
43 | 46 |
|
44 | 47 | import org.junit.jupiter.api.BeforeEach; |
| 48 | +import org.junit.jupiter.api.Test; |
45 | 49 | import org.junit.jupiter.api.io.TempDir; |
46 | 50 | import org.junit.jupiter.params.ParameterizedTest; |
47 | 51 | import org.junit.jupiter.params.provider.ValueSource; |
| 52 | +import org.mockito.Mockito; |
48 | 53 |
|
| 54 | +import java.io.IOException; |
49 | 55 | import java.util.Collections; |
50 | 56 | import java.util.List; |
51 | 57 | import java.util.UUID; |
52 | 58 | import java.util.stream.Collectors; |
53 | 59 |
|
54 | 60 | import static org.apache.paimon.table.SchemaEvolutionTableTestBase.rowData; |
55 | 61 | import static org.assertj.core.api.Assertions.assertThat; |
| 62 | +import static org.assertj.core.api.Assertions.assertThatThrownBy; |
56 | 63 |
|
57 | 64 | /** Tests for {@link FallbackReadFileStoreTable}. */ |
58 | 65 | public class FallbackReadFileStoreTableTest { |
@@ -242,6 +249,89 @@ public void testWriteGoesToWrapped(boolean wrappedFirst) throws Exception { |
242 | 249 | assertThat(mergedPartitions).containsExactlyInAnyOrder(1, 2, 3); |
243 | 250 | } |
244 | 251 |
|
| 252 | + @Test |
| 253 | + public void testFallbackReadFailFastDefaultSwallowsException() throws Exception { |
| 254 | + FallbackReadFileStoreTable table = setUpTableWithThrowingFallback(false); |
| 255 | + Split split = onlyFallbackSplit(table); |
| 256 | + |
| 257 | + // Default behavior: the failing fallback read is swallowed and the reader |
| 258 | + // falls through to the main branch, which has no data for partition 3 and |
| 259 | + // either returns an empty reader or throws something other than the |
| 260 | + // injected fallback exception. |
| 261 | + try { |
| 262 | + table.newRead().createReader(split); |
| 263 | + } catch (Exception e) { |
| 264 | + assertThat(e.getMessage()) |
| 265 | + .as("fallback exception must not propagate when fail-fast is disabled") |
| 266 | + .doesNotContain("injected fallback failure"); |
| 267 | + } |
| 268 | + } |
| 269 | + |
| 270 | + @Test |
| 271 | + public void testFallbackReadFailFastPropagatesException() throws Exception { |
| 272 | + FallbackReadFileStoreTable table = setUpTableWithThrowingFallback(true); |
| 273 | + Split split = onlyFallbackSplit(table); |
| 274 | + |
| 275 | + assertThatThrownBy(() -> table.newRead().createReader(split)) |
| 276 | + .hasMessageContaining("injected fallback failure"); |
| 277 | + } |
| 278 | + |
| 279 | + private FallbackReadFileStoreTable setUpTableWithThrowingFallback(boolean failFast) |
| 280 | + throws Exception { |
| 281 | + String branchName = "bc"; |
| 282 | + FileStoreTable mainTable = createTable(); |
| 283 | + writeDataIntoTable(mainTable, 0, rowData(1, 10)); |
| 284 | + mainTable.createBranch(branchName); |
| 285 | + FileStoreTable branchTable = createTableFromBranch(mainTable, branchName); |
| 286 | + writeDataIntoTable(branchTable, 0, rowData(3, 60)); |
| 287 | + |
| 288 | + Options overrides = new Options(); |
| 289 | + overrides.set(CoreOptions.SCAN_FALLBACK_BRANCH_READ_FAIL_FAST, failFast); |
| 290 | + FileStoreTable mainWithOption = mainTable.copy(overrides.toMap()); |
| 291 | + |
| 292 | + FileStoreTable spyBranch = Mockito.spy(branchTable); |
| 293 | + InnerTableRead throwing = throwingInnerTableRead(); |
| 294 | + Mockito.doReturn(throwing).when(spyBranch).newRead(); |
| 295 | + |
| 296 | + return new FallbackReadFileStoreTable(mainWithOption, spyBranch, true); |
| 297 | + } |
| 298 | + |
| 299 | + private static Split onlyFallbackSplit(FallbackReadFileStoreTable table) { |
| 300 | + DataTableScan scan = table.newScan(); |
| 301 | + scan.withFilter(new PredicateBuilder(ROW_TYPE).equal(0, 3)); |
| 302 | + List<Split> splits = scan.plan().splits(); |
| 303 | + assertThat(splits).hasSize(1); |
| 304 | + FallbackReadFileStoreTable.FallbackSplit fs = |
| 305 | + (FallbackReadFileStoreTable.FallbackSplit) splits.get(0); |
| 306 | + assertThat(fs.isFallback()).isTrue(); |
| 307 | + return splits.get(0); |
| 308 | + } |
| 309 | + |
| 310 | + private static InnerTableRead throwingInnerTableRead() { |
| 311 | + return new InnerTableRead() { |
| 312 | + @Override |
| 313 | + public InnerTableRead withFilter(Predicate predicate) { |
| 314 | + return this; |
| 315 | + } |
| 316 | + |
| 317 | + @Override |
| 318 | + public InnerTableRead withReadType(RowType readType) { |
| 319 | + return this; |
| 320 | + } |
| 321 | + |
| 322 | + @Override |
| 323 | + public TableRead withIOManager(org.apache.paimon.disk.IOManager ioManager) { |
| 324 | + return this; |
| 325 | + } |
| 326 | + |
| 327 | + @Override |
| 328 | + public org.apache.paimon.reader.RecordReader<InternalRow> createReader(Split split) |
| 329 | + throws IOException { |
| 330 | + throw new IOException("injected fallback failure"); |
| 331 | + } |
| 332 | + }; |
| 333 | + } |
| 334 | + |
245 | 335 | private void writeDataIntoTable( |
246 | 336 | FileStoreTable table, long commitIdentifier, InternalRow... allData) throws Exception { |
247 | 337 | StreamTableWrite write = table.newWrite(commitUser); |
|
0 commit comments