88package org .elasticsearch .xpack .esql ;
99
1010import org .apache .lucene .util .BytesRef ;
11+ import org .elasticsearch .common .Strings ;
1112import org .elasticsearch .common .time .DateFormatter ;
1213import org .elasticsearch .compute .data .Page ;
1314import 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