77package org .elasticsearch .xpack .sql .qa .single_node ;
88
99import com .carrotsearch .randomizedtesting .annotations .ParametersFactory ;
10-
1110import org .elasticsearch .common .CheckedConsumer ;
1211import org .elasticsearch .common .UUIDs ;
1312import org .elasticsearch .common .collect .Tuple ;
1413import org .elasticsearch .xpack .sql .qa .jdbc .JdbcIntegrationTestCase ;
1514
1615import java .io .IOException ;
16+ import java .nio .file .Files ;
17+ import java .nio .file .Path ;
1718import java .sql .ResultSet ;
1819import java .util .ArrayList ;
1920import java .util .LinkedHashMap ;
2324import java .util .Map ;
2425import java .util .Objects ;
2526import java .util .Set ;
26- import java .util .stream .Stream ;
2727
2828import static java .lang .String .join ;
2929import static java .util .Arrays .asList ;
3333import static org .hamcrest .collection .IsEmptyCollection .empty ;
3434
3535/**
36- * <p>This test was introduced because of the inconsistencies regarding NULL argument handling. NULL literal vs NULL field
36+ * <p>This test was introduced because of the inconsistencies regarding NULL argument handling. NULL literal vs NULL field
3737 * value as function arguments in some case result in different function return values.</p>
3838 *
3939 * <p>Functions should return with the same value no matter if the argument(s) came from a field or from a literal.</p>
40- *
41- * <p>The test class based on the example function calls (and argument specifications) generates all the
42- * permutations of the function arguments (4 options per argument: value/NULL as literal/field) and tests that the
43- * function calls with the same argument values provide the same result regardless of the source (literal, field)
40+ *
41+ * <p>The test class based on the example function calls (and argument specifications) generates all the
42+ * permutations of the function arguments (4 options per argument: value/NULL as literal/field) and tests that the
43+ * function calls with the same argument values provide the same result regardless of the source (literal, field)
4444 * of the arguments.</p>
45- *
45+ *
4646 * <p>To ignore any of the tests, add an .ignore() method call after the Fn ctors in the FUNCTION_CALLS_TO_TEST list below, like:
4747 * <code> new Fn("ASCII", "foobar").ignore()</code></p>
4848 */
@@ -73,51 +73,51 @@ public class ConsistentFunctionArgHandlingIT extends JdbcIntegrationTestCase {
7373 new Fn ("UCASE" , "foobar" )
7474 );
7575
76- private static final List <String > NON_TESTED_FUNCTIONS = asList (
77- "CURDATE" , "CURRENT_DATE" , "CURRENT_TIME" , "CURRENT_TIMESTAMP" , "CURTIME" , "DATEADD" , "DATEDIFF" , "DATEPART" ,
78- "DATETIME_FORMAT" , "DATETIME_PARSE" , "DATETRUNC" , "DATE_ADD" , "DATE_DIFF" , "DATE_PARSE" , "DATE_PART" , "DATE_TRUNC" , "DAY" ,
79- "DAYNAME" , "DAYOFMONTH" , "DAYOFWEEK" , "DAYOFYEAR" , "DAY_NAME" , "DAY_OF_MONTH" , "DAY_OF_WEEK" , "DAY_OF_YEAR" , "DOM" , "DOW" , "DOY" ,
80- "FORMAT" , "HOUR" , "HOUR_OF_DAY" , "IDOW" , "ISODAYOFWEEK" , "ISODOW" , "ISOWEEK" , "ISOWEEKOFYEAR" , "ISO_DAY_OF_WEEK" ,
81- "ISO_WEEK_OF_YEAR" , "IW" , "IWOY" , "MINUTE" , "MINUTE_OF_DAY" , "MINUTE_OF_HOUR" , "MONTH" , "MONTHNAME" , "MONTH_NAME" ,
82- "MONTH_OF_YEAR" , "NOW" , "QUARTER" , "SECOND" , "SECOND_OF_MINUTE" , "TIMESTAMPADD" , "TIMESTAMPDIFF" , "TIMESTAMP_ADD" ,
83- "TIMESTAMP_DIFF" , "TIME_PARSE" , "TODAY" , "TO_CHAR" , "WEEK" , "WEEK_OF_YEAR" , "YEAR" , "ABS" , "ACOS" , "ASIN" , "ATAN" ,
84- "ATAN2" , "CBRT" , "CEIL" , "CEILING" , "COS" , "COSH" , "COT" , "DEGREES" , "E" , "EXP" , "EXPM1" , "FLOOR" , "LOG" , "LOG10" ,
85- "MOD" , "PI" , "POWER" , "RADIANS" , "RAND" , "RANDOM" , "ROUND" , "SIGN" , "SIGNUM" , "SIN" , "SINH" , "SQRT" , "TAN" , "TRUNC" ,
86- "TRUNCATE" , "CAST" , "CONVERT" , "DATABASE" , "USER" , "ST_ASTEXT" , "ST_ASWKT" , "ST_DISTANCE" ,
87- "ST_GEOMETRYTYPE" , "ST_GEOMFROMTEXT" , "ST_WKTTOSQL" , "ST_X" , "ST_Y" , "ST_Z" );
88-
89- private enum Source { FIELD , LITERAL }
90-
76+ private static final List <String > NON_TESTED_FUNCTIONS ;
77+ static {
78+ try {
79+ Class <?> c = ConsistentFunctionArgHandlingIT . class ;
80+ NON_TESTED_FUNCTIONS = Files . readAllLines ( Path . of ( c . getResource ( c . getSimpleName () + "-non-tested-functions.txt" ). toURI ()));
81+ } catch ( Exception ex ) {
82+ throw new RuntimeException ( ex );
83+ }
84+ }
85+
86+ private enum Source {
87+ FIELD ,
88+ LITERAL
89+ }
90+
9191 private static class Fn {
9292 private final String name ;
9393 private final List <Argument > arguments ;
9494 private List <String > aliases = new ArrayList <>();
9595 private boolean ignored = false ;
96-
96+
9797 private Fn (String name , Object ... arguments ) {
9898 this .name = name ;
9999 this .arguments = new ArrayList <>();
100100 for (Object a : arguments ) {
101101 this .arguments .add (Argument .class .isAssignableFrom (a .getClass ()) ? (Argument ) a : new Argument (a ));
102102 }
103103 }
104-
104+
105105 public Fn aliases (String ... aliases ) {
106106 this .aliases = asList (aliases );
107107 return this ;
108- }
109-
108+ }
109+
110110 public Fn ignore () {
111111 this .ignored = true ;
112112 return this ;
113113 }
114-
114+
115115 @ Override
116116 public String toString () {
117117 return name + "(" + arguments .stream ().map (a -> String .valueOf (a .exampleValue )).collect (joining (", " )) + ")" ;
118118 }
119119 }
120-
120+
121121 private static class Argument {
122122 private final Object exampleValue ;
123123 private final Source [] acceptedSources ;
@@ -127,27 +127,27 @@ private Argument(Object exampleValue, Source... acceptedSources) {
127127 this .acceptedSources = acceptedSources .length == 0 ? Source .values () : acceptedSources ;
128128 }
129129 }
130-
130+
131131 @ ParametersFactory
132132 public static Iterable <Object []> testFactory () {
133133 List <Object []> tests = new ArrayList <>();
134- tests .add (new Object []{ null });
135- FUNCTION_CALLS_TO_TEST .forEach (f -> tests .add (new Object []{ f }));
134+ tests .add (new Object [] { null });
135+ FUNCTION_CALLS_TO_TEST .forEach (f -> tests .add (new Object [] { f }));
136136 return tests ;
137137 }
138-
138+
139139 private final Fn fn ;
140-
140+
141141 public ConsistentFunctionArgHandlingIT (Fn fn ) {
142142 this .fn = fn ;
143143 }
144-
144+
145145 public void test () throws Exception {
146146 if (fn == null ) {
147147 checkScalarFunctionCoverage ();
148148 return ;
149149 }
150-
150+
151151 assumeFalse ("Ignored" , fn .ignored );
152152
153153 // create a record for the function, where all the example (non-null) argument values are stored in fields
@@ -157,20 +157,20 @@ public void test() throws Exception {
157157 final String argPrefix = "arg_" + functionName + "_" ;
158158 final String nullArgPrefix = "arg_null_" + functionName + "_" ;
159159 final String testDocId = functionName + "_" + UUIDs .base64UUID ();
160-
160+
161161 indexTestDocForFunction (functionName , indexName , argPrefix , nullArgPrefix , testDocId );
162162
163163 List <List <Object >> possibleValuesPerArguments = fn .arguments .stream ().map (a -> asList (a .exampleValue , null )).collect (toList ());
164164 List <List <Source >> acceptedSourcesPerArguments = fn .arguments .stream ().map (a -> asList (a .acceptedSources )).collect (toList ());
165-
165+
166166 iterateAllPermutations (possibleValuesPerArguments , argValues -> {
167167 // we only want to check the function calls that have at least a single NULL argument
168168 if (argValues .stream ().noneMatch (Objects ::isNull )) {
169169 return ;
170170 }
171171
172172 List <Tuple <String , Object >> results = new ArrayList <>();
173-
173+
174174 iterateAllPermutations (acceptedSourcesPerArguments , argSources -> {
175175 List <String > functionCallArgs = new ArrayList <>();
176176 List <String > functionCallArgsForAssert = new ArrayList <>();
@@ -193,18 +193,17 @@ public void test() throws Exception {
193193 final String functionCall = functionName + "(" + join (", " , functionCallArgs ) + ")" ;
194194 final String query = "SELECT " + functionCall + " FROM " + indexName + " WHERE docId = '" + testDocId + "'" ;
195195 ResultSet retVal = esJdbc ().createStatement ().executeQuery (query );
196-
196+
197197 assertTrue (retVal .next ());
198198 results .add (tuple (functionName + "(" + join (", " , functionCallArgsForAssert ) + ")" , retVal .getObject (1 )));
199199 // only a single row should be returned
200200 assertFalse (retVal .next ());
201201
202- Stream <Object > returnValueStream = results .stream ().map (Tuple ::v2 );
203- if (returnValueStream .distinct ().count () > 1 ) {
204- int maxResultWidth = returnValueStream .mapToInt (o -> asLiteralInQuery (o ).length ()).max ().orElse (20 );
202+ if (results .stream ().map (Tuple ::v2 ).distinct ().count () > 1 ) {
203+ int maxResultWidth = results .stream ().map (Tuple ::v2 ).mapToInt (o -> asLiteralInQuery (o ).length ()).max ().orElse (20 );
205204 String resultsAsString = results .stream ()
206- .map (r -> String .format (Locale .ROOT , "%2$-" + maxResultWidth + "s // %1$s" , r .v1 (), asLiteralInQuery (r .v2 ())))
207- .collect (joining ("\n " ));
205+ .map (r -> String .format (Locale .ROOT , "%2$-" + maxResultWidth + "s // %1$s" , r .v1 (), asLiteralInQuery (r .v2 ())))
206+ .collect (joining ("\n " ));
208207 fail ("The result of the last call differs from the other calls:\n " + resultsAsString );
209208 }
210209 });
@@ -246,9 +245,12 @@ private void checkScalarFunctionCoverage() throws Exception {
246245 functions .removeAll (fn .aliases );
247246 }
248247 functions .removeAll (NON_TESTED_FUNCTIONS );
249-
250- assertThat ("Missing function checks: [" + functions .stream ().map (s -> "\" " + s + "\" " ).collect (joining (", " )) + "]" ,
251- functions , empty ());
248+
249+ assertThat (
250+ "Some functions are not covered by this test" ,
251+ functions ,
252+ empty ()
253+ );
252254 }
253255
254256 private static String asLiteralInQuery (Object argValue ) {
@@ -264,9 +266,9 @@ private static String asLiteralInQuery(Object argValue) {
264266 return argInQuery ;
265267 }
266268
267- private static <T > void iterateAllPermutations (List <List <T >> possibleValuesPerItem , CheckedConsumer <List <T >, Exception > consumer )
269+ private static <T > void iterateAllPermutations (List <List <T >> possibleValuesPerItem , CheckedConsumer <List <T >, Exception > consumer )
268270 throws Exception {
269-
271+
270272 if (possibleValuesPerItem .isEmpty ()) {
271273 consumer .accept (new ArrayList <>());
272274 return ;
@@ -280,6 +282,5 @@ private static <T> void iterateAllPermutations(List<List<T>> possibleValuesPerIt
280282 }
281283 });
282284 }
283-
284- }
285285
286+ }
0 commit comments