# HG changeset patch # User František Kučera # Date 1388229579 -3600 # Node ID 102ba0fcb07f0607184240a474a6fc03ae1b9ec5 # Parent 03bf24449c7a6d60b057a302a8d9a20db857176b TabularPrefetchingFormatter: prefetch whole result set to avoid value overflow the cell diff -r 03bf24449c7a -r 102ba0fcb07f java/sql-dk/src/info/globalcode/sql/dk/Functions.java --- a/java/sql-dk/src/info/globalcode/sql/dk/Functions.java Fri Dec 27 21:26:30 2013 +0100 +++ b/java/sql-dk/src/info/globalcode/sql/dk/Functions.java Sat Dec 28 12:19:39 2013 +0100 @@ -121,11 +121,19 @@ } public static String rpad(String s, int n) { - return String.format("%1$-" + n + "s", s); + if (n > 0) { + return String.format("%1$-" + n + "s", s); + } else { + return s; + } } public static String lpad(String s, int n) { - return String.format("%1$" + n + "s", s); + if (n > 0) { + return String.format("%1$" + n + "s", s); + } else { + return s; + } } public static String repeat(char ch, int count) { diff -r 03bf24449c7a -r 102ba0fcb07f java/sql-dk/src/info/globalcode/sql/dk/configuration/Configuration.java --- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/Configuration.java Fri Dec 27 21:26:30 2013 +0100 +++ b/java/sql-dk/src/info/globalcode/sql/dk/configuration/Configuration.java Sat Dec 28 12:19:39 2013 +0100 @@ -22,6 +22,7 @@ import info.globalcode.sql.dk.formatting.SilentFormatter; import info.globalcode.sql.dk.formatting.SingleValueFormatter; import info.globalcode.sql.dk.formatting.TabularFormatter; +import info.globalcode.sql.dk.formatting.TabularPrefetchingFormatter; import info.globalcode.sql.dk.formatting.XmlFormatter; import java.util.ArrayList; import java.util.Collection; @@ -56,6 +57,7 @@ l.add(new FormatterDefinition(SingleValueFormatter.NAME, SingleValueFormatter.class.getName())); l.add(new FormatterDefinition(XmlFormatter.NAME, XmlFormatter.class.getName())); l.add(new FormatterDefinition(TabularFormatter.NAME, TabularFormatter.class.getName())); + l.add(new FormatterDefinition(TabularPrefetchingFormatter.NAME, TabularPrefetchingFormatter.class.getName())); buildInFormatters = Collections.unmodifiableCollection(l); } diff -r 03bf24449c7a -r 102ba0fcb07f java/sql-dk/src/info/globalcode/sql/dk/formatting/AbstractFormatter.java --- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/AbstractFormatter.java Fri Dec 27 21:26:30 2013 +0100 +++ b/java/sql-dk/src/info/globalcode/sql/dk/formatting/AbstractFormatter.java Sat Dec 28 12:19:39 2013 +0100 @@ -160,7 +160,7 @@ throw new IllegalStateException("Parameters '" + parameters + "' must be set before columns header – was already set: " + currentColumnsHeader); } - if (currentQuery == null) { + if (currentQuery == null && parameters != null) { throw new IllegalStateException("Parameters '" + parameters + "' must be set after query – was not yet set."); } } diff -r 03bf24449c7a -r 102ba0fcb07f java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularFormatter.java --- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularFormatter.java Fri Dec 27 21:26:30 2013 +0100 +++ b/java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularFormatter.java Sat Dec 28 12:19:39 2013 +0100 @@ -22,6 +22,8 @@ import static info.globalcode.sql.dk.Functions.lpad; import static info.globalcode.sql.dk.Functions.rpad; import static info.globalcode.sql.dk.Functions.repeat; +import java.util.Arrays; +import java.util.List; /** * @@ -59,12 +61,19 @@ public void writeColumnsHeader(ColumnsHeader header) { super.writeColumnsHeader(header); - columnWidth = new int[header.getColumnCount()]; + initColumnWidths(header.getColumnCount()); printTableIndent(); printTableBorder("╭"); - for (ColumnDescriptor cd : header.getColumnDescriptors()) { - setColumnWidth(cd.getColumnNumber(), cd.getLabel().length() + cd.getTypeName().length() + HEADER_TYPE_PREFIX.length() + HEADER_TYPE_SUFFIX.length()); + + List columnDescriptors = header.getColumnDescriptors(); + + for (ColumnDescriptor cd : columnDescriptors) { + // padding: make header cell at least same width as data cells in this column + int typeWidth = cd.getTypeName().length() + HEADER_TYPE_PREFIX.length() + HEADER_TYPE_SUFFIX.length(); + cd.setLabel(rpad(cd.getLabel(), getColumnWidth(cd.getColumnNumber()) - typeWidth)); + updateColumnWidth(cd.getColumnNumber(), cd.getLabel().length() + typeWidth); + if (!cd.isFirstColumn()) { printTableBorder("┬"); } @@ -73,7 +82,7 @@ printTableBorder("╮"); out.println(); - for (ColumnDescriptor cd : header.getColumnDescriptors()) { + for (ColumnDescriptor cd : columnDescriptors) { if (cd.isFirstColumn()) { printTableIndent(); printTableBorder("│ "); @@ -87,8 +96,6 @@ if (cd.isLastColumn()) { printTableBorder(" │"); } - - setColumnWidth(cd.getColumnNumber(), cd.getLabel().length() + cd.getTypeName().length() + HEADER_TYPE_PREFIX.length() + HEADER_TYPE_SUFFIX.length()); } out.println(); @@ -106,6 +113,24 @@ out.flush(); } + /** + * Must be called before + * {@linkplain #updateColumnWidth(int, int)} + * and {@linkplain #getColumnWidth(int)} + * for each result set. + * + * @param columnCount number of columns in current result set + */ + protected void initColumnWidths(int columnCount) { + if (columnWidth == null) { + columnWidth = new int[columnCount]; + } + } + + protected void cleanColumnWidths() { + columnWidth = null; + } + @Override public void writeColumnValue(Object value) { super.writeColumnValue(value); @@ -133,7 +158,7 @@ columnWidth[columnNumber - 1] = width; } - private void updateColumnWidth(int columnNumber, int width) { + protected void updateColumnWidth(int columnNumber, int width) { int oldWidth = getColumnWidth(columnNumber); setColumnWidth(columnNumber, Math.max(width, oldWidth)); @@ -180,6 +205,7 @@ printTableBorder("╯"); out.println(); + cleanColumnWidths(); out.print(TerminalColor.Yellow, "Record count: "); out.println(getCurrentRowCount()); diff -r 03bf24449c7a -r 102ba0fcb07f java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularPrefetchingFormatter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularPrefetchingFormatter.java Sat Dec 28 12:19:39 2013 +0100 @@ -0,0 +1,124 @@ +/** + * SQL-DK + * Copyright © 2013 František Kučera (frantovo.cz) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package info.globalcode.sql.dk.formatting; + +import info.globalcode.sql.dk.Parameter; +import java.util.ArrayList; +import java.util.List; + +/** + * Prefetches whole result set and computes column widths. Whole table is flushed at once in + * {@linkplain #writeEndResultSet()}. + * + * Long values will not overflow the cells, but whole result set must be loaded into memory. + * + * @author Ing. František Kučera (frantovo.cz) + */ +public class TabularPrefetchingFormatter extends TabularFormatter { + + public static final String NAME = "tabular-prefetching"; // bash-completion:formatter + private String currentSql; + private List currentParameters; + private ColumnsHeader currentHeader; + private List currentResultSet; + private Object[] currentRow; + private int currentColumnsCount; + private boolean prefetchDone = false; + + public TabularPrefetchingFormatter(FormatterContext formatterContext) { + super(formatterContext); + } + + @Override + protected int getCurrentColumnsCount() { + if (prefetchDone) { + return super.getCurrentColumnsCount(); + } else { + return currentColumnsCount; + } + } + + @Override + public void writeStartResultSet() { + currentResultSet = new ArrayList<>(); + } + + @Override + public void writeQuery(String sql) { + currentSql = sql; + } + + @Override + public void writeParameters(List parameters) { + currentParameters = parameters; + } + + @Override + public void writeColumnsHeader(ColumnsHeader header) { + currentHeader = header; + initColumnWidths(header.getColumnCount()); + } + + @Override + public void writeStartRow() { + currentRow = new Object[currentHeader.getColumnCount()]; + currentResultSet.add(currentRow); + currentColumnsCount = 0; + } + + @Override + public void writeColumnValue(Object value) { + currentRow[currentColumnsCount] = value; + currentColumnsCount++; + String textRepresentation = toString(value); + /** TODO: count only printable characters (currently not an issue) */ + updateColumnWidth(currentColumnsCount, textRepresentation.length()); + } + + @Override + public void writeEndRow() { + // do nothing + } + + @Override + public void writeEndResultSet() { + prefetchDone = true; + + super.writeStartResultSet(); + super.writeQuery(currentSql); + super.writeParameters(currentParameters); + super.writeColumnsHeader(currentHeader); + + for (Object[] row : currentResultSet) { + super.writeStartRow(); + for (Object cell : row) { + super.writeColumnValue(cell); + } + super.writeEndRow(); + } + + currentColumnsCount = 0; + currentSql = null; + currentParameters = null; + currentHeader = null; + currentRow = null; + currentResultSet = null; + super.writeEndResultSet(); + prefetchDone = false; + } +}