package dbfit.environment; import dbfit.annotations.DatabaseEnvironment; import dbfit.api.AbstractDbEnvironment; import dbfit.util.DbParameterAccessor; import dbfit.util.NameNormaliser; import java.math.BigDecimal; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import dbfit.util.Direction; import static dbfit.util.Direction.*; @DatabaseEnvironment(name="DB2", driver="com.ibm.db2.jcc.DB2Driver") public class DB2Environment extends AbstractDbEnvironment { public DB2Environment(String driverClassName) { super(driverClassName); } protected String parseCommandText(String commandText) { commandText = commandText.replaceAll(paramNamePattern, "?"); return super.parseCommandText(commandText); } private static String paramNamePattern = "[@:]([A-Za-z0-9_]+)"; private static Pattern paramRegex = Pattern.compile(paramNamePattern); public Pattern getParameterPattern() { return paramRegex; } protected String getConnectionString(String dataSource) { return "jdbc:db2://" + dataSource; } protected String getConnectionString(String dataSource, String database) { return "jdbc:db2://" + dataSource + "/" + database; } public Map<String, DbParameterAccessor> getAllColumns(String tableOrViewName) throws SQLException { String[] qualifiers = NameNormaliser.normaliseName(tableOrViewName) .split("\\."); String qry = " select colname as column_name, typename as data_type, length, " + "'P' as direction from syscat.columns where "; if (qualifiers.length == 2) { qry += " lower(tabschema)=? and lower(tabname)=? "; } else { qry += " (lower(tabname)=?)"; } qry += " order by colname"; return readIntoParams(qualifiers, qry); } private Map<String, DbParameterAccessor> readIntoParams( String[] queryParameters, String query) throws SQLException { try (PreparedStatement dc = currentConnection.prepareStatement(query)) { for (int i = 0; i < queryParameters.length; i++) { dc.setString(i + 1, NameNormaliser.normaliseName(queryParameters[i])); } ResultSet rs = dc.executeQuery(); Map<String, DbParameterAccessor> allParams = new HashMap<String, DbParameterAccessor>(); int position = 0; while (rs.next()) { String paramName = rs.getString(1); if (paramName == null) paramName = ""; String dataType = rs.getString(2); // int length=rs.getInt(3); String direction = rs.getString(4); Direction paramDirection = getParameterDirection(direction); DbParameterAccessor dbp = createDbParameterAccessor( paramName, paramDirection, getSqlType(dataType), getJavaClass(dataType), paramDirection == RETURN_VALUE ? -1 : position++); allParams.put(NameNormaliser.normaliseName(paramName), dbp); } rs.close(); return allParams; } } private static Direction getParameterDirection(String direction) { if ("P".equals(direction)) return INPUT; if ("O".equals(direction)) return OUTPUT; if ("B".equals(direction)) return INPUT_OUTPUT; if ("C".equals(direction)) return RETURN_VALUE; // todo return val throw new UnsupportedOperationException("Direction " + direction + " is not supported"); } // List interface has sequential search, so using list instead of array to // map types private static List<String> stringTypes = Arrays.asList(new String[] { "VARCHAR", "CHAR", "CHARACTER", "GRAPHIC", "VARGRAPHIC" }); private static List<String> intTypes = Arrays.asList(new String[] { "SMALLINT", "INT", "INTEGER" }); private static List<String> longTypes = Arrays .asList(new String[] { "BIGINT" }); private static List<String> floatTypes = Arrays .asList(new String[] { "FLOAT", "REAL" }); private static List<String> doubleTypes = Arrays .asList(new String[] { "DOUBLE" }); private static List<String> decimalTypes = Arrays.asList(new String[] { "DECIMAL", "DEC", "DECFLOAT", "NUMERIC" }); private static List<String> dateTypes = Arrays .asList(new String[] { "DATE" }); private static List<String> timestampTypes = Arrays .asList(new String[] { "TIMESTAMP" }); private static String NormaliseTypeName(String dataType) { dataType = dataType.toUpperCase().trim(); return dataType; } private static int getSqlType(String dataType) { // todo:strip everything from first blank dataType = NormaliseTypeName(dataType); if (stringTypes.contains(dataType)) return java.sql.Types.VARCHAR; if (decimalTypes.contains(dataType)) return java.sql.Types.NUMERIC; if (intTypes.contains(dataType)) return java.sql.Types.INTEGER; if (floatTypes.contains(dataType)) return java.sql.Types.FLOAT; if (doubleTypes.contains(dataType)) return java.sql.Types.DOUBLE; if (longTypes.contains(dataType)) return java.sql.Types.BIGINT; if (timestampTypes.contains(dataType)) return java.sql.Types.TIMESTAMP; if (dateTypes.contains(dataType)) return java.sql.Types.DATE; throw new UnsupportedOperationException("Type " + dataType + " is not supported"); } public Class<?> getJavaClass(String dataType) { dataType = NormaliseTypeName(dataType); if (stringTypes.contains(dataType)) return String.class; if (decimalTypes.contains(dataType)) return BigDecimal.class; if (intTypes.contains(dataType)) return Integer.class; if (floatTypes.contains(dataType)) return Float.class; if (dateTypes.contains(dataType)) return java.sql.Date.class; if (doubleTypes.contains(dataType)) return Double.class; if (longTypes.contains(dataType)) return Long.class; if (timestampTypes.contains(dataType)) return java.sql.Timestamp.class; throw new UnsupportedOperationException("Type " + dataType + " is not supported"); } public Map<String, DbParameterAccessor> getAllProcedureParameters( String procName) throws SQLException { String[] qualifiers = NameNormaliser.normaliseName(procName).split( "\\."); String qry = " select parmname as column_name, typename as data_type, length, " + "rowtype as direction, ordinal from SYSIBM.SYSroutinePARMS where "; if (qualifiers.length == 2) { qry += " lower(routineschema)=? and lower(routinename)=? "; } else { qry += " (lower(routinename)=?)"; } qry += " order by ordinal"; return readIntoParams(qualifiers, qry); } }