|
3 | 3 | import java.sql.SQLException; |
4 | 4 | import java.util.AbstractMap; |
5 | 5 | import java.util.ArrayList; |
| 6 | +import java.util.LinkedHashMap; |
6 | 7 | import java.util.List; |
7 | 8 | import java.util.Map; |
| 9 | +import java.util.Objects; |
| 10 | +import java.util.Set; |
| 11 | +import java.util.TreeSet; |
8 | 12 |
|
9 | 13 | import sqlancer.IgnoreMeException; |
10 | 14 | import sqlancer.Randomly; |
@@ -61,6 +65,7 @@ public class EETDMLOracle<E extends Expression<C>, S extends AbstractSchema<?, T |
61 | 65 | private final EETTransformer<E, ?> transformer; |
62 | 66 | private final ExpectedErrors errors; |
63 | 67 |
|
| 68 | + private static final int MAX_DIFF_ROWS_REPORTED = 10; // max differing post-image rows displayed in report log |
64 | 69 | private String generatedQueryString; |
65 | 70 |
|
66 | 71 | public EETDMLOracle(G state, EETDMLGenerator<E, T, C> gen, ExpectedErrors expectedErrors) { |
@@ -137,8 +142,8 @@ public void check() throws SQLException { |
137 | 142 | List<List<String>> transformedImage = executeAndSnapshotPostImage(table, transformedStatement, columnCount); |
138 | 143 |
|
139 | 144 | if (!originalImage.equals(transformedImage)) { |
140 | | - throw new AssertionError( |
141 | | - mismatchMessage(originalStatement, transformedStatement, originalImage, transformedImage)); |
| 145 | + throw new AssertionError(mismatchMessage(table, originalStatement, transformedStatement, originalImage, |
| 146 | + transformedImage)); |
142 | 147 | } |
143 | 148 | } finally { |
144 | 149 | new SQLQueryAdapter(gen.dropRowIdColumnStatement(table), errors, true).execute(state); |
@@ -234,14 +239,56 @@ private List<List<String>> snapshotPostImage(String selectStatement, int columnC |
234 | 239 | return rows; |
235 | 240 | } |
236 | 241 |
|
237 | | - private static String mismatchMessage(String originalStatement, String transformedStatement, |
| 242 | + private String mismatchMessage(T table, String originalStatement, String transformedStatement, |
238 | 243 | List<List<String>> originalImage, List<List<String>> transformedImage) { |
239 | | - return new StringBuilder() |
240 | | - .append("-- The original and transformed statements left the database in different states") |
241 | | - .append(" (different post-images):").append(System.lineSeparator()).append("-- original (") |
242 | | - .append(originalImage.size()).append(" rows): ").append(originalStatement).append(';') |
243 | | - .append(System.lineSeparator()).append("-- transformed (").append(transformedImage.size()) |
244 | | - .append(" rows): ").append(transformedStatement).append(';').append(System.lineSeparator()).toString(); |
| 244 | + List<String> header = new ArrayList<>(); |
| 245 | + header.add(EETDMLGenerator.ROW_ID_COLUMN); |
| 246 | + for (C column : table.getColumns()) { |
| 247 | + header.add(column.getName()); |
| 248 | + } |
| 249 | + |
| 250 | + Map<String, List<String>> originalByRowId = indexByRowId(originalImage); |
| 251 | + Map<String, List<String>> transformedByRowId = indexByRowId(transformedImage); |
| 252 | + Set<String> allRowIds = new TreeSet<>(); |
| 253 | + allRowIds.addAll(originalByRowId.keySet()); |
| 254 | + allRowIds.addAll(transformedByRowId.keySet()); |
| 255 | + |
| 256 | + String nl = System.lineSeparator(); |
| 257 | + StringBuilder message = new StringBuilder() |
| 258 | + .append("-- The original and transformed statements left the database in different states.").append(nl) |
| 259 | + .append("-- original: ").append(originalStatement).append(';').append(nl).append("-- transformed: ") |
| 260 | + .append(transformedStatement).append(';').append(nl).append("-- differing post-image rows (") |
| 261 | + .append(String.join(", ", header)).append("):").append(nl); |
| 262 | + int shown = 0; |
| 263 | + for (String rowId : allRowIds) { |
| 264 | + List<String> originalRow = originalByRowId.get(rowId); |
| 265 | + List<String> transformedRow = transformedByRowId.get(rowId); |
| 266 | + if (Objects.equals(originalRow, transformedRow)) { |
| 267 | + continue; |
| 268 | + } |
| 269 | + if (shown == MAX_DIFF_ROWS_REPORTED) { |
| 270 | + message.append("-- ... (further differences omitted)").append(nl); |
| 271 | + break; |
| 272 | + } |
| 273 | + message.append("-- original: ").append(renderRow(originalRow)).append(nl); |
| 274 | + message.append("-- transformed: ").append(renderRow(transformedRow)).append(nl); |
| 275 | + shown++; |
| 276 | + } |
| 277 | + return message.toString(); |
| 278 | + } |
| 279 | + |
| 280 | + // Indexes a post-image by its row identifier (the first column of each row) |
| 281 | + private static Map<String, List<String>> indexByRowId(List<List<String>> image) { |
| 282 | + Map<String, List<String>> byRowId = new LinkedHashMap<>(); |
| 283 | + for (List<String> row : image) { |
| 284 | + byRowId.put(row.get(0), row); |
| 285 | + } |
| 286 | + return byRowId; |
| 287 | + } |
| 288 | + |
| 289 | + // Renders a post-image row for the finding message, or "(row absent)" when the row is missing on that side |
| 290 | + private static String renderRow(List<String> row) { |
| 291 | + return row == null ? "(row absent)" : row.toString(); |
245 | 292 | } |
246 | 293 |
|
247 | 294 | @Override |
|
0 commit comments