Skip to content

Commit caf8afc

Browse files
authored
Render all datatypes in pipetables error output (#118758)
Some types (such as ip or time) require custom formatting logic and were not correctly rendered in actual/expected comparison table. This change: * properly formats such data types * add data types to the column headers * removes outer pipes
1 parent b461baf commit caf8afc

File tree

1 file changed

+98
-54
lines changed
  • x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql

1 file changed

+98
-54
lines changed

x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvAssert.java

Lines changed: 98 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package org.elasticsearch.xpack.esql;
99

1010
import org.apache.lucene.util.BytesRef;
11+
import org.elasticsearch.common.Strings;
1112
import org.elasticsearch.common.time.DateFormatter;
1213
import org.elasticsearch.compute.data.Page;
1314
import org.elasticsearch.logging.Logger;
@@ -197,7 +198,13 @@ public static void assertData(
197198
for (int row = 0; row < expectedValues.size(); row++) {
198199
try {
199200
if (row >= actualValues.size()) {
200-
dataFailure("Expected more data but no more entries found after [" + row + "]", dataFailures, expected, actualValues);
201+
dataFailure(
202+
"Expected more data but no more entries found after [" + row + "]",
203+
dataFailures,
204+
expected,
205+
actualValues,
206+
valueTransformer
207+
);
201208
}
202209

203210
if (logger != null) {
@@ -208,45 +215,17 @@ public static void assertData(
208215
var actualRow = actualValues.get(row);
209216

210217
for (int column = 0; column < expectedRow.size(); column++) {
211-
var expectedValue = expectedRow.get(column);
212-
var actualValue = actualRow.get(column);
213218
var expectedType = expected.columnTypes().get(column);
219+
var expectedValue = convertExpectedValue(expectedType, expectedRow.get(column));
220+
var actualValue = actualRow.get(column);
214221

215-
if (expectedValue != null) {
216-
// convert the long from CSV back to its STRING form
217-
if (expectedType == Type.DATETIME) {
218-
expectedValue = rebuildExpected(expectedValue, Long.class, x -> UTC_DATE_TIME_FORMATTER.formatMillis((long) x));
219-
} else if (expectedType == Type.DATE_NANOS) {
220-
expectedValue = rebuildExpected(
221-
expectedValue,
222-
Long.class,
223-
x -> DateFormatter.forPattern("strict_date_optional_time_nanos").formatNanos((long) x)
224-
);
225-
} else if (expectedType == Type.GEO_POINT) {
226-
expectedValue = rebuildExpected(expectedValue, BytesRef.class, x -> GEO.wkbToWkt((BytesRef) x));
227-
} else if (expectedType == Type.CARTESIAN_POINT) {
228-
expectedValue = rebuildExpected(expectedValue, BytesRef.class, x -> CARTESIAN.wkbToWkt((BytesRef) x));
229-
} else if (expectedType == Type.GEO_SHAPE) {
230-
expectedValue = rebuildExpected(expectedValue, BytesRef.class, x -> GEO.wkbToWkt((BytesRef) x));
231-
} else if (expectedType == Type.CARTESIAN_SHAPE) {
232-
expectedValue = rebuildExpected(expectedValue, BytesRef.class, x -> CARTESIAN.wkbToWkt((BytesRef) x));
233-
} else if (expectedType == Type.IP) {
234-
// convert BytesRef-packed IP to String, allowing subsequent comparison with what's expected
235-
expectedValue = rebuildExpected(expectedValue, BytesRef.class, x -> DocValueFormat.IP.format((BytesRef) x));
236-
} else if (expectedType == Type.VERSION) {
237-
// convert BytesRef-packed Version to String
238-
expectedValue = rebuildExpected(expectedValue, BytesRef.class, x -> new Version((BytesRef) x).toString());
239-
} else if (expectedType == UNSIGNED_LONG) {
240-
expectedValue = rebuildExpected(expectedValue, Long.class, x -> unsignedLongAsNumber((long) x));
241-
}
242-
}
243222
var transformedExpected = valueTransformer.apply(expectedType, expectedValue);
244223
var transformedActual = valueTransformer.apply(expectedType, actualValue);
245224
if (Objects.equals(transformedExpected, transformedActual) == false) {
246225
dataFailures.add(new DataFailure(row, column, transformedExpected, transformedActual));
247226
}
248227
if (dataFailures.size() > 10) {
249-
dataFailure("", dataFailures, expected, actualValues);
228+
dataFailure("", dataFailures, expected, actualValues, valueTransformer);
250229
}
251230
}
252231

@@ -255,7 +234,8 @@ public static void assertData(
255234
"Plan has extra columns, returned [" + actualRow.size() + "], expected [" + expectedRow.size() + "]",
256235
dataFailures,
257236
expected,
258-
actualValues
237+
actualValues,
238+
valueTransformer
259239
);
260240
}
261241
} catch (AssertionError ae) {
@@ -267,53 +247,89 @@ public static void assertData(
267247
}
268248
}
269249
if (dataFailures.isEmpty() == false) {
270-
dataFailure("", dataFailures, expected, actualValues);
250+
dataFailure("", dataFailures, expected, actualValues, valueTransformer);
271251
}
272252
if (expectedValues.size() < actualValues.size()) {
273-
dataFailure("Elasticsearch still has data after [" + expectedValues.size() + "] entries", dataFailures, expected, actualValues);
253+
dataFailure(
254+
"Elasticsearch still has data after [" + expectedValues.size() + "] entries",
255+
dataFailures,
256+
expected,
257+
actualValues,
258+
valueTransformer
259+
);
274260
}
275261
}
276262

277263
private static void dataFailure(
278264
String description,
279265
List<DataFailure> dataFailures,
280266
ExpectedResults expectedValues,
281-
List<List<Object>> actualValues
267+
List<List<Object>> actualValues,
268+
BiFunction<Type, Object, Object> valueTransformer
282269
) {
283-
var expected = pipeTable("Expected:", expectedValues.columnNames(), expectedValues.values(), 25);
284-
var actual = pipeTable("Actual:", expectedValues.columnNames(), actualValues, 25);
270+
var expected = pipeTable(
271+
"Expected:",
272+
expectedValues.columnNames(),
273+
expectedValues.columnTypes(),
274+
expectedValues.values(),
275+
(type, value) -> valueTransformer.apply(type, convertExpectedValue(type, value))
276+
);
277+
var actual = pipeTable("Actual:", expectedValues.columnNames(), expectedValues.columnTypes(), actualValues, valueTransformer);
285278
fail(description + System.lineSeparator() + describeFailures(dataFailures) + actual + expected);
286279
}
287280

288-
private static String pipeTable(String description, List<String> headers, List<List<Object>> values, int maxRows) {
281+
private static final int MAX_ROWS = 25;
282+
283+
private static String pipeTable(
284+
String description,
285+
List<String> headers,
286+
List<Type> types,
287+
List<List<Object>> values,
288+
BiFunction<Type, Object, Object> valueTransformer
289+
) {
290+
int rows = Math.min(MAX_ROWS, values.size());
289291
int[] width = new int[headers.size()];
290-
for (int i = 0; i < width.length; i++) {
291-
width[i] = headers.get(i).length();
292-
for (List<Object> row : values) {
293-
width[i] = Math.max(width[i], String.valueOf(row.get(i)).length());
292+
String[][] printableValues = new String[rows][headers.size()];
293+
for (int c = 0; c < headers.size(); c++) {
294+
width[c] = header(headers.get(c), types.get(c)).length();
295+
}
296+
for (int r = 0; r < rows; r++) {
297+
for (int c = 0; c < headers.size(); c++) {
298+
printableValues[r][c] = String.valueOf(valueTransformer.apply(types.get(c), values.get(r).get(c)));
299+
width[c] = Math.max(width[c], printableValues[r][c].length());
294300
}
295301
}
296302

297303
var result = new StringBuilder().append(System.lineSeparator()).append(description).append(System.lineSeparator());
298-
for (int c = 0; c < width.length; c++) {
299-
appendValue(result, headers.get(c), width[c]);
304+
// headers
305+
appendPaddedValue(result, header(headers.get(0), types.get(0)), width[0]);
306+
for (int c = 1; c < width.length; c++) {
307+
result.append(" | ");
308+
appendPaddedValue(result, header(headers.get(c), types.get(c)), width[c]);
300309
}
301-
result.append('|').append(System.lineSeparator());
302-
for (int r = 0; r < Math.min(maxRows, values.size()); r++) {
303-
for (int c = 0; c < width.length; c++) {
304-
appendValue(result, values.get(r).get(c), width[c]);
310+
result.append(System.lineSeparator());
311+
// values
312+
for (int r = 0; r < printableValues.length; r++) {
313+
appendPaddedValue(result, printableValues[r][0], width[0]);
314+
for (int c = 1; c < printableValues[r].length; c++) {
315+
result.append(" | ");
316+
appendPaddedValue(result, printableValues[r][c], width[c]);
305317
}
306-
result.append('|').append(System.lineSeparator());
318+
result.append(System.lineSeparator());
307319
}
308-
if (values.size() > maxRows) {
320+
if (values.size() > rows) {
309321
result.append("...").append(System.lineSeparator());
310322
}
311323
return result.toString();
312324
}
313325

314-
private static void appendValue(StringBuilder result, Object value, int width) {
315-
result.append('|').append(value);
316-
for (int i = 0; i < width - String.valueOf(value).length(); i++) {
326+
private static String header(String name, Type type) {
327+
return name + ':' + Strings.toLowercaseAscii(type.name());
328+
}
329+
330+
private static void appendPaddedValue(StringBuilder result, String value, int width) {
331+
result.append(value);
332+
for (int i = 0; i < width - (value != null ? value.length() : 4); i++) {
317333
result.append(' ');
318334
}
319335
}
@@ -369,6 +385,34 @@ private static Comparator<List<Object>> resultRowComparator(List<Type> types) {
369385
};
370386
}
371387

388+
private static Object convertExpectedValue(Type expectedType, Object expectedValue) {
389+
if (expectedValue == null) {
390+
return null;
391+
}
392+
393+
// convert the long from CSV back to its STRING form
394+
return switch (expectedType) {
395+
case Type.DATETIME -> rebuildExpected(expectedValue, Long.class, x -> UTC_DATE_TIME_FORMATTER.formatMillis((long) x));
396+
case Type.DATE_NANOS -> rebuildExpected(
397+
expectedValue,
398+
Long.class,
399+
x -> DateFormatter.forPattern("strict_date_optional_time_nanos").formatNanos((long) x)
400+
);
401+
case Type.GEO_POINT, Type.GEO_SHAPE -> rebuildExpected(expectedValue, BytesRef.class, x -> GEO.wkbToWkt((BytesRef) x));
402+
case Type.CARTESIAN_POINT, Type.CARTESIAN_SHAPE -> rebuildExpected(
403+
expectedValue,
404+
BytesRef.class,
405+
x -> CARTESIAN.wkbToWkt((BytesRef) x)
406+
);
407+
case Type.IP -> // convert BytesRef-packed IP to String, allowing subsequent comparison with what's expected
408+
rebuildExpected(expectedValue, BytesRef.class, x -> DocValueFormat.IP.format((BytesRef) x));
409+
case Type.VERSION -> // convert BytesRef-packed Version to String
410+
rebuildExpected(expectedValue, BytesRef.class, x -> new Version((BytesRef) x).toString());
411+
case UNSIGNED_LONG -> rebuildExpected(expectedValue, Long.class, x -> unsignedLongAsNumber((long) x));
412+
default -> expectedValue;
413+
};
414+
}
415+
372416
private static Object rebuildExpected(Object expectedValue, Class<?> clazz, Function<Object, Object> mapper) {
373417
if (List.class.isAssignableFrom(expectedValue.getClass())) {
374418
assertThat(((List<?>) expectedValue).get(0), instanceOf(clazz));

0 commit comments

Comments
 (0)