package adql.db; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.junit.AfterClass; import org.junit.Test; import adql.db.DBType.DBDatatype; import adql.db.FunctionDef.FunctionParam; import adql.parser.ParseException; import adql.query.operand.ADQLColumn; import adql.query.operand.ADQLOperand; import adql.query.operand.NumericConstant; import adql.query.operand.StringConstant; import adql.query.operand.function.ADQLFunction; import adql.query.operand.function.DefaultUDF; import adql.query.operand.function.geometry.PointFunction; public class TestFunctionDef { @AfterClass public static void tearDownAfterClass() throws Exception{ DBType.DBDatatype.UNKNOWN.setCustomType(null); } @Test public void testIsString(){ for(DBDatatype type : DBDatatype.values()){ switch(type){ case CHAR: case VARCHAR: case TIMESTAMP: case CLOB: assertTrue(new FunctionDef("foo", new DBType(type)).isString); break; default: assertFalse(new FunctionDef("foo", new DBType(type)).isString); } } } @Test public void testIsGeometry(){ for(DBDatatype type : DBDatatype.values()){ switch(type){ case POINT: case REGION: assertTrue(new FunctionDef("foo", new DBType(type)).isGeometry); break; default: assertFalse(new FunctionDef("foo", new DBType(type)).isGeometry); } } } @Test public void testIsNumeric(){ for(DBDatatype type : DBDatatype.values()){ switch(type){ case CHAR: case VARCHAR: case TIMESTAMP: case POINT: case REGION: case CLOB: case UNKNOWN: assertFalse(new FunctionDef("foo", new DBType(type)).isNumeric); break; case UNKNOWN_NUMERIC: default: assertTrue(new FunctionDef("foo", new DBType(type)).isNumeric); } } } @Test public void testToString(){ assertEquals("fct1()", new FunctionDef("fct1").toString()); assertEquals("fct1() -> VARCHAR", new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR)).toString()); assertEquals("fct1(foo DOUBLE) -> VARCHAR", new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{new FunctionParam("foo", new DBType(DBDatatype.DOUBLE))}).toString()); assertEquals("fct1(foo DOUBLE)", new FunctionDef("fct1", new FunctionParam[]{new FunctionParam("foo", new DBType(DBDatatype.DOUBLE))}).toString()); assertEquals("fct1(foo DOUBLE, pt POINT) -> VARCHAR", new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{new FunctionParam("foo", new DBType(DBDatatype.DOUBLE)),new FunctionParam("pt", new DBType(DBDatatype.POINT))}).toString()); assertEquals("fct1(foo DOUBLE, pt POINT)", new FunctionDef("fct1", null, new FunctionParam[]{new FunctionParam("foo", new DBType(DBDatatype.DOUBLE)),new FunctionParam("pt", new DBType(DBDatatype.POINT))}).toString()); } @Test public void testParse(){ final String WRONG_FULL_SYNTAX = "Wrong function definition syntax! Expected syntax: \"<regular_identifier>(<parameters>?) <return_type>?\", where <regular_identifier>=\"[a-zA-Z]+[a-zA-Z0-9_]*\", <return_type>=\" -> <type_name>\", <parameters>=\"(<regular_identifier> <type_name> (, <regular_identifier> <type_name>)*)\", <type_name> should be one of the types described in the UPLOAD section of the TAP documentation. Examples of good syntax: \"foo()\", \"foo() -> VARCHAR\", \"foo(param INTEGER)\", \"foo(param1 INTEGER, param2 DOUBLE) -> DOUBLE\""; final String WRONG_PARAM_SYNTAX = "Wrong parameters syntax! Expected syntax: \"(<regular_identifier> <type_name> (, <regular_identifier> <type_name>)*)\", where <regular_identifier>=\"[a-zA-Z]+[a-zA-Z0-9_]*\", <type_name> should be one of the types described in the UPLOAD section of the TAP documentation. Examples of good syntax: \"()\", \"(param INTEGER)\", \"(param1 INTEGER, param2 DOUBLE)\""; // NULL test: try{ FunctionDef.parse(null); fail("A NULL string is not valide!"); }catch(Exception ex){ assertTrue(ex instanceof NullPointerException); assertEquals("Missing string definition to build a FunctionDef!", ex.getMessage()); } // EMPTY STRING test: try{ FunctionDef.parse(""); fail("An empty string is not valide!"); }catch(Exception ex){ assertTrue(ex instanceof ParseException); assertEquals(WRONG_FULL_SYNTAX, ex.getMessage()); } // CORRECT string definitions: try{ assertEquals("foo()", FunctionDef.parse("foo()").toString()); assertEquals("foo() -> VARCHAR", FunctionDef.parse("foo() -> string").toString()); assertEquals("foo() -> VARCHAR", FunctionDef.parse("foo()->string").toString()); assertEquals("foo(toto VARCHAR) -> SMALLINT", FunctionDef.parse("foo(toto varchar) -> boolean").toString()); assertEquals("foo(param1 DOUBLE, param2 INTEGER) -> DOUBLE", FunctionDef.parse(" foo ( param1 numeric, param2 int ) -> DOUBLE ").toString()); assertEquals("foo_ALTernative2first(p POINT, d TIMESTAMP) -> TIMESTAMP", FunctionDef.parse("foo_ALTernative2first (p POINT,d date) -> time").toString()); assertEquals("blabla_123(toto INTEGER, bla SMALLINT, truc CLOB, bidule CHAR, smurph POINT, date TIMESTAMP) -> SMALLINT", FunctionDef.parse("blabla_123(toto int4, bla bool, truc text, bidule character, smurph point, date timestamp) -> BOOLEAN").toString()); }catch(Exception ex){ ex.printStackTrace(System.err); fail("All this string definitions are correct."); } // TYPE PARAMETER test: try{ for(DBDatatype t : DBDatatype.values()){ switch(t){ case CHAR: case VARCHAR: case BINARY: case VARBINARY: assertEquals("foo() -> " + t.toString() + "(10)", FunctionDef.parse("foo() -> " + t.toString() + "(10)").toString()); break; default: assertEquals("foo() -> " + t.toString(), FunctionDef.parse("foo() -> " + t.toString() + "(10)").toString()); } } }catch(Exception ex){ ex.printStackTrace(System.err); fail("Wrong type parsing!"); } // TYPE WITH SPACES AND/OR PARAMETER test: try{ assertEquals("foo() -> DOUBLE", FunctionDef.parse("foo() -> double precision").toString()); assertEquals("foo(bar DOUBLE)", FunctionDef.parse("foo(bar DOUBLE Precision )").toString()); assertEquals("foo() -> VARCHAR", FunctionDef.parse("foo() -> character varying").toString()); assertEquals("foo(bar VARBINARY)", FunctionDef.parse("foo(bar bit varying)").toString()); assertEquals("foo(bar VARCHAR(12))", FunctionDef.parse("foo(bar varchar (12))").toString()); assertEquals("foo(bar VARCHAR(12))", FunctionDef.parse("foo(bar character varying (12))").toString()); assertEquals("foo() -> DOUBLE", FunctionDef.parse("foo() -> double precision (2)").toString()); }catch(Exception ex){ ex.printStackTrace(System.err); fail("Wrong type parsing!"); } // WRONG string definitions: try{ FunctionDef.parse("123()"); fail("No number is allowed as first character of a function name!"); }catch(Exception ex){ assertTrue(ex instanceof ParseException); assertEquals(WRONG_FULL_SYNTAX, ex.getMessage()); } try{ FunctionDef.parse("1foo()"); fail("No number is allowed as first character of a function name!"); }catch(Exception ex){ assertTrue(ex instanceof ParseException); assertEquals(WRONG_FULL_SYNTAX, ex.getMessage()); } try{ FunctionDef.parse("foo,truc()"); fail("No other character than [a-zA-Z0-9_] is allowed after a first character [a-zA-Z] in a function name!"); }catch(Exception ex){ assertTrue(ex instanceof ParseException); assertEquals(WRONG_FULL_SYNTAX, ex.getMessage()); } try{ FunctionDef.parse("foo"); fail("A function definition must contain at list parenthesis even if there is no parameter."); }catch(Exception ex){ assertTrue(ex instanceof ParseException); assertEquals(WRONG_FULL_SYNTAX, ex.getMessage()); } try{ FunctionDef.parse("foo(param)"); fail("A parameter must always have a type!"); }catch(Exception ex){ assertTrue(ex instanceof ParseException); assertEquals("Wrong syntax for the 1-th parameter: \"param\"! Expected syntax: \"(<regular_identifier> <type_name> (, <regular_identifier> <type_name>)*)\", where <regular_identifier>=\"[a-zA-Z]+[a-zA-Z0-9_]*\", <type_name> should be one of the types described in the UPLOAD section of the TAP documentation. Examples of good syntax: \"()\", \"(param INTEGER)\", \"(param1 INTEGER, param2 DOUBLE)\"", ex.getMessage()); } try{ FunctionDef fct = FunctionDef.parse("foo()->aType"); assertTrue(fct.isUnknown); assertFalse(fct.isString); assertFalse(fct.isNumeric); assertFalse(fct.isGeometry); assertEquals("?aType?", fct.returnType.type.toString()); }catch(Exception ex){ ex.printStackTrace(System.err); fail("Unknown types MUST be allowed!"); } try{ FunctionDef fct = FunctionDef.parse("foo()->aType(10)"); assertTrue(fct.isUnknown); assertFalse(fct.isString); assertFalse(fct.isNumeric); assertFalse(fct.isGeometry); assertEquals("?aType(10)?", fct.returnType.type.toString()); }catch(Exception ex){ ex.printStackTrace(System.err); fail("Unknown types MUST be allowed!"); } try{ FunctionDef.parse("foo() -> "); fail("The return type is missing!"); }catch(Exception ex){ assertTrue(ex instanceof ParseException); assertEquals(WRONG_FULL_SYNTAX, ex.getMessage()); } try{ FunctionDef.parse("foo(,)"); fail("Missing parameter definition!"); }catch(Exception ex){ assertTrue(ex instanceof ParseException); assertEquals(WRONG_PARAM_SYNTAX, ex.getMessage()); } try{ FunctionDef.parse("foo(param1 int,)"); fail("Missing parameter definition!"); }catch(Exception ex){ assertTrue(ex instanceof ParseException); assertEquals(WRONG_PARAM_SYNTAX, ex.getMessage()); } try{ FunctionDef fct = FunctionDef.parse("foo(param1 aType)"); assertTrue(fct.getParam(0).type.isUnknown()); assertFalse(fct.getParam(0).type.isString()); assertFalse(fct.getParam(0).type.isNumeric()); assertFalse(fct.getParam(0).type.isGeometry()); assertEquals("?aType?", fct.getParam(0).type.toString()); }catch(Exception ex){ ex.printStackTrace(System.err); fail("Unknown types MUST be allowed!"); } try{ FunctionDef fct = FunctionDef.parse("foo(param1 aType(10))"); assertTrue(fct.getParam(0).type.isUnknown()); assertFalse(fct.getParam(0).type.isString()); assertFalse(fct.getParam(0).type.isNumeric()); assertFalse(fct.getParam(0).type.isGeometry()); assertEquals("?aType(10)?", fct.getParam(0).type.toString()); }catch(Exception ex){ ex.printStackTrace(System.err); fail("Unknown types MUST be allowed!"); } try{ FunctionDef fct = FunctionDef.parse("INTERSECTION(region1 region, region2 region) -> region"); assertEquals(DBType.DBDatatype.REGION, fct.getParam(0).type.type); assertEquals(DBType.DBDatatype.REGION, fct.getParam(1).type.type); assertEquals(DBType.DBDatatype.REGION, fct.returnType.type); }catch(Exception ex){ ex.printStackTrace(System.err); fail("Impossible to parse this REGION based FunctionDef! (see console for more details)"); } } @Test public void testCompareToFunctionDef(){ // DEFINITION 1 :: fct1() -> VARCHAR FunctionDef def1 = new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR)); // TEST :: Identity test (def1 with def1): [EQUAL] assertEquals(0, def1.compareTo(def1)); // TEST :: With a function having a different name and also no parameter: [GREATER] assertEquals(1, def1.compareTo(new FunctionDef("fct0", new DBType(DBDatatype.VARCHAR)))); // TEST :: With a function having the same name, but a different return type: [EQUAL} assertEquals(0, def1.compareTo(new FunctionDef("fct1", new DBType(DBDatatype.INTEGER)))); // TEST :: With a function having the same name, but 2 parameters: [LESS (6 characters: ΓΈ against 100100)] assertEquals(-6, def1.compareTo(new FunctionDef("fct1", new DBType(DBDatatype.INTEGER), new FunctionParam[]{new FunctionParam("foo", new DBType(DBDatatype.INTEGER)),new FunctionParam("foo", new DBType(DBDatatype.INTEGER))}))); // DEFINITION 1 :: fct1(foo1 CHAR(12), foo2 DOUBLE) -> VARCHAR def1 = new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{new FunctionParam("foo1", new DBType(DBDatatype.CHAR, 12)),new FunctionParam("foo2", new DBType(DBDatatype.DOUBLE))}); // TEST :: Identity test (def1 with def1): [EQUAL] assertEquals(0, def1.compareTo(def1)); // DEFINITION 2 :: fct1(foo1 CHAR(12), foo2 VARCHAR) -> VARCHAR FunctionDef def2 = new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{new FunctionParam("foo1", new DBType(DBDatatype.CHAR, 12)),new FunctionParam("foo2", new DBType(DBDatatype.VARCHAR))}); // TEST :: Identity test (def2 with def2): [EQUAL] assertEquals(0, def2.compareTo(def2)); // TEST :: Same name, but different type for the last parameter only: [GREATER (because Numeric = 10 > String = 01)] assertEquals(1, def1.compareTo(def2)); // DEFINITION 2 :: fct2(foo1 CHAR(12), foo2 DOUBLE) -> VARCHAR def2 = new FunctionDef("fct2", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{new FunctionParam("foo1", new DBType(DBDatatype.CHAR, 12)),new FunctionParam("foo2", new DBType(DBDatatype.DOUBLE))}); // TEST :: Identity test (def2 with def2): [EQUAL] assertEquals(0, def2.compareTo(def2)); // TEST :: Different name but same parameters: [LESS] assertEquals(-1, def1.compareTo(def2)); // DEFINITION 2 :: fct1(foo1 CHAR(12), foo2 POINT) -> VARCHAR def2 = new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{new FunctionParam("foo1", new DBType(DBDatatype.CHAR, 12)),new FunctionParam("foo2", new DBType(DBDatatype.POINT))}); // TEST :: Identity test (def2 with def2): [EQUAL] assertEquals(0, def2.compareTo(def2)); // TEST :: Same name, but different type for the last parameter only: [GREATER] assertEquals(1, def1.compareTo(def2)); } @Test public void testCompareToADQLFunction(){ // DEFINITION :: fct1() -> VARCHAR FunctionDef def = new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR)); // TEST :: NULL: try{ def.compareTo((ADQLFunction)null); fail("Missing ADQL function for comparison with FunctionDef!"); }catch(Exception e){ assertTrue(e instanceof NullPointerException); assertEquals("Missing ADQL function with which comparing this function definition!", e.getMessage()); } // TEST :: "fct1()": [EQUAL] assertEquals(0, def.compareTo(new DefaultUDF("fct1", null))); // TEST :: "fct0()": [GREATER] assertEquals(1, def.compareTo(new DefaultUDF("fct0", null))); // TEST :: "fct1(12.3, 3.14)": [LESS (of 2 params)] assertEquals(-2, def.compareTo(new DefaultUDF("fct1", new ADQLOperand[]{new NumericConstant(12.3),new NumericConstant(3.14)}))); // DEFINITION :: fct1(foo1 CHAR(12), foo2 DOUBLE) -> VARCHAR def = new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{new FunctionParam("foo1", new DBType(DBDatatype.CHAR, 12)),new FunctionParam("foo2", new DBType(DBDatatype.DOUBLE))}); // TEST :: "fct1('blabla', 'blabla2')": [GREATER (because the second param is numeric and Numeric = 10 > String = 01)] assertEquals(1, def.compareTo(new DefaultUDF("fct1", new ADQLOperand[]{new StringConstant("blabla"),new StringConstant("blabla2")}))); // TEST :: "fct1('blabla', POINT('COORDSYS', 1.2, 3.4))": [GREATER (same reason ; POINT is considered as a String)] try{ assertEquals(1, def.compareTo(new DefaultUDF("fct1", new ADQLOperand[]{new StringConstant("blabla"),new PointFunction(new StringConstant("COORDSYS"), new NumericConstant(1.2), new NumericConstant(3.4))}))); }catch(Exception e){ e.printStackTrace(); fail(); } // Test with an UNKNOWN numeric type: // TEST :: "fct0(foo)", where foo is a simple UNKNOWN [EQUAL] FunctionDef def0 = new FunctionDef("fct0", null, new FunctionParam[]{new FunctionParam("whatever", new DBType(DBDatatype.VARCHAR))}); DefaultDBColumn dbcol = new DefaultDBColumn("foo", new DefaultDBTable("toto")); dbcol.setDatatype(new DBType(DBDatatype.UNKNOWN)); ADQLColumn col = new ADQLColumn("foo"); col.setDBLink(dbcol); assertEquals(0, def0.compareTo(new DefaultUDF("fct0", new ADQLOperand[]{col}))); // TEST :: "fct0(foo)", where foo is an UNKNOWN NUMERIC [LESS] dbcol.setDatatype(new DBType(DBDatatype.UNKNOWN_NUMERIC)); assertEquals(-1, def0.compareTo(new DefaultUDF("fct0", new ADQLOperand[]{col}))); } }