TabularPrefetchingFormatter: prefetch whole result set to avoid value overflow the cell v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Sat, 28 Dec 2013 12:19:39 +0100
branchv_0
changeset 88102ba0fcb07f
parent 87 03bf24449c7a
child 89 98d18e9a357b
TabularPrefetchingFormatter: prefetch whole result set to avoid value overflow the cell
java/sql-dk/src/info/globalcode/sql/dk/Functions.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/Configuration.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/AbstractFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularPrefetchingFormatter.java
     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 +}