diff --git a/jOOQ-test/configuration/org/jooq/configuration/IvanD/h2/information_schema.properties b/jOOQ-test/configuration/org/jooq/configuration/IvanD/h2/information_schema.properties new file mode 100644 index 0000000000..9c689f93c0 --- /dev/null +++ b/jOOQ-test/configuration/org/jooq/configuration/IvanD/h2/information_schema.properties @@ -0,0 +1,17 @@ +#example properties file +jdbc.Driver=org.h2.Driver +jdbc.URL=jdbc:h2:~/test +jdbc.Schema=INFORMATION_SCHEMA +jdbc.User=sa +jdbc.Password= + +generator=org.jooq.util.DefaultGenerator +generator.database=org.jooq.util.h2.H2Database +generator.database.includes=SCHEMATA,TABLES,COLUMNS,CONSTRAINTS,CROSS_REFERENCES,TYPE_INFO,FUNCTION_ALIASES,FUNCTION_COLUMNS,SEQUENCES +generator.database.excludes= +generator.generate.deprecated=false +generator.generate.instance-fields=false +generator.generate.records=false + +generator.target.package=org.jooq.util.h2.information_schema +generator.target.directory=./src/main/java \ No newline at end of file diff --git a/jOOQ-test/configuration/org/jooq/configuration/IvanD/h2/library.properties b/jOOQ-test/configuration/org/jooq/configuration/IvanD/h2/library.properties new file mode 100644 index 0000000000..3de66d6bdf --- /dev/null +++ b/jOOQ-test/configuration/org/jooq/configuration/IvanD/h2/library.properties @@ -0,0 +1,48 @@ +#example properties file +jdbc.Driver=org.h2.Driver +#jdbc.URL=jdbc:h2:tcp://localhost/~/test +jdbc.URL=jdbc:h2:~/test +jdbc.Schema=PUBLIC +jdbc.User=sa +jdbc.Password= + +generator=org.jooq.util.DefaultGenerator +generator.database=org.jooq.util.h2.H2Database +generator.database.includes=.* +generator.database.excludes=T_BOOK_DETAILS,SYSTEM_SEQUENCE.* + +#Database enum type mappings +generator.database.enum-type.BOOLEAN_10=1,0 +generator.database.enum-type.BOOLEAN_YN_UC="Y",N +generator.database.enum-type.BOOLEAN_YN_LC=y,"n" +generator.database.enum-type.BOOLEAN_YES_NO_UC="YES","NO" +generator.database.enum-type.BOOLEAN_YES_NO_LC=yes,no +generator.database.enum-type.BOOLEAN_TRUE_FALSE_UC=TRUE,FALSE +generator.database.enum-type.BOOLEAN_TRUE_FALSE_LC=true,false + +generator.database.forced-type.BOOLEAN_10=(?i:(.*?\.)?T_BOOLEANS\.ONE_ZERO) +generator.database.forced-type.BOOLEAN_YN_UC=(?i:(.*?\.)?T_BOOLEANS\.Y_N_UC) +generator.database.forced-type.BOOLEAN_YN_LC=(?i:(.*?\.)?T_BOOLEANS\.Y_N_LC) +generator.database.forced-type.BOOLEAN_YES_NO_UC=(?i:(.*?\.)?T_BOOLEANS\.YES_NO_UC) +generator.database.forced-type.BOOLEAN_YES_NO_LC=(?i:(.*?\.)?T_BOOLEANS\.YES_NO_LC) +generator.database.forced-type.BOOLEAN_TRUE_FALSE_UC=(?i:(.*?\.)?T_BOOLEANS\.TRUE_FALSE_UC) +generator.database.forced-type.BOOLEAN_TRUE_FALSE_LC=(?i:(.*?\.)?T_BOOLEANS\.TRUE_FALSE_LC) + +#[#677] Forced types +generator.database.forced-type.BOOLEAN=(?i:(.*?\.)?T_BOOLEANS\.(VC|C|N)_BOOLEAN) + +#Generator configuration +generator.generate.relations=true +generator.generate.instance-fields=false +generator.generate.generated-annotation=false + +#Generate a master data table enum from T_LANGUAGE +generator.generate.master-data-tables=T_LANGUAGE,T_658_11,T_658_21,T_658_31,T_658_12,T_658_22,T_658_32 +generator.generate.master-data-table-literal.T_LANGUAGE=CD +generator.generate.master-data-table-description.T_LANGUAGE=DESCRIPTION +generator.generate.master-data-table-literal.T_658_12=CD +generator.generate.master-data-table-literal.T_658_22=CD +generator.generate.master-data-table-literal.T_658_32=CD + +generator.target.package=org.jooq.test.h2.generatedclasses +generator.target.directory=./src \ No newline at end of file diff --git a/jOOQ-test/configuration/org/jooq/configuration/IvanD/h2/library.xml b/jOOQ-test/configuration/org/jooq/configuration/IvanD/h2/library.xml new file mode 100644 index 0000000000..ab854ac34f --- /dev/null +++ b/jOOQ-test/configuration/org/jooq/configuration/IvanD/h2/library.xml @@ -0,0 +1,133 @@ + + + + org.h2.Driver + jdbc:h2:~/test + sa + + + + org.jooq.util.DefaultGenerator + + org.jooq.util.h2.H2Database + .* + T_BOOK_DETAILS,SYSTEM_SEQUENCE.* + REC_VERSION + REC_TIMESTAMP + false + true + PUBLIC + + + T_LANGUAGE + CD + DESCRIPTION + + + T_658_11 + + + T_658_21 + + + T_658_31 + + + T_658_12 + CD + + + T_658_22 + CD + + + T_658_32 + CD + + + + + + org.jooq.test._.converters.Boolean_10 + org.jooq.test._.converters.Boolean_10_Converter + + + org.jooq.test._.converters.Boolean_TF_LC + org.jooq.test._.converters.Boolean_TF_LC_Converter + + + org.jooq.test._.converters.Boolean_TF_UC + org.jooq.test._.converters.Boolean_TF_UC_Converter + + + org.jooq.test._.converters.Boolean_YN_LC + org.jooq.test._.converters.Boolean_YN_LC_Converter + + + org.jooq.test._.converters.Boolean_YN_UC + org.jooq.test._.converters.Boolean_YN_UC_Converter + + + org.jooq.test._.converters.Boolean_YES_NO_LC + org.jooq.test._.converters.Boolean_YES_NO_LC_Converter + + + org.jooq.test._.converters.Boolean_YES_NO_UC + org.jooq.test._.converters.Boolean_YES_NO_UC_Converter + + + + + + BOOLEAN + (?i:(.*?.)?T_BOOLEANS.(VC|C|N)_BOOLEAN) + + + + org.jooq.test._.converters.Boolean_YES_NO_LC + (?i:(.*?.)?T_BOOLEANS.YES_NO_LC) + + + org.jooq.test._.converters.Boolean_YES_NO_UC + (?i:(.*?.)?T_BOOLEANS.YES_NO_UC) + + + org.jooq.test._.converters.Boolean_YN_LC + (?i:(.*?.)?T_BOOLEANS.Y_N_LC) + + + org.jooq.test._.converters.Boolean_YN_UC + (?i:(.*?.)?T_BOOLEANS.Y_N_UC) + + + org.jooq.test._.converters.Boolean_TF_LC + (?i:(.*?.)?T_BOOLEANS.TRUE_FALSE_LC) + + + org.jooq.test._.converters.Boolean_TF_UC + (?i:(.*?.)?T_BOOLEANS.TRUE_FALSE_UC) + + + org.jooq.test._.converters.Boolean_10 + (?i:(.*?.)?T_BOOLEANS.ONE_ZERO) + + + + + true + true + true + false + false + false + false + true + true + false + + + org.jooq.test.h2.generatedclasses + ./src + + + \ No newline at end of file diff --git a/jOOQ-test/src/org/jooq/test/_/testcases/FormatTests.java b/jOOQ-test/src/org/jooq/test/_/testcases/FormatTests.java index de48edca3f..7bbeae43cb 100644 --- a/jOOQ-test/src/org/jooq/test/_/testcases/FormatTests.java +++ b/jOOQ-test/src/org/jooq/test/_/testcases/FormatTests.java @@ -41,7 +41,10 @@ import static junit.framework.Assert.assertTrue; import java.io.ByteArrayInputStream; import java.io.InputStream; +import java.math.BigDecimal; +import java.util.HashSet; import java.util.List; +import java.util.Set; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -86,6 +89,45 @@ extends BaseTest decimalPointIndexSet = new HashSet(); + for (String formatLine : format.split("\n")) { + // Include only data lines + if (formatLine.startsWith("|")) { + decimalPointIndexSet.add(formatLine.indexOf(".")); + } + } + + // Remove -1 position + decimalPointIndexSet.remove(-1); + + // Check if all decimal points have the same position + assertEquals(1, decimalPointIndexSet.size()); + } + @Test public void testFormatHTML() throws Exception { List> fields = TBook().getFields(); diff --git a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java index 86f1683cc4..111777db6e 100644 --- a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java +++ b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java @@ -1222,6 +1222,11 @@ public abstract class jOOQAbstractTest< new CRUDTests(this).testNonUpdatables(); } + @Test + public void testFormat() throws Exception { + new FormatTests(this).testFormat(); + } + @Test public void testFormatHTML() throws Exception { new FormatTests(this).testFormatHTML(); diff --git a/jOOQ/src/main/java/org/jooq/impl/ResultImpl.java b/jOOQ/src/main/java/org/jooq/impl/ResultImpl.java index ee578f2ec4..a0f39ffc47 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ResultImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/ResultImpl.java @@ -88,6 +88,7 @@ import org.w3c.dom.Element; /** * @author Lukas Eder + * @author Ivan Dugic */ class ResultImpl implements Result, AttachableInternal { @@ -937,33 +938,73 @@ class ResultImpl implements Result, AttachableInternal { @Override public final String format(int maxRecords) { - final int MAX_WIDTH = 50; + final int COL_MIN_WIDTH = 4; + final int COL_MAX_WIDTH = 50; + // Numeric columns have greater max width because values are aligned + final int NUM_COL_MAX_WIDTH = 100; + final int MAX_RECORDS = 50; - StringBuilder sb = new StringBuilder(); - Map, Integer> widths = new HashMap, Integer>(); - - // Initialise widths + // Get max decimal places for numeric type columns + Map, Integer> decimalPlacesMap = new HashMap, Integer>(); for (Field f : getFields()) { - widths.put(f, 4); + if (Number.class.isAssignableFrom(f.getType())) { + List decimalPlacesList = new ArrayList(); + + // Initialize + decimalPlacesList.add(0); + + // Collect all decimal places for the column values + String value; + for (int i = 0; i < min(MAX_RECORDS, size()); i++) { + value = format0(getValue(i, f)); + decimalPlacesList.add(getDecimalPlaces(value)); + } + + // Find max + decimalPlacesMap.put(f, Collections.max(decimalPlacesList)); + } } - // Find width for every field to satisfy formatting the first 50 records + // Get max column widths + Map, Integer> widthMap = new HashMap, Integer>(); + int colMaxWidth; for (Field f : getFields()) { - widths.put(f, min(MAX_WIDTH, max(widths.get(f), f.getName().length()))); + // Is number column? + boolean isNumCol = Number.class.isAssignableFrom(f.getType()); + colMaxWidth = isNumCol ? NUM_COL_MAX_WIDTH : COL_MAX_WIDTH; + + // Collect all widths for the column + List widthList = new ArrayList(); + + // Add column name width first + widthList.add(min(colMaxWidth, max(COL_MIN_WIDTH, f.getName().length()))); + + // Add column values width + String value; for (int i = 0; i < min(MAX_RECORDS, size()); i++) { - widths.put(f, min(MAX_WIDTH, max(widths.get(f), format0(getValue(i, f)).length()))); + value = format0(getValue(i, f)); + // Align number values before width is calculated + if (isNumCol) { + value = alignNumberValue(decimalPlacesMap.get(f), value); + } + + widthList.add(min(colMaxWidth, value.length())); } + + // Find max + widthMap.put(f, Collections.max(widthList)); } // Begin the writing // --------------------------------------------------------------------- + StringBuilder sb = new StringBuilder(); // Write top line sb.append("+"); for (Field f : getFields()) { - sb.append(rightPad("", widths.get(f), "-")); + sb.append(rightPad("", widthMap.get(f), "-")); sb.append("+"); } @@ -973,20 +1014,20 @@ class ResultImpl implements Result, AttachableInternal { String padded; if (Number.class.isAssignableFrom(f.getType())) { - padded = leftPad(f.getName(), widths.get(f)); + padded = leftPad(f.getName(), widthMap.get(f)); } else { - padded = rightPad(f.getName(), widths.get(f)); + padded = rightPad(f.getName(), widthMap.get(f)); } - sb.append(abbreviate(padded, widths.get(f))); + sb.append(abbreviate(padded, widthMap.get(f))); sb.append("|"); } // Write separator sb.append("\n+"); for (Field f : getFields()) { - sb.append(rightPad("", widths.get(f), "-")); + sb.append(rightPad("", widthMap.get(f), "-")); sb.append("+"); } @@ -995,16 +1036,21 @@ class ResultImpl implements Result, AttachableInternal { sb.append("\n|"); for (Field f : getFields()) { String value = format0(getValue(i, f)).replace("\n", "{lf}").replace("\r", "{cr}"); - String padded; + String padded; if (Number.class.isAssignableFrom(f.getType())) { - padded = leftPad(value, widths.get(f)); + // Align number value before left pad + value = alignNumberValue(decimalPlacesMap.get(f), value); + + // Left pad + padded = leftPad(value, widthMap.get(f)); } else { - padded = rightPad(value, widths.get(f)); + // Right pad + padded = rightPad(value, widthMap.get(f)); } - sb.append(abbreviate(padded, widths.get(f))); + sb.append(abbreviate(padded, widthMap.get(f))); sb.append("|"); } } @@ -1013,7 +1059,7 @@ class ResultImpl implements Result, AttachableInternal { if (size() > 0) { sb.append("\n+"); for (Field f : getFields()) { - sb.append(rightPad("", widths.get(f), "-")); + sb.append(rightPad("", widthMap.get(f), "-")); sb.append("+"); } } @@ -1028,6 +1074,34 @@ class ResultImpl implements Result, AttachableInternal { return sb.toString(); } + private String alignNumberValue(Integer columnDecimalPlaces, String value) { + if (!"{null}".equals(value) && columnDecimalPlaces != 0) { + int decimalPlaces = getDecimalPlaces(value); + int rightPadSize = value.length() + columnDecimalPlaces - decimalPlaces; + + if (decimalPlaces == 0) { + // If integer value, add one for decimal point + value = rightPad(value, rightPadSize + 1); + } + else { + value = rightPad(value, rightPadSize); + } + } + + return value; + } + + private Integer getDecimalPlaces(String value) { + int decimalPlaces = 0; + + int dotIndex = value.indexOf("."); + if (dotIndex != -1) { + decimalPlaces = value.length() - dotIndex - 1; + } + + return decimalPlaces; + } + @Override public final String formatHTML() { StringBuilder sb = new StringBuilder(); @@ -1140,6 +1214,10 @@ class ResultImpl implements Result, AttachableInternal { else if (value instanceof EnumType) { formatted = ((EnumType) value).getLiteral(); } + else if (value instanceof Number) { + // Remove insignificant zeros + formatted = value.toString().replaceAll("(?:(\\..*[^0])0+|\\.0+)$", "$1"); + } else { formatted = value.toString(); }