diff --git a/build.gradle.kts b/build.gradle.kts index a8c6dc2..0bc2c52 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -38,6 +38,7 @@ repositories { } } mavenCentral() + jcenter() } dependencies { @@ -55,6 +56,9 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter:$junitVersion") testImplementation("org.hamcrest:hamcrest:2.1") + // Mockito + testImplementation("org.mockito:mockito-core:3.0.0") + // deployer for packagecloud deployerJars("io.packagecloud.maven.wagon:maven-packagecloud-wagon:0.0.6") } diff --git a/src/main/java/org/utplsql/api/FileMapper.java b/src/main/java/org/utplsql/api/FileMapper.java deleted file mode 100644 index 1e5ac66..0000000 --- a/src/main/java/org/utplsql/api/FileMapper.java +++ /dev/null @@ -1,110 +0,0 @@ -package org.utplsql.api; - - -import oracle.jdbc.OracleConnection; -import oracle.jdbc.OracleTypes; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.sql.*; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -public final class FileMapper { - - private static final Logger logger = LoggerFactory.getLogger(FileMapper.class); - - private FileMapper() { - } - - /** - * Call the database api to build the custom file mappings. - */ - public static Array buildFileMappingArray( - Connection conn, FileMapperOptions mapperOptions) throws SQLException { - OracleConnection oraConn = conn.unwrap(OracleConnection.class); - - Map> typeMap = conn.getTypeMap(); - typeMap.put(CustomTypes.UT_FILE_MAPPING, FileMapping.class); - typeMap.put(CustomTypes.UT_KEY_VALUE_PAIR, KeyValuePair.class); - conn.setTypeMap(typeMap); - - CallableStatement callableStatement = conn.prepareCall( - "BEGIN " + - "? := ut_file_mapper.build_file_mappings(" + - "a_object_owner => ?, " + - "a_file_paths => ?, " + - "a_file_to_object_type_mapping => ?, " + - "a_regex_pattern => ?, " + - "a_object_owner_subexpression => ?, " + - "a_object_name_subexpression => ?, " + - "a_object_type_subexpression => ?); " + - "END;"); - - int paramIdx = 0; - callableStatement.registerOutParameter(++paramIdx, OracleTypes.ARRAY, CustomTypes.UT_FILE_MAPPINGS); - - if (mapperOptions.getObjectOwner() == null) { - callableStatement.setNull(++paramIdx, Types.VARCHAR); - } else { - callableStatement.setString(++paramIdx, mapperOptions.getObjectOwner()); - } - - logger.debug("Building fileMappingArray"); - Object[] filePathsArray = mapperOptions.getFilePaths().toArray(); - for ( Object elem : filePathsArray ) { - logger.debug("Path: " + elem); - } - - callableStatement.setArray( - ++paramIdx, oraConn.createOracleArray(CustomTypes.UT_VARCHAR2_LIST, filePathsArray)); - - if (mapperOptions.getTypeMappings() == null) { - callableStatement.setNull(++paramIdx, Types.ARRAY, CustomTypes.UT_KEY_VALUE_PAIRS); - } else { - callableStatement.setArray( - ++paramIdx, oraConn.createOracleArray(CustomTypes.UT_KEY_VALUE_PAIRS, mapperOptions.getTypeMappings().toArray())); - } - - if (mapperOptions.getRegexPattern() == null) { - callableStatement.setNull(++paramIdx, Types.VARCHAR); - } else { - callableStatement.setString(++paramIdx, mapperOptions.getRegexPattern()); - } - - if (mapperOptions.getOwnerSubExpression() == null) { - callableStatement.setNull(++paramIdx, Types.INTEGER); - } else { - callableStatement.setInt(++paramIdx, mapperOptions.getOwnerSubExpression()); - } - - if (mapperOptions.getNameSubExpression() == null) { - callableStatement.setNull(++paramIdx, Types.INTEGER); - } else { - callableStatement.setInt(++paramIdx, mapperOptions.getNameSubExpression()); - } - - if (mapperOptions.getTypeSubExpression() == null) { - callableStatement.setNull(++paramIdx, Types.INTEGER); - } else { - callableStatement.setInt(++paramIdx, mapperOptions.getTypeSubExpression()); - } - - callableStatement.execute(); - return callableStatement.getArray(1); - } - - public static List buildFileMappingList( - Connection conn, FileMapperOptions mapperOptions) throws SQLException { - java.sql.Array fileMappings = buildFileMappingArray(conn, mapperOptions); - - List mappingList = new ArrayList<>(); - for (Object obj : (Object[]) fileMappings.getArray()) { - mappingList.add((FileMapping) obj); - } - - return mappingList; - } - -} diff --git a/src/main/java/org/utplsql/api/db/DynamicParameterList.java b/src/main/java/org/utplsql/api/db/DynamicParameterList.java new file mode 100644 index 0000000..68f88a1 --- /dev/null +++ b/src/main/java/org/utplsql/api/db/DynamicParameterList.java @@ -0,0 +1,181 @@ +package org.utplsql.api.db; + +import oracle.jdbc.OracleConnection; + +import java.sql.CallableStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.LinkedHashMap; +import java.util.stream.Collectors; + +/** Lets you build a list of parameters for a CallableStatement + *
+ * Create it with the Builder (DynamicParameterList.builder()) + * + * @author pesse + */ +public class DynamicParameterList { + + private LinkedHashMap params; + + interface DynamicParameter { + void setParam( CallableStatement statement, int index ) throws SQLException; + } + + private DynamicParameterList(LinkedHashMap params) { + this.params = params; + } + + /** Returns the SQL of this ParameterList as comma-separated list of the parameter identifiers:
+ * + * e.g. "a_parameter1 => ?, a_parameter2 => ?" + * + * @return comma-separated list of parameter identifiers + */ + public String getSql() { + return params.keySet().stream() + .map(e -> e + " => ?") + .collect(Collectors.joining(", ")); + } + + /** Sets the contained parameters in the order they were added to the given statement by index, starting with the given one + * + * @param statement The statement to set the parameters to + * @param startIndex The index to start with + * @throws SQLException SQLException of the underlying statement.setX methods + */ + public void setParamsStartWithIndex(CallableStatement statement, int startIndex ) throws SQLException { + int index = startIndex; + for ( DynamicParameter param : params.values() ) { + param.setParam(statement, index++); + } + } + + /** Returns a builder to create a DynamicParameterList + * + * @return Builder + */ + public static DynamicParameterListBuilder builder() { + return new DynamicParameterListBuilder(); + } + + /** Builder-class for DynamicParameterList + *
+ * Usage: + *
+     *  DynamicParameterList.builder()
+     *      .add("parameter1", "StringParameter")
+     *      .addIfNotEmpty("parameter2", 123)
+     *      .build();
+     * 
+ * + * @author pesse + */ + public static class DynamicParameterListBuilder { + + private LinkedHashMap params = new LinkedHashMap<>(); + + private DynamicParameterListBuilder() { + + } + + public DynamicParameterListBuilder add(String identifier, String value ) { + params.put(identifier, new DynamicParameterList.DynamicStringParameter(value)); + return this; + } + + public DynamicParameterListBuilder addIfNotEmpty(String identifier, String value ) { + if ( value != null && !value.isEmpty() ) { + add(identifier, value); + } + return this; + } + + public DynamicParameterListBuilder add(String identifier, Integer value ) { + params.put(identifier, new DynamicParameterList.DynamicIntegerParameter(value)); + return this; + } + + public DynamicParameterListBuilder addIfNotEmpty(String identifier, Integer value ) { + if ( value != null) { + add(identifier, value); + } + return this; + } + + public DynamicParameterListBuilder add(String identifier, Object[] value, String customTypeName, OracleConnection oraConnection ) { + params.put(identifier, new DynamicParameterList.DynamicArrayParameter(value, customTypeName, oraConnection)); + return this; + } + + public DynamicParameterListBuilder addIfNotEmpty(String identifier, Object[] value, String customTypeName, OracleConnection oraConnection ) { + if ( value != null && value.length > 0 ) { + add(identifier, value, customTypeName, oraConnection); + } + return this; + } + + public DynamicParameterList build() { + return new DynamicParameterList(params); + } + } + + /* Implementations of DynamicStringParameter */ + private static class DynamicStringParameter implements DynamicParameter { + private final String value; + + DynamicStringParameter( String value ) { + this.value = value; + } + + @Override + public void setParam(CallableStatement statement, int index) throws SQLException { + if ( value == null ) { + statement.setNull(index, Types.VARCHAR); + } else { + statement.setString(index, value); + } + } + } + + private static class DynamicIntegerParameter implements DynamicParameter { + private final Integer value; + + DynamicIntegerParameter( Integer value ) { + this.value = value; + } + + @Override + public void setParam(CallableStatement statement, int index) throws SQLException { + if ( value == null ) { + statement.setNull(index, Types.INTEGER); + } else { + statement.setInt(index, value); + } + } + } + + private static class DynamicArrayParameter implements DynamicParameter { + private final Object[] value; + private final String customTypeName; + private final OracleConnection oraConnection; + + DynamicArrayParameter( Object[] value, String customTypeName, OracleConnection oraConnection ) { + this.value = value; + this.customTypeName = customTypeName; + this.oraConnection = oraConnection; + } + + @Override + public void setParam(CallableStatement statement, int index) throws SQLException { + if ( value == null ) { + statement.setNull(index, Types.ARRAY, customTypeName); + } else { + statement.setArray( + index, oraConnection.createOracleArray(customTypeName, value) + ); + } + } + } + +} diff --git a/src/main/java/org/utplsql/api/testRunner/AbstractTestRunnerStatement.java b/src/main/java/org/utplsql/api/testRunner/AbstractTestRunnerStatement.java index dbd5086..d53863e 100644 --- a/src/main/java/org/utplsql/api/testRunner/AbstractTestRunnerStatement.java +++ b/src/main/java/org/utplsql/api/testRunner/AbstractTestRunnerStatement.java @@ -2,7 +2,6 @@ import oracle.jdbc.OracleConnection; import org.utplsql.api.CustomTypes; -import org.utplsql.api.FileMapper; import org.utplsql.api.FileMapping; import org.utplsql.api.TestRunnerOptions; diff --git a/src/main/java/org/utplsql/api/testRunner/FileMapper.java b/src/main/java/org/utplsql/api/testRunner/FileMapper.java new file mode 100644 index 0000000..b599a5a --- /dev/null +++ b/src/main/java/org/utplsql/api/testRunner/FileMapper.java @@ -0,0 +1,84 @@ +package org.utplsql.api.testRunner; + + +import oracle.jdbc.OracleConnection; +import oracle.jdbc.OracleTypes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.utplsql.api.CustomTypes; +import org.utplsql.api.FileMapperOptions; +import org.utplsql.api.FileMapping; +import org.utplsql.api.KeyValuePair; +import org.utplsql.api.db.DynamicParameterList; + +import java.sql.*; +import java.util.*; + +final class FileMapper { + + private static final Logger logger = LoggerFactory.getLogger(FileMapper.class); + + private FileMapper() { + } + + /** + * Call the database api to build the custom file mappings. + */ + private static Array buildFileMappingArray( + Connection conn, FileMapperOptions mapperOptions) throws SQLException { + OracleConnection oraConn = conn.unwrap(OracleConnection.class); + + Map> typeMap = conn.getTypeMap(); + typeMap.put(CustomTypes.UT_FILE_MAPPING, FileMapping.class); + typeMap.put(CustomTypes.UT_KEY_VALUE_PAIR, KeyValuePair.class); + conn.setTypeMap(typeMap); + + logger.debug("Building fileMappingArray"); + final Object[] filePathsArray = mapperOptions.getFilePaths().toArray(); + for ( Object elem : filePathsArray ) { + logger.debug("Path: " + elem); + } + Object[] typeMapArray = null; + if ( mapperOptions.getTypeMappings() != null ) { + typeMapArray = mapperOptions.getTypeMappings().toArray(); + } + + DynamicParameterList parameterList = DynamicParameterList.builder() + .add("a_file_paths", filePathsArray, CustomTypes.UT_VARCHAR2_LIST, oraConn) + .addIfNotEmpty("a_object_owner", mapperOptions.getObjectOwner()) + .addIfNotEmpty("a_file_to_object_type_mapping", typeMapArray, CustomTypes.UT_KEY_VALUE_PAIRS, oraConn) + .addIfNotEmpty("a_regex_pattern", mapperOptions.getRegexPattern()) + .addIfNotEmpty("a_object_owner_subexpression", mapperOptions.getOwnerSubExpression()) + .addIfNotEmpty("a_object_name_subexpression", mapperOptions.getNameSubExpression()) + .addIfNotEmpty("a_object_type_subexpression", mapperOptions.getTypeSubExpression()) + .build(); + + CallableStatement callableStatement = conn.prepareCall( + "BEGIN " + + "? := ut_file_mapper.build_file_mappings(" + + parameterList.getSql() + + "); " + + "END;"); + + int paramIdx = 0; + callableStatement.registerOutParameter(++paramIdx, OracleTypes.ARRAY, CustomTypes.UT_FILE_MAPPINGS); + + parameterList.setParamsStartWithIndex(callableStatement, ++paramIdx); + + callableStatement.execute(); + return callableStatement.getArray(1); + } + + static List buildFileMappingList( + Connection conn, FileMapperOptions mapperOptions) throws SQLException { + java.sql.Array fileMappings = buildFileMappingArray(conn, mapperOptions); + + List mappingList = new ArrayList<>(); + for (Object obj : (Object[]) fileMappings.getArray()) { + mappingList.add((FileMapping) obj); + } + + return mappingList; + } + +} diff --git a/src/test/java/org/utplsql/api/db/DynamicParameterListTest.java b/src/test/java/org/utplsql/api/db/DynamicParameterListTest.java new file mode 100644 index 0000000..e2bd6b2 --- /dev/null +++ b/src/test/java/org/utplsql/api/db/DynamicParameterListTest.java @@ -0,0 +1,57 @@ +package org.utplsql.api.db; + +import oracle.jdbc.OracleConnection; +import org.junit.jupiter.api.Test; + +import java.sql.CallableStatement; +import java.sql.SQLException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; + +public class DynamicParameterListTest { + + @Test + void call_with_three_different_types() throws SQLException { + + CallableStatement mockedStatement = mock(CallableStatement.class); + OracleConnection mockedConn = mock(OracleConnection.class); + + Object[] numArr = new Object[]{1, 2}; + + DynamicParameterList parameterList = DynamicParameterList.builder() + .add("a_object_owner", "MyOwner") + .add("a_num_param", 123) + .add("a_num_array", numArr, "MY_NUM_ARR", mockedConn) + .build(); + + assertEquals("a_object_owner => ?, a_num_param => ?, a_num_array => ?", parameterList.getSql()); + + parameterList.setParamsStartWithIndex(mockedStatement, 5); + + verify(mockedStatement).setString(5, "MyOwner"); + verify(mockedStatement).setInt(6, 123); + verify(mockedConn).createOracleArray("MY_NUM_ARR", numArr); + verify(mockedStatement).setArray(7, null); + } + + @Test + void when_not_accept_empty_filter_empty_elements() throws SQLException { + + CallableStatement mockedStatement = mock(CallableStatement.class); + OracleConnection mockedConn = mock(OracleConnection.class); + + DynamicParameterList parameterList = DynamicParameterList.builder() + .addIfNotEmpty("a_object_owner", (String)null) + .addIfNotEmpty("a_num_param", (Integer)null) + .addIfNotEmpty("a_num_array", new Object[]{}, "MY_NUM_ARR", mockedConn) + .build(); + + assertEquals("", parameterList.getSql()); + + parameterList.setParamsStartWithIndex(mockedStatement, 2); + + verifyNoMoreInteractions(mockedStatement); + verifyNoMoreInteractions(mockedConn); + } +} diff --git a/src/test/java/org/utplsql/api/FileMapperIT.java b/src/test/java/org/utplsql/api/testRunner/FileMapperIT.java similarity index 53% rename from src/test/java/org/utplsql/api/FileMapperIT.java rename to src/test/java/org/utplsql/api/testRunner/FileMapperIT.java index 20ff1b4..5c7a306 100644 --- a/src/test/java/org/utplsql/api/FileMapperIT.java +++ b/src/test/java/org/utplsql/api/testRunner/FileMapperIT.java @@ -1,6 +1,12 @@ -package org.utplsql.api; +package org.utplsql.api.testRunner; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.utplsql.api.AbstractDatabaseTest; +import org.utplsql.api.FileMapperOptions; +import org.utplsql.api.FileMapping; +import org.utplsql.api.KeyValuePair; +import org.utplsql.api.testRunner.FileMapper; import java.sql.SQLException; import java.util.ArrayList; @@ -45,4 +51,37 @@ private void assertMapping(FileMapping fileMapping, String owner, String name, S assertEquals(type, fileMapping.getObjectType()); } + @Nested + class Default_type_mapping { + + void checkTypeMapping( List typeMappings ) throws SQLException { + List filePaths = java.util.Arrays.asList( + "/award_bonus.prc", + "/betwnstr.fnc", + "/package_body.pkb", + "/type_body.tpb", + "/trigger.trg"); + FileMapperOptions mapperOptions = new FileMapperOptions(filePaths); + mapperOptions.setTypeMappings(typeMappings); + + List fileMappings = FileMapper.buildFileMappingList(getConnection(), mapperOptions); + + assertEquals("PROCEDURE", fileMappings.get(0).getObjectType()); + assertEquals("FUNCTION", fileMappings.get(1).getObjectType()); + assertEquals("PACKAGE BODY", fileMappings.get(2).getObjectType()); + assertEquals("TYPE BODY", fileMappings.get(3).getObjectType()); + assertEquals("TRIGGER", fileMappings.get(4).getObjectType()); + } + + @Test + void is_used_on_null_parameter() throws SQLException { + checkTypeMapping(null); + } + + @Test + void is_used_on_empty_parameter() throws SQLException { + checkTypeMapping(new ArrayList<>()); + } + } + }