TabularPrefetchingFormatter: prefetch whole result set to avoid value overflow the cell
1.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/Functions.java Fri Dec 27 21:26:30 2013 +0100
1.2 +++ b/java/sql-dk/src/info/globalcode/sql/dk/Functions.java Sat Dec 28 12:19:39 2013 +0100
1.3 @@ -121,11 +121,19 @@
1.4 }
1.5
1.6 public static String rpad(String s, int n) {
1.7 - return String.format("%1$-" + n + "s", s);
1.8 + if (n > 0) {
1.9 + return String.format("%1$-" + n + "s", s);
1.10 + } else {
1.11 + return s;
1.12 + }
1.13 }
1.14
1.15 public static String lpad(String s, int n) {
1.16 - return String.format("%1$" + n + "s", s);
1.17 + if (n > 0) {
1.18 + return String.format("%1$" + n + "s", s);
1.19 + } else {
1.20 + return s;
1.21 + }
1.22 }
1.23
1.24 public static String repeat(char ch, int count) {
2.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/Configuration.java Fri Dec 27 21:26:30 2013 +0100
2.2 +++ b/java/sql-dk/src/info/globalcode/sql/dk/configuration/Configuration.java Sat Dec 28 12:19:39 2013 +0100
2.3 @@ -22,6 +22,7 @@
2.4 import info.globalcode.sql.dk.formatting.SilentFormatter;
2.5 import info.globalcode.sql.dk.formatting.SingleValueFormatter;
2.6 import info.globalcode.sql.dk.formatting.TabularFormatter;
2.7 +import info.globalcode.sql.dk.formatting.TabularPrefetchingFormatter;
2.8 import info.globalcode.sql.dk.formatting.XmlFormatter;
2.9 import java.util.ArrayList;
2.10 import java.util.Collection;
2.11 @@ -56,6 +57,7 @@
2.12 l.add(new FormatterDefinition(SingleValueFormatter.NAME, SingleValueFormatter.class.getName()));
2.13 l.add(new FormatterDefinition(XmlFormatter.NAME, XmlFormatter.class.getName()));
2.14 l.add(new FormatterDefinition(TabularFormatter.NAME, TabularFormatter.class.getName()));
2.15 + l.add(new FormatterDefinition(TabularPrefetchingFormatter.NAME, TabularPrefetchingFormatter.class.getName()));
2.16 buildInFormatters = Collections.unmodifiableCollection(l);
2.17 }
2.18
3.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/AbstractFormatter.java Fri Dec 27 21:26:30 2013 +0100
3.2 +++ b/java/sql-dk/src/info/globalcode/sql/dk/formatting/AbstractFormatter.java Sat Dec 28 12:19:39 2013 +0100
3.3 @@ -160,7 +160,7 @@
3.4 throw new IllegalStateException("Parameters '" + parameters + "' must be set before columns header – was already set: " + currentColumnsHeader);
3.5 }
3.6
3.7 - if (currentQuery == null) {
3.8 + if (currentQuery == null && parameters != null) {
3.9 throw new IllegalStateException("Parameters '" + parameters + "' must be set after query – was not yet set.");
3.10 }
3.11 }
4.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularFormatter.java Fri Dec 27 21:26:30 2013 +0100
4.2 +++ b/java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularFormatter.java Sat Dec 28 12:19:39 2013 +0100
4.3 @@ -22,6 +22,8 @@
4.4 import static info.globalcode.sql.dk.Functions.lpad;
4.5 import static info.globalcode.sql.dk.Functions.rpad;
4.6 import static info.globalcode.sql.dk.Functions.repeat;
4.7 +import java.util.Arrays;
4.8 +import java.util.List;
4.9
4.10 /**
4.11 *
4.12 @@ -59,12 +61,19 @@
4.13 public void writeColumnsHeader(ColumnsHeader header) {
4.14 super.writeColumnsHeader(header);
4.15
4.16 - columnWidth = new int[header.getColumnCount()];
4.17 + initColumnWidths(header.getColumnCount());
4.18
4.19 printTableIndent();
4.20 printTableBorder("╭");
4.21 - for (ColumnDescriptor cd : header.getColumnDescriptors()) {
4.22 - setColumnWidth(cd.getColumnNumber(), cd.getLabel().length() + cd.getTypeName().length() + HEADER_TYPE_PREFIX.length() + HEADER_TYPE_SUFFIX.length());
4.23 +
4.24 + List<ColumnDescriptor> columnDescriptors = header.getColumnDescriptors();
4.25 +
4.26 + for (ColumnDescriptor cd : columnDescriptors) {
4.27 + // padding: make header cell at least same width as data cells in this column
4.28 + int typeWidth = cd.getTypeName().length() + HEADER_TYPE_PREFIX.length() + HEADER_TYPE_SUFFIX.length();
4.29 + cd.setLabel(rpad(cd.getLabel(), getColumnWidth(cd.getColumnNumber()) - typeWidth));
4.30 + updateColumnWidth(cd.getColumnNumber(), cd.getLabel().length() + typeWidth);
4.31 +
4.32 if (!cd.isFirstColumn()) {
4.33 printTableBorder("┬");
4.34 }
4.35 @@ -73,7 +82,7 @@
4.36 printTableBorder("╮");
4.37 out.println();
4.38
4.39 - for (ColumnDescriptor cd : header.getColumnDescriptors()) {
4.40 + for (ColumnDescriptor cd : columnDescriptors) {
4.41 if (cd.isFirstColumn()) {
4.42 printTableIndent();
4.43 printTableBorder("│ ");
4.44 @@ -87,8 +96,6 @@
4.45 if (cd.isLastColumn()) {
4.46 printTableBorder(" │");
4.47 }
4.48 -
4.49 - setColumnWidth(cd.getColumnNumber(), cd.getLabel().length() + cd.getTypeName().length() + HEADER_TYPE_PREFIX.length() + HEADER_TYPE_SUFFIX.length());
4.50 }
4.51 out.println();
4.52
4.53 @@ -106,6 +113,24 @@
4.54 out.flush();
4.55 }
4.56
4.57 + /**
4.58 + * Must be called before
4.59 + * {@linkplain #updateColumnWidth(int, int)}
4.60 + * and {@linkplain #getColumnWidth(int)}
4.61 + * for each result set.
4.62 + *
4.63 + * @param columnCount number of columns in current result set
4.64 + */
4.65 + protected void initColumnWidths(int columnCount) {
4.66 + if (columnWidth == null) {
4.67 + columnWidth = new int[columnCount];
4.68 + }
4.69 + }
4.70 +
4.71 + protected void cleanColumnWidths() {
4.72 + columnWidth = null;
4.73 + }
4.74 +
4.75 @Override
4.76 public void writeColumnValue(Object value) {
4.77 super.writeColumnValue(value);
4.78 @@ -133,7 +158,7 @@
4.79 columnWidth[columnNumber - 1] = width;
4.80 }
4.81
4.82 - private void updateColumnWidth(int columnNumber, int width) {
4.83 + protected void updateColumnWidth(int columnNumber, int width) {
4.84 int oldWidth = getColumnWidth(columnNumber);
4.85 setColumnWidth(columnNumber, Math.max(width, oldWidth));
4.86
4.87 @@ -180,6 +205,7 @@
4.88 printTableBorder("╯");
4.89 out.println();
4.90
4.91 + cleanColumnWidths();
4.92
4.93 out.print(TerminalColor.Yellow, "Record count: ");
4.94 out.println(getCurrentRowCount());
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
5.2 +++ b/java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularPrefetchingFormatter.java Sat Dec 28 12:19:39 2013 +0100
5.3 @@ -0,0 +1,124 @@
5.4 +/**
5.5 + * SQL-DK
5.6 + * Copyright © 2013 František Kučera (frantovo.cz)
5.7 + *
5.8 + * This program is free software: you can redistribute it and/or modify
5.9 + * it under the terms of the GNU General Public License as published by
5.10 + * the Free Software Foundation, either version 3 of the License, or
5.11 + * (at your option) any later version.
5.12 + *
5.13 + * This program is distributed in the hope that it will be useful,
5.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
5.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
5.16 + * GNU General Public License for more details.
5.17 + *
5.18 + * You should have received a copy of the GNU General Public License
5.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
5.20 + */
5.21 +package info.globalcode.sql.dk.formatting;
5.22 +
5.23 +import info.globalcode.sql.dk.Parameter;
5.24 +import java.util.ArrayList;
5.25 +import java.util.List;
5.26 +
5.27 +/**
5.28 + * Prefetches whole result set and computes column widths. Whole table is flushed at once in
5.29 + * {@linkplain #writeEndResultSet()}.
5.30 + *
5.31 + * Long values will not overflow the cells, but whole result set must be loaded into memory.
5.32 + *
5.33 + * @author Ing. František Kučera (frantovo.cz)
5.34 + */
5.35 +public class TabularPrefetchingFormatter extends TabularFormatter {
5.36 +
5.37 + public static final String NAME = "tabular-prefetching"; // bash-completion:formatter
5.38 + private String currentSql;
5.39 + private List<? extends Parameter> currentParameters;
5.40 + private ColumnsHeader currentHeader;
5.41 + private List<Object[]> currentResultSet;
5.42 + private Object[] currentRow;
5.43 + private int currentColumnsCount;
5.44 + private boolean prefetchDone = false;
5.45 +
5.46 + public TabularPrefetchingFormatter(FormatterContext formatterContext) {
5.47 + super(formatterContext);
5.48 + }
5.49 +
5.50 + @Override
5.51 + protected int getCurrentColumnsCount() {
5.52 + if (prefetchDone) {
5.53 + return super.getCurrentColumnsCount();
5.54 + } else {
5.55 + return currentColumnsCount;
5.56 + }
5.57 + }
5.58 +
5.59 + @Override
5.60 + public void writeStartResultSet() {
5.61 + currentResultSet = new ArrayList<>();
5.62 + }
5.63 +
5.64 + @Override
5.65 + public void writeQuery(String sql) {
5.66 + currentSql = sql;
5.67 + }
5.68 +
5.69 + @Override
5.70 + public void writeParameters(List<? extends Parameter> parameters) {
5.71 + currentParameters = parameters;
5.72 + }
5.73 +
5.74 + @Override
5.75 + public void writeColumnsHeader(ColumnsHeader header) {
5.76 + currentHeader = header;
5.77 + initColumnWidths(header.getColumnCount());
5.78 + }
5.79 +
5.80 + @Override
5.81 + public void writeStartRow() {
5.82 + currentRow = new Object[currentHeader.getColumnCount()];
5.83 + currentResultSet.add(currentRow);
5.84 + currentColumnsCount = 0;
5.85 + }
5.86 +
5.87 + @Override
5.88 + public void writeColumnValue(Object value) {
5.89 + currentRow[currentColumnsCount] = value;
5.90 + currentColumnsCount++;
5.91 + String textRepresentation = toString(value);
5.92 + /** TODO: count only printable characters (currently not an issue) */
5.93 + updateColumnWidth(currentColumnsCount, textRepresentation.length());
5.94 + }
5.95 +
5.96 + @Override
5.97 + public void writeEndRow() {
5.98 + // do nothing
5.99 + }
5.100 +
5.101 + @Override
5.102 + public void writeEndResultSet() {
5.103 + prefetchDone = true;
5.104 +
5.105 + super.writeStartResultSet();
5.106 + super.writeQuery(currentSql);
5.107 + super.writeParameters(currentParameters);
5.108 + super.writeColumnsHeader(currentHeader);
5.109 +
5.110 + for (Object[] row : currentResultSet) {
5.111 + super.writeStartRow();
5.112 + for (Object cell : row) {
5.113 + super.writeColumnValue(cell);
5.114 + }
5.115 + super.writeEndRow();
5.116 + }
5.117 +
5.118 + currentColumnsCount = 0;
5.119 + currentSql = null;
5.120 + currentParameters = null;
5.121 + currentHeader = null;
5.122 + currentRow = null;
5.123 + currentResultSet = null;
5.124 + super.writeEndResultSet();
5.125 + prefetchDone = false;
5.126 + }
5.127 +}