Skip to content

Add a black-box RTAS behavior test suite - #657

Draft
mkuchenbecker wants to merge 10 commits into
linkedin:mainfrom
mkuchenbecker:mkuchenbecker/r5-pin-rtas-spec-drop
Draft

Add a black-box RTAS behavior test suite#657
mkuchenbecker wants to merge 10 commits into
linkedin:mainfrom
mkuchenbecker:mkuchenbecker/r5-pin-rtas-spec-drop

Conversation

@mkuchenbecker

@mkuchenbecker mkuchenbecker commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

A cohesive set of black-box RTAS tests, driven through Spark SQL against a real embedded OpenHouse
server (RtasBehaviorTest). RTAS (CREATE OR REPLACE TABLE ... AS SELECT) defines a brand new table
body, so it is intentionally allowed schema/partition evolutions the incremental ALTER TABLE path
forbids, while still preserving the table's identity. The suite pins that contract so a future change
can't silently apply the update-path guards to the replace path or lose the table identity.

Tests (RtasBehaviorTest)

  • testRtasMayDropColumn — RTAS may drop a column.
  • testRtasMayAddColumn — RTAS may add a column.
  • testRtasMayChangePartitionSpec — RTAS may repartition by a different column.
  • testRtasMayRemovePartitioning — RTAS may replace a partitioned table with an unpartitioned body.
  • testRtasReplacesData — the row set after RTAS reflects the SELECT.
  • testRtasPreservesTableIdentityopenhouse.tableUUID is preserved across the replace.
  • testRtasRejectedWhenReplaceNotEnabled — RTAS is rejected unless replace.enabled is set.

Testing Done

  • RtasBehaviorTest (all 7) — green on the spark-3.5 itest. Spotless clean.

RTAS defines a new table body, so it is intentionally allowed to drop columns
and repartition — evolutions the incremental ALTER path forbids. Add a
black-box SQL pin test so a future change can't silently start applying the
update-path schema/partition guards to the replace path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mkuchenbecker mkuchenbecker changed the title Pin RTAS spec-change / column-drop as intended behavior (test-only) RTAS spec-change / column-drop tests Jul 24, 2026
Replace the single spec-change/column-drop pin with RtasBehaviorTest, a cohesive
set of black-box SQL tests (real embedded server) for CREATE OR REPLACE TABLE AS
SELECT: may drop a column, add a column, change the partition spec, remove
partitioning, replace the data, preserve the table identity (openhouse.tableUUID),
and is rejected when replace.enabled is not set.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mkuchenbecker mkuchenbecker changed the title RTAS spec-change / column-drop tests Add a black-box RTAS behavior test suite Jul 24, 2026
Broadens the RTAS integration coverage per the RTAS product spec, all as
black-box Spark SQL against the embedded OpenHouse server.

RtasBehaviorTest (+4):
- orc->parquet file-format change via RTAS lands parquet data files
- encryption-off via RTAS is permitted and leaves the table readable
- pre-replace snapshot stays readable via VERSION AS OF on a disconnected
  timeline (old schema+body); pins current behavior vs the spec future
  time-travel-should-error gate
- restore to a pre-replace snapshot works, with data loss on the replaced body

RtasDmlAfterReplaceTest (new, 7): after a table is replaced via RTAS, ordinary
DML still behaves correctly. Copy-on-write insert/delete/update/merge/insert-
overwrite/read, plus a merge-on-read delete that still emits ORC position
delete files (asserted via .delete_files metadata, matching the existing
DeleteFileReplicationTestSpark strategy to avoid the shaded delete-loader
classpath collision on readback).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
.collect(Collectors.toMap(r -> r.getString(0), r -> r.getString(1)));
}

private static List<String> dataFilePaths(SparkSession spark, String table) {

@mkuchenbecker mkuchenbecker Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

used in one funciton, not needed ot be a helper.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (7dbce83) — inlined dataFilePaths (it was only used in this one test).

.collect(Collectors.toList());
}

private static Map<String, String> tableProperties(SparkSession spark, String table) {

@mkuchenbecker mkuchenbecker Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the helper and inline.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tableProperties is used in 5 tests, so I kept it as a shared helper rather than duplicate the same SHOW TBLPROPERTIES stream in each. I inlined the genuinely single-use helpers (dataFilePaths, tableArg). Happy to inline this one too if you'd rather. (7dbce83)

spark.sql("ALTER TABLE " + table + " SET TBLPROPERTIES ('replace.enabled'='true')");
}

private static List<String> columnsOf(SparkSession spark, String table) {

@mkuchenbecker mkuchenbecker Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the helper and inline.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

columnsOf is used in 3 tests — same reasoning as the tableProperties thread: kept it shared to avoid duplicating the DESCRIBE TABLE stream. Single-use helpers were inlined. (7dbce83)

@@ -0,0 +1,232 @@
package com.linkedin.openhouse.spark.catalogtest;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this in 3.5 dir vs 3.1? I would prefer all tests added in 3.5.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved — all RTAS tests now live in the spark-3.5 itest dir (3.5-only). This file (RtasDmlAfterReplaceTest) is deleted; its coverage is folded into the new RtasDmlMatrixTest. (7dbce83)

* <p>The outcome is asserted purely from the {@code .delete_files} metadata table. The table body
* is intentionally NOT read back: applying position deletes on read goes through Iceberg's own
* (shaded) delete-loader, which collides with the unshaded Parquet/ORC classes on this shared
* integration-test task's classpath. The dedicated {@code DeleteFileReplicationTestSpark} uses

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test with ORC too. Fix the classpath issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed the root cause rather than working around it. The itest classpath carried both the shaded ORC bundled in the runtime uber jar (org.apache.iceberg.shaded...orc) and the unshaded iceberg-data/orc-core, so Iceberg's merge-on-read delete loader mixed the two on read-back (the ClassCastException). Excluded iceberg-data from the module (same as apps/spark-3.5), so merge-on-read now reads back on both ORC and Parquet. The new matrix exercises ORC × merge-on-read across the full DML set. (7dbce83)

Comment on lines +356 to +365
List<Row> restored =
spark.sql("SELECT id, data FROM " + table + " ORDER BY id").collectAsList();
assertEquals(2, restored.size(), "restore should bring back the pre-replace row count");
assertEquals(
1L, restored.get(0).getLong(0), "restore should bring back the pre-replace body");
assertEquals(
2L, restored.get(1).getLong(0), "restore should bring back the pre-replace body");
assertTrue(
spark.sql("SELECT id FROM " + table + " WHERE id = 100").collectAsList().isEmpty(),
"the replaced body should be lost after restoring the earlier snapshot");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you need to test DML after replace. You have not applied teh lessons of the framework. Write a Set of DML tests that take in a table. That should work normally, after rtas, after rtas+restore, with or without MoR enabled.

The test for dml is "i have a table, do dml tests" and we are preparing different tables for that. Its fine to test the restore behaviour like we are, but DML needs to be tested after the restore.

I see it as (BaseTable, BaseTAble + RTAS, BaseTable + RTAS + Restore, (Basetable + Mor), (Basetable + Mor) +RTAS,...).foreach(table -> runAllDmlTests(Table))

We get to the list of preparations by multiplexing over properties.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebuilt as RtasDmlMatrixTest. Table preparations are multiplexed over properties — write mode (copy-on-write / merge-on-read) × file format (Parquet / ORC) × partitioning × lifecycle (base, RTAS, RTAS+restore) — and the same DML set (read / insert / delete / update / merge / insert-overwrite) runs against every preparation. Every prep converges to the same canonical seed, so one assertion is valid for all of them: 144 cases (155 RTAS tests total). DML is now tested after RTAS and after RTAS+restore, with and without merge-on-read.

One behavior note: with PARTITIONED BY (data) each seed row is its own single-row file, so a single-row merge-on-read DELETE removes the whole file as a metadata-only delete (no position delete) — correct Iceberg behavior — so the delete-file assertion is scoped to the unpartitioned cases; row-level correctness is asserted everywhere. (7dbce83)

…ck classpath

Addresses the review feedback on the RTAS test suite:

- Fix the merge-on-read read-back classpath collision instead of working
  around it: exclude iceberg-data from the spark-3.5 itest classpath (as
  apps/spark-3.5 already does) so the shaded ORC in the runtime uber jar no
  longer clashes with the unshaded ORC used by Iceberg's delete loader. This
  lets merge-on-read ORC/Parquet tables read back correctly in-module.

- Move the RTAS tests into the spark-3.5 itest dir (3.5-only) and inline the
  single-use helpers in RtasBehaviorTest.

- Replace the one-off DML test with RtasDmlMatrixTest: a parameterized matrix
  multiplexing table preparations over write mode (copy-on-write / merge-on-
  read), file format (Parquet / ORC), partitioning, and lifecycle (base /
  RTAS / RTAS+restore), running the same DML set (read/insert/delete/update/
  merge/insert-overwrite) against every preparation. Every preparation
  converges to the same canonical seed, so one assertion is valid for all;
  144 cases. Merge-on-read delete-file production is asserted for the
  unpartitioned cases (a single-row-per-file partitioned delete is a
  whole-file metadata delete and correctly writes no position delete).

RtasBehaviorTest (11) + RtasDmlMatrixTest (144) = 155 RTAS cases; full
catalogTest green on spark-3.5.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment on lines +97 to +101
for (WriteMode writeMode : WriteMode.values()) {
for (FileFormat format : FileFormat.values()) {
for (Partitioning partitioning : Partitioning.values()) {
for (Lifecycle lifecycle : Lifecycle.values()) {
for (DmlOp op : DmlOp.values()) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

map and stream functions, not nested fors.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — cases() is now a flatMap cartesian product over the axis enums, no nested loops. (621dc27)

Comment on lines +81 to +83
BASE,
RTAS,
RTAS_RESTORE;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add block comment explanations

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added block comments on each axis enum, the preparation thunks, withTable, and the delete-file gating. (621dc27)

}

/** Builds a table that ends holding exactly the canonical seed rows, per the requested axes. */
private void prepareSeededTable(

@mkuchenbecker mkuchenbecker Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rather than switch on prepare seeded table just treat each preparation as a lamba that creates a table. A thunk, this can ensure the table is cleaned up. and manage the lifecycle

withTable(params) {
table -> {
table with scoped lifetime
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Each Lifecycle constant is now a preparation thunk (prepare(...) builds+seeds the table), and withTable(axes, table -> { ... }) gives the table a scoped lifetime — it's always dropped in finally. The prepare switch is gone. (621dc27)

boolean expectDeleteFiles =
writeMode.isMergeOnRead() && partitioning == Partitioning.UNPARTITIONED;
switch (op) {
case READ:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this CASE seems overly verbose. I would remove this runDml function.

if you have the test tables you could to a testTables.foreach(TestInsert) vs a "run DML" function.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed runDml. Each DML operation is now a DmlCheck enum constant with its own run(...) that does the op and asserts, so the test body is just "prepare a table, apply the op". (621dc27)

@mkuchenbecker mkuchenbecker left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall I think the structure needs work. Think of it more funcitonal than data driven.

Build up the set of tables as preparations. For each preparaation, run each DML test (each can be their own test). The whole point is that we have a list of preparations that can be plugged into each dml test and it should work.

mkuchenbecker and others added 2 commits July 24, 2026 13:43
Addresses the review feedback on RtasDmlMatrixTest:

- cases() is now a stream/flatMap cartesian product instead of nested for
  loops.
- Table preparations are thunks: each Lifecycle constant knows how to
  build+seed its table, and a withTable(...) { table -> ... } helper gives the
  table a scoped lifetime and always drops it, replacing the prepare switch.
- DML operations are functions: each DmlCheck constant performs one operation
  and asserts its outcome, replacing the runDml switch. The test body is just
  "prepare a table, apply the op".
- Added block-comment explanations on the axes, the preparation thunks, and
  the delete-file gating.

No behavior change: still 144 cases, all green; teeth-check reproduces the
same 36-failure signature (24 DELETE row-level + 12 merge-on-read
unpartitioned update/merge delete-file) under mutation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

/** The cartesian product of every axis, one JUnit case per combination. */
static Stream<Arguments> cases() {
return Arrays.stream(WriteMode.values())

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesn't need to be one giant statement. reduce nesting

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The giant nested statement is gone: with each DML operation now a composable check run in sequence on one prepared table, the parameter source is just preparations() (the 4 preparation axes), and the per-op DmlCheck/runDml dispatch was removed entirely. (982bc1f)

table -> {
// Sanity: every preparation must converge to the canonical seed before DML runs.
assertRows(spark, table, SEED_ROWS);
op.run(spark, table, expectDeleteFiles(writeMode, partitioning));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just inline and remove dmlcheck

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed DmlCheck. Each DML operation is now an inline composable check (readCheck/insertCheck/deleteCheck/updateCheck/mergeCheck/insertOverwriteCheck) applied directly to the prepared table. (982bc1f)

* delete) — correct Iceberg behavior — hence delete files are only expected for the unpartitioned
* merge-on-read cases, where the seed rows share a data file.
*/
private static boolean expectDeleteFiles(WriteMode writeMode, Partitioning partitioning) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get rid of this pattern. This is logic embedded in a test. The test construciton should do the assertion even if we end up duplicating some setup.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed. There's no expectDeleteFiles flag / conditional-assert pattern anymore — merge-on-read delete-file production is asserted only in the dedicated mergeOnRead*WritesDeleteFiles tests, which are constructed for exactly the case (unpartitioned merge-on-read) that produces position delete files. (982bc1f)

+ ")");
}

private static void insertValues(SparkSession spark, String table, String values) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove helper

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the rewrite. The composable-check redesign removed the shared helper this referred to (the preparation is now a plain switch calling the composed steps, and the DML checks are inline). (982bc1f)

return writeMode.isMergeOnRead() && partitioning == Partitioning.UNPARTITIONED;
}

private static void createTable(SparkSession spark, String table, String using, String props) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove helper

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the rewrite. The composable-check redesign removed the shared helper this referred to (the preparation is now a plain switch calling the composed steps, and the DML checks are inline). (982bc1f)

spark.sql("INSERT INTO " + table + " VALUES " + values);
}

private static void replaceAsSelect(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove helper

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the rewrite. The composable-check redesign removed the shared helper this referred to (the preparation is now a plain switch calling the composed steps, and the DML checks are inline). (982bc1f)

+ " AS s(id, data)");
}

private static void restoreToSnapshot(SparkSession spark, String table, long snapshotId) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove helper

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the rewrite. The composable-check redesign removed the shared helper this referred to (the preparation is now a plain switch calling the composed steps, and the DML checks are inline). (982bc1f)

+ ")");
}

private static long latestSnapshotId(SparkSession spark, String table) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove helper

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the rewrite. The composable-check redesign removed the shared helper this referred to (the preparation is now a plain switch calling the composed steps, and the DML checks are inline). (982bc1f)

.getLong(0);
}

private static String tableProps(WriteMode writeMode, FileFormat format) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove helper

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the rewrite. The composable-check redesign removed the shared helper this referred to (the preparation is now a plain switch calling the composed steps, and the DML checks are inline). (982bc1f)

}

private static void assertDeleteFilesPresent(
SparkSession spark, String table, boolean expectDeleteFiles) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't assert if you don;t expect delete files.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. The general DML checks no longer assert on delete files at all; only the dedicated unpartitioned merge-on-read tests — which are built specifically to produce position delete files — assert they exist. (982bc1f)

mkuchenbecker and others added 2 commits July 24, 2026 14:16
Follow-up to the review: remove the remaining abstraction.

- Removed the DmlCheck enum: each DML operation is now its own explicit test
  (read/insert/delete/update/merge/insertOverwrite), parameterized over the
  table preparations, doing its own SQL and assertion directly.
- Inlined the small SQL builder helpers (createTable/insertValues/
  replaceAsSelect/restore/latestSnapshot/tableProps) into the Lifecycle
  preparations and withTable.
- Replaced the expectDeleteFiles/assertDeleteFilesPresent conditional (logic
  embedded in a shared test) with dedicated merge-on-read tests, constructed
  for the case that produces position delete files (unpartitioned MoR), that
  assert unconditionally.
- Reduced the preparations() stream nesting (no DmlCheck level) and kept the
  cartesian product as stream functions.

162 cases (144 row-correctness across preparations + 18 merge-on-read
delete-file), all green; teeth-check reproduces 42 failures under mutation
(24 row + 18 delete-file), reverts to green.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rop' into mkuchenbecker/r5-pin-rtas-spec-drop
*/
RTAS_RESTORE {
@Override
void prepare(SparkSession spark, String table, String using, String props) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make props kv

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Table properties are now authored as a key/value Map<String,String> (tableProps) and rendered into the TBLPROPERTIES (...) body at the SQL boundary (tblProperties), instead of a hand-concatenated string. (982bc1f)

* back the seed and drops the junk body.
*/
RTAS_RESTORE {
@Override

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

override is a smell here. you have over-abstracted. This should just be a function call.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Removed the enum abstract-method override; Lifecycle is now a plain enum and prepare(...) dispatches with a simple switch that calls the composed steps directly — just a function call, no anonymous-subclass hierarchy. (982bc1f)


/**
* How the table under test came to exist. Each constant builds a table and leaves it holding
* exactly the canonical seed rows, so the DML assertions are identical across all three.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assersions should be delta-based not absolute. This is too brittle. Tests should compose.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. The DML checks are now delta-based and compose: each check asserts its own effect relative to the current table state (a count delta, or a row it just wrote), so they're order-independent and repeatable — insertCheck(t); insertCheck(t) both pass — and the whole sequence runs against a single prepared table. (982bc1f)

mkuchenbecker and others added 2 commits July 24, 2026 15:05
Reworks the DML matrix around composition. Each DML operation is a
composable check that asserts its own effect relative to the current table
state (a count delta, a row it just wrote) rather than an absolute
post-preparation snapshot, so the checks are order-independent and
repeatable: insertCheck(t); insertCheck(t) both pass. A single prepared
table is therefore handed through the whole sequence of checks.

- One @ParameterizedTest per preparation (writeMode x fileFormat x
  partitioning x lifecycle) runs read/insert/insert-again/delete/update/
  merge/insertOverwrite in sequence on the one table.
- A preparation is composed from small steps (createSeeded, rtas, restore);
  a Lifecycle is just a particular composition of them.
- Merge-on-read delete-file production stays in dedicated unpartitioned-MoR
  tests, where a partial-file delete actually writes position deletes.

The fully multiplied matrix is the coverage (preparations x DML checks);
the JUnit invocation count is not gamed. All green; every composable check
and the delete-file assertion were teeth-checked (each detected a mutation).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Addresses review feedback on RtasDmlMatrixTest:
- Replace the Lifecycle enum's per-constant abstract-method override with a
  plain switch in prepare() — the preparation is just a function call, not an
  anonymous-subclass hierarchy.
- Author table properties as a key/value Map and render them to the
  TBLPROPERTIES body at the SQL boundary, instead of hand-concatenating a
  properties string.

No behavior change: 42 invocations green (144 row-correctness + 18 merge-on-read
delete-file); the merge-on-read delete-file tests continue to pass, which
confirms the key/value properties render correctly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant