From f176f7537d01967bc909737d23e91074dbbacdb3 Mon Sep 17 00:00:00 2001 From: Chris Jaekl Date: Fri, 24 Mar 2017 19:17:41 +0900 Subject: [PATCH] Fix and fine-tune suppress_nulls. --- pom.xml | 2 +- .../java/net/jaekl/squelch/db/DbDriver.java | 39 +++++++-- .../java/net/jaekl/squelch/db/Setting.java | 61 ++++++++++++++ .../java/net/jaekl/squelch/stmt/PSet.java | 57 +++---------- .../java/net/jaekl/squelch/stmt/Tabular.java | 44 +++++----- .../java/net/jaekl/squelch/stmt/PSetTest.java | 38 ++++++--- .../net/jaekl/squelch/stmt/TabularTest.java | 81 +++++++++++++++++++ 7 files changed, 243 insertions(+), 79 deletions(-) create mode 100644 src/main/java/net/jaekl/squelch/db/Setting.java diff --git a/pom.xml b/pom.xml index 86b3db0..051053c 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ net.jaekl.squelch squelch jar - 0.1a-SNAPSHOT + 0.1b-SNAPSHOT squelch http://maven.apache.org diff --git a/src/main/java/net/jaekl/squelch/db/DbDriver.java b/src/main/java/net/jaekl/squelch/db/DbDriver.java index 72472de..cf116de 100644 --- a/src/main/java/net/jaekl/squelch/db/DbDriver.java +++ b/src/main/java/net/jaekl/squelch/db/DbDriver.java @@ -3,10 +3,14 @@ package net.jaekl.squelch.db; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; +import java.util.HashMap; import java.util.Locale; public abstract class DbDriver { - private boolean m_suppressNulls = false; + // Well-known setting names + public static final String SUPPRESS_NULLS = "suppress_nulls"; + + private HashMap m_settings; // Returns true iff. this DbDriver knows how to connect to the given JDBC URL abstract public boolean handles(String jdbcUrl); @@ -14,13 +18,38 @@ public abstract class DbDriver { // Execute line as a statement of this type abstract String getJdbcDriverClassName(); + DbDriver() { + m_settings = new HashMap(); + m_settings.put(SUPPRESS_NULLS, new Setting(SUPPRESS_NULLS, Boolean.class, Boolean.valueOf(false))); + } + // ------------------- // Getters and setters + public Setting[] getSettings() + { + return m_settings.values().toArray(new Setting[m_settings.size()]); + } - public boolean isSuppressNulls() { return m_suppressNulls; } - public void setSuppressNulls(boolean value) { m_suppressNulls = value; } - - + public boolean isSet(String name) + { + Setting setting = m_settings.get(name); + if (null != setting) { + return setting.getBoolean(); + } + return false; + } + public void set(String name, Object value) + { + String lcName = name.toLowerCase(Locale.CANADA); + Setting setting = m_settings.get(lcName); + if (null != setting) { + setting.set(value); + } + else { + throw new IllegalArgumentException("Setting \"" + name + "\" not found."); + } + } + // Open a new Connection to the database. Note that the caller must close() this at some point. public Connection connect(String jdbcUrl, String userName, String password) throws ClassNotFoundException, SQLException { diff --git a/src/main/java/net/jaekl/squelch/db/Setting.java b/src/main/java/net/jaekl/squelch/db/Setting.java new file mode 100644 index 0000000..85934ce --- /dev/null +++ b/src/main/java/net/jaekl/squelch/db/Setting.java @@ -0,0 +1,61 @@ +package net.jaekl.squelch.db; + +import java.util.Locale; + +public class Setting { + private String m_name; + private Class m_type; + private Object m_value; + + public Setting(String name, Class type, Object defaultValue) + { + m_name = name; + m_type = type; + m_value = defaultValue; + } + + public String getName() { return m_name; } + + public boolean getBoolean() + { + assert(Boolean.class == m_type); + + if (m_value instanceof Boolean) { + return ((Boolean)m_value).booleanValue(); + } + return false; + } + + public void set(Object value) + { + if (Boolean.class == m_type) { + if (value instanceof String) { + m_value = parseBoolean((String)value); + } + else if (value instanceof Boolean) { + m_value = value; + } + } + else { + throw new IllegalArgumentException("This type not yet supported."); + } + } + + private boolean parseBoolean(String value) + { + if (null == value) { + return false; + } + + String setting = value.trim().toLowerCase(Locale.CANADA); + + if (setting.equals("yes") || setting.equals("true") || setting.equals("on") || setting.equals("1")) { + return true; + } + if (setting.equals("no") || setting.equals("false") || setting.equals("off") || setting.equals("0")) { + return false; + } + + throw new IllegalArgumentException("UnrecognizedBooleanValue: \"" + value + "\""); + } +} diff --git a/src/main/java/net/jaekl/squelch/stmt/PSet.java b/src/main/java/net/jaekl/squelch/stmt/PSet.java index a64edc2..1431ab9 100644 --- a/src/main/java/net/jaekl/squelch/stmt/PSet.java +++ b/src/main/java/net/jaekl/squelch/stmt/PSet.java @@ -7,10 +7,9 @@ import java.sql.SQLException; import java.util.Locale; import net.jaekl.squelch.db.DbDriver; +import net.jaekl.squelch.db.Setting; public class PSet extends Stmt { - private static final String SUPPRESS_NULLS = "suppress_nulls"; - @Override public boolean handles(String line) { if (null == line) { @@ -24,72 +23,40 @@ public class PSet extends Stmt { public int exec(DbDriver driver, Connection conn, PrintWriter pw, String line) throws IOException, SQLException { - String trimmed = line.substring(6).trim(); + String trimmed = line.substring(5).trim(); int equPos = trimmed.indexOf('='); if (equPos > 0) { return setValue(driver, pw, trimmed, equPos); } - else if (trimmed.length() > 0) { - return displayValue(driver, pw, trimmed); - } else { - // TODO: StringTable - pw.println("???"); - return 0; + return displayValue(driver, pw, trimmed); } } private int displayValue(DbDriver driver, PrintWriter pw, String trimmed) { - String lcName = trimmed.toLowerCase(Locale.CANADA); + Setting[] settings = driver.getSettings(); - if (SUPPRESS_NULLS.equals(lcName)) { - // TODO: StringTable - pw.println(SUPPRESS_NULLS + ": " + (driver.isSuppressNulls() ? "on" : "off")); - } - else { - // TODO: StringTable - pw.println("??? Unrecognized setting: \"" + trimmed + "\". Ignored."); + for (Setting setting : settings) { + String lcName = trimmed.toLowerCase(Locale.CANADA); + + if ("".equals(lcName) || setting.getName().equals(lcName)) { + // TODO: StringTable + pw.println(setting.getName() + ": " + (setting.getBoolean())); + } } return 0; } - private boolean parseBoolean(String value) - { - if (null == value) { - return false; - } - - String setting = value.trim().toLowerCase(Locale.CANADA); - - if (setting.equals("yes") || setting.equals("true") || setting.equals("on") || setting.equals("1")) { - return true; - } - if (setting.equals("no") || setting.equals("false") || setting.equals("off") || setting.equals("0")) { - return false; - } - - throw new IllegalArgumentException("UnrecognizedBooleanValue: \"" + value + "\""); - } - private int setValue(DbDriver driver, PrintWriter pw, String trimmed, int equPos) { String name = trimmed.substring(0, equPos).trim(); String value = trimmed.substring(equPos + 1).trim(); - String lcName = name.toLowerCase(Locale.CANADA); - - if (lcName.equals("suppress_nulls")) { - driver.setSuppressNulls(parseBoolean(value)); - } - else { - // TODO: Stringtable - pw.println("??? Unrecognized setting name \"" + name + "\" ignored."); - return 0; - } + driver.set(name, value); return 1; } diff --git a/src/main/java/net/jaekl/squelch/stmt/Tabular.java b/src/main/java/net/jaekl/squelch/stmt/Tabular.java index eef757d..6a060f1 100644 --- a/src/main/java/net/jaekl/squelch/stmt/Tabular.java +++ b/src/main/java/net/jaekl/squelch/stmt/Tabular.java @@ -69,21 +69,21 @@ abstract public class Tabular { if (pending > 0) { writeHeader(pw, cols, colWidths, suppressed); writeRowBuffer(pw, rowBuf, colWidths, suppressed); - rowCount = rowBuf.getPending(); + rowCount = pending; } - if (driver.isSuppressNulls()) { - // TODO: StringTable - pw.println("Row limit for suppress_nulls has been reached; output may have been truncated."); - writeDivider(pw, colWidths, suppressed); - } - else { - while (pending > 0) { - rowBuf = bufferRows(driver, colWidths); - writeRowBuffer(pw, rowBuf, colWidths, suppressed); - pending = rowBuf.getPending(); - rowCount += pending; + rowBuf = bufferRows(driver, colWidths); + pending = rowBuf.getPending(); + while (pending > 0) { + if (driver.isSet(DbDriver.SUPPRESS_NULLS)) { + writeDivider(pw, colWidths, suppressed); + pw.println("Row limit for suppress_nulls has been reached; output may have been truncated."); + break; } + writeRowBuffer(pw, rowBuf, colWidths, suppressed); + rowCount += pending; + rowBuf = bufferRows(driver, colWidths); + pending = rowBuf.getPending(); } if (rowCount > 0) { @@ -164,13 +164,13 @@ abstract public class Tabular { } } - if ((!allColsNull) || (!driver.isSuppressNulls())) + if ((!allColsNull) || (!driver.isSet(DbDriver.SUPPRESS_NULLS))) { rowBuf.addRow(row); // Check whether all values in this row will fit in the current column widths for (int colIdx = 0; colIdx < colWidths.length; ++colIdx) { - int width = ("" + row.getValue(colIdx + 1)).length(); + int width = stringify(row.getValue(colIdx + 1)).length(); if (width > colWidths[colIdx]) { // Widen the column to fit this value colWidths[colIdx] = width; @@ -307,8 +307,12 @@ abstract public class Tabular { { boolean[] result = new boolean[cols.length]; - if (rowBuf.getPending() < 1) { - // No data rows, so do not suppress any columns. + if ( !(driver.isSet(DbDriver.SUPPRESS_NULLS)) + || (rowBuf.getPending() < 1) ) + { + // Null-suppression is turned off, or + // there are no data rows, + // so do not suppress any columns. for (int colIdx = 0; colIdx < cols.length; ++colIdx) { result[colIdx] = false; } @@ -346,8 +350,10 @@ abstract public class Tabular { writeDivider(pw, colWidths, suppressed); for (int idx = 0; idx < cols.length; ++idx) { - Column col = cols[idx]; - pw.print("| " + centrePad(col.getLabel(), colWidths[idx]) + " "); + if (!suppressed[idx]) { + Column col = cols[idx]; + pw.print("| " + centrePad(col.getLabel(), colWidths[idx]) + " "); + } } pw.println("|"); @@ -362,7 +368,7 @@ abstract public class Tabular { if (!suppressed[colIdx]) { Object obj = row.getValue(colIdx + 1); String value = stringify(obj); - int width = stringWidth(obj); + int width = stringWidth(value); String padding = repChar(' ', colWidths[colIdx] - width); pw.print("| " + value + padding + " "); } diff --git a/src/test/java/net/jaekl/squelch/stmt/PSetTest.java b/src/test/java/net/jaekl/squelch/stmt/PSetTest.java index c33e00a..8c17d88 100644 --- a/src/test/java/net/jaekl/squelch/stmt/PSetTest.java +++ b/src/test/java/net/jaekl/squelch/stmt/PSetTest.java @@ -8,6 +8,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.sql.SQLException; +import net.jaekl.squelch.db.DbDriver; import net.jaekl.squelch.db.DbDriverMock; import net.jaekl.squelch.sql.ConnectionMock; @@ -60,23 +61,23 @@ public class PSetTest { for (String value : on) { pset.exec(driver, conn, pw, "\\pset suppress_nulls=" + value); - assertTrue(driver.isSuppressNulls()); + assertTrue(driver.isSet(DbDriver.SUPPRESS_NULLS)); pset.exec(driver, conn, pw, "\\pset Suppress_NULLS=" + value); - assertTrue(driver.isSuppressNulls()); + assertTrue(driver.isSet(DbDriver.SUPPRESS_NULLS)); } for (String value : off) { pset.exec(driver, conn, pw, "\\pset suppress_nulls=" + value); - assertFalse(driver.isSuppressNulls()); + assertFalse(driver.isSet(DbDriver.SUPPRESS_NULLS)); pset.exec(driver, conn, pw, "\\pset Suppress_NULLS=" + value); - assertFalse(driver.isSuppressNulls()); + assertFalse(driver.isSet(DbDriver.SUPPRESS_NULLS)); } for (String value : on) { pset.exec(driver, conn, pw, "\\PSET SuPPreSS_NuLLs=" + value); - assertTrue(driver.isSuppressNulls()); + assertTrue(driver.isSet(DbDriver.SUPPRESS_NULLS)); } pw.close(); @@ -93,16 +94,35 @@ public class PSetTest { for (String cmd : cmds) { DbDriverMock driver = new DbDriverMock(); - driver.setSuppressNulls(true); + driver.set(DbDriver.SUPPRESS_NULLS, true); String output = doExec(driver, cmd); - assertEquals("suppress_nulls: on\n", output); + assertEquals("suppress_nulls: true\n", output); - driver.setSuppressNulls(false); + driver.set(DbDriver.SUPPRESS_NULLS, false); output = doExec(driver, cmd); - assertEquals("suppress_nulls: off\n", output); + assertEquals("suppress_nulls: false\n", output); } } + @Test + public void testExec_displayAll() throws IOException, SQLException + { + String[] cmds = { "\\pset", "\\pset ", "\\pset ", + "\\PsET", "\\PsET ", "\\PSET", "\\PSET " }; + + for (String cmd : cmds) { + DbDriverMock driver = new DbDriverMock(); + + driver.set(DbDriver.SUPPRESS_NULLS, true); + String output = doExec(driver, cmd); + assertTrue(output.contains("suppress_nulls: true\n")); + + driver.set(DbDriver.SUPPRESS_NULLS, false); + output = doExec(driver, cmd); + assertTrue(output.contains("suppress_nulls: false\n")); + } + } + private String doExec(DbDriverMock driver, String cmd) throws IOException, SQLException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); diff --git a/src/test/java/net/jaekl/squelch/stmt/TabularTest.java b/src/test/java/net/jaekl/squelch/stmt/TabularTest.java index ddfe3d4..1dfe4db 100644 --- a/src/test/java/net/jaekl/squelch/stmt/TabularTest.java +++ b/src/test/java/net/jaekl/squelch/stmt/TabularTest.java @@ -15,6 +15,7 @@ import javax.sql.rowset.serial.SerialException; import junit.framework.Assert; +import net.jaekl.squelch.db.DbDriver; import net.jaekl.squelch.db.DbDriverMock; import net.jaekl.squelch.sql.Column; import net.jaekl.squelch.sql.Row; @@ -128,6 +129,57 @@ public class TabularTest { } } + @Test + public void test_printTable_withNulls() throws IOException, SQLException + { + DbDriverMock driver = new DbDriverMock(); + + TabularMock tabular = createTableWithNulls(); + driver.set(DbDriver.SUPPRESS_NULLS, true); + + try ( + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintWriter pw = new PrintWriter(new OutputStreamWriter(baos, StandardCharsets.UTF_8)); + ) + { + tabular.printTable(driver, pw, "No rows returned."); + pw.close(); + baos.close(); + String actual = baos.toString(); + assertEquals( "+---------+--------+------------+\n" + + "| EmpId | Value1 | Value3 |\n" + + "+---------+--------+------------+\n" + + "| 12345 | Fred | Flintstone |\n" + + "| 7654321 | Barney | Rubble |\n" + + "+---------+--------+------------+\n" + + "2 row(s) returned.\n", + actual); + } + + tabular = createTableWithNulls(); + driver.set(DbDriver.SUPPRESS_NULLS, false); + + try ( + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintWriter pw = new PrintWriter(new OutputStreamWriter(baos, StandardCharsets.UTF_8)); + ) + { + tabular.printTable(driver, pw, "No rows returned."); + pw.close(); + baos.close(); + String actual = baos.toString(); + assertEquals( "+---------+--------+--------+------------+\n" + + "| EmpId | Value1 | Value2 | Value3 |\n" + + "+---------+--------+--------+------------+\n" + + "| 12345 | Fred | null | Flintstone |\n" + + "| 7654321 | Barney | null | Rubble |\n" + + "+---------+--------+--------+------------+\n" + + "2 row(s) returned.\n", + actual); + } + + } + @Test public void test_repChar() { Tabular tabular = new TabularMock(); @@ -210,4 +262,33 @@ public class TabularTest { return tabular; } + + private TabularMock createTableWithNulls() + { + TabularMock tabular = new TabularMock(); + + Column[] cols = { + new Column("EmpId", Long.class, 10), + new Column("Value1", String.class, 14), + new Column("Value2", String.class, 14), + new Column("Value3", String.class, 14) + }; + tabular.mock_setCols(cols); + + Row row = new Row(cols.length); + row.setValue(1, Long.valueOf(12345)); + row.setValue(2, "Fred"); + row.setValue(3, null); + row.setValue(4, "Flintstone"); + tabular.mock_addRow(row); + + row = new Row(cols.length); + row.setValue(1, Long.valueOf(7654321)); + row.setValue(2, "Barney"); + row.setValue(3, null); + row.setValue(4, "Rubble"); + tabular.mock_addRow(row); + + return tabular; + } } -- 2.30.2