From bb9b1774cd6b96464b5c602458ad622ff17fd6cb Mon Sep 17 00:00:00 2001 From: Chris Jaekl Date: Mon, 31 Oct 2016 20:54:38 +0900 Subject: [PATCH] dumps the content of blobs, and reworks table formatting to add spaces around lines --- .../java/net/jaekl/squelch/stmt/Tabular.java | 76 +++++++++++++++++-- .../net/jaekl/squelch/stmt/DescribeTest.java | 14 ++-- .../net/jaekl/squelch/stmt/TabularTest.java | 64 ++++++++++++++-- 3 files changed, 135 insertions(+), 19 deletions(-) diff --git a/src/main/java/net/jaekl/squelch/stmt/Tabular.java b/src/main/java/net/jaekl/squelch/stmt/Tabular.java index 130f4a5..582b26a 100644 --- a/src/main/java/net/jaekl/squelch/stmt/Tabular.java +++ b/src/main/java/net/jaekl/squelch/stmt/Tabular.java @@ -1,9 +1,16 @@ package net.jaekl.squelch.stmt; import java.io.PrintWriter; +import java.nio.ByteBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.CodingErrorAction; +import java.nio.charset.StandardCharsets; import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; import java.sql.Types; +import javax.xml.bind.DatatypeConverter; + import net.jaekl.squelch.sql.Column; import net.jaekl.squelch.sql.Row; @@ -13,6 +20,8 @@ import net.jaekl.squelch.sql.Row; // Includes routines to output the data as a human-friendly table, or as CSV. abstract public class Tabular { + private static final long BLOB_MAX = 65536; + private static class RowBuffer { private final int ROW_BUF_SIZE = 50; @@ -221,9 +230,62 @@ abstract public class Tabular { return sb.toString(); } + String stringify(Object obj) throws SQLException + { + if (obj instanceof java.sql.Blob) { + java.sql.Blob blob = null; + + try { + blob = (java.sql.Blob)obj; + long length = blob.length(); + if (length > BLOB_MAX) { + length = BLOB_MAX; + } + byte[] content = blob.getBytes(1, (int)length); + + try { + // Assume that the BLOB is actually UTF-8 text, and attempt to return it that way. + // This happens to work well for the databases that I deal with regularly. + // Other users may want to change this assumption here... + return StandardCharsets.UTF_8.newDecoder().onMalformedInput(CodingErrorAction.REPORT) + .onUnmappableCharacter(CodingErrorAction.REPORT) + .decode(ByteBuffer.wrap(content)) + .toString(); + } + catch (CharacterCodingException exc) { + // If we get here, then the BLOB's content is not valid UTF-8. + // This may be because it's in a different character set, or because it's non-text + // (e.g., a bitmap image). + // We'll hex-dump it instead. + return DatatypeConverter.printHexBinary(content); + } + } + finally { + if (null != blob) { + try { + blob.free(); + } + catch (UnsupportedOperationException | SQLFeatureNotSupportedException exc) { + // free() was only a hint; if the hint is not welcome, then that's OK. + } + } + } + } + + return "" + obj; + } + + int stringWidth(Object obj) + { + if (obj instanceof java.sql.Blob) { + return 1; + } + return ("" + obj).length(); + } + void writeDivider(PrintWriter pw, int[] colWidths) { for (int idx = 0; idx < colWidths.length; ++idx) { - pw.print("+" + repChar('-', colWidths[idx])); + pw.print("+" + repChar('-', colWidths[idx] + 2)); } pw.println("+"); } @@ -233,21 +295,23 @@ abstract public class Tabular { for (int idx = 0; idx < cols.length; ++idx) { Column col = cols[idx]; - pw.print("|" + centrePad(col.getLabel(), colWidths[idx])); + pw.print("| " + centrePad(col.getLabel(), colWidths[idx]) + " "); } pw.println("|"); writeDivider(pw, colWidths); } - void writeRowBuffer(PrintWriter pw, RowBuffer rowBuf, int[] colWidths) { + void writeRowBuffer(PrintWriter pw, RowBuffer rowBuf, int[] colWidths) throws SQLException { for (int rowIdx = 0; rowIdx < rowBuf.getPending(); ++rowIdx) { Row row = rowBuf.getRow(rowIdx); for (int colIdx = 0; colIdx < colWidths.length; ++colIdx) { - String value = "" + row.getValue(colIdx + 1); - String padding = repChar(' ', colWidths[colIdx] - value.length()); - pw.print("|" + value + padding); + Object obj = row.getValue(colIdx + 1); + String value = stringify(obj); + int width = stringWidth(obj); + String padding = repChar(' ', colWidths[colIdx] - width); + pw.print("| " + value + padding + " "); } pw.println("|"); } diff --git a/src/test/java/net/jaekl/squelch/stmt/DescribeTest.java b/src/test/java/net/jaekl/squelch/stmt/DescribeTest.java index f211105..607a80d 100644 --- a/src/test/java/net/jaekl/squelch/stmt/DescribeTest.java +++ b/src/test/java/net/jaekl/squelch/stmt/DescribeTest.java @@ -199,13 +199,13 @@ public class DescribeTest { private String construct_runs_expected() { return "TABLE runs\n" - + "+--------+-----------+---------+\n" - + "| Column | Type |Modifiers|\n" - + "+--------+-----------+---------+\n" - + "|runid |int4(10) |NOT NULL |\n" - + "|projname|varchar(80)|NOT NULL |\n" - + "|version |varchar(10)|NULL |\n" - + "+--------+-----------+---------+\n" + + "+----------+-------------+-----------+\n" + + "| Column | Type | Modifiers |\n" + + "+----------+-------------+-----------+\n" + + "| runid | int4(10) | NOT NULL |\n" + + "| projname | varchar(80) | NOT NULL |\n" + + "| version | varchar(10) | NULL |\n" + + "+----------+-------------+-----------+\n" + "3 row(s) returned.\n"; } diff --git a/src/test/java/net/jaekl/squelch/stmt/TabularTest.java b/src/test/java/net/jaekl/squelch/stmt/TabularTest.java index f0e05bf..b72add3 100644 --- a/src/test/java/net/jaekl/squelch/stmt/TabularTest.java +++ b/src/test/java/net/jaekl/squelch/stmt/TabularTest.java @@ -10,6 +10,11 @@ import java.nio.charset.StandardCharsets; import java.sql.SQLException; import java.sql.Types; +import javax.sql.rowset.serial.SerialBlob; +import javax.sql.rowset.serial.SerialException; + +import junit.framework.Assert; + import net.jaekl.squelch.sql.Column; import net.jaekl.squelch.sql.Row; @@ -110,12 +115,12 @@ public class TabularTest { pw.close(); baos.close(); String actual = baos.toString(); - assertEquals( "+-------+---------+----------+\n" - + "| EmpId |FirstName| LastName |\n" - + "+-------+---------+----------+\n" - + "|12345 |Fred |Flintstone|\n" - + "|7654321|Barney |Rubble |\n" - + "+-------+---------+----------+\n" + assertEquals( "+---------+-----------+------------+\n" + + "| EmpId | FirstName | LastName |\n" + + "+---------+-----------+------------+\n" + + "| 12345 | Fred | Flintstone |\n" + + "| 7654321 | Barney | Rubble |\n" + + "+---------+-----------+------------+\n" + "2 row(s) returned.\n", actual); } @@ -131,6 +136,53 @@ public class TabularTest { assertEquals("------", tabular.repChar('-', 6)); } + @Test + public void test_stringify() throws SerialException, SQLException { + Tabular tabular = new TabularMock(); + + String[] data = { + "This is the way the world ends, not with a bang but a whimper.", + + "To be, or not to be, that is the question:\n" + + "Whether 'tis Nobler in the mind to suffer\n" + + "The Slings and Arrows of outrageous Fortune,\n" + + "Or to take Arms against a Sea of troubles,\n" + + "And by opposing end them: to die, to sleep\n" + + "No more; and by a sleep, to say we end\n" + + "The Heart-ache, and the thousand Natural shocks\n" + + "That Flesh is heir to? 'Tis a consummation\n" + + "Devoutly to be wished. To die, to sleep,\n" + + "To sleep, perchance to Dream; aye, there's the rub,\n" + + "For in that sleep of death, what dreams may come,\n" + + "When we have shuffled off this mortal coil,\n" + + "Must give us pause. There's the respect\n" + + "That makes Calamity of so long life:\n", + + "ἄνδρα μοι ἔννεπε, μοῦσα, πολύτροπον, ὃς μάλα πολλὰ\n" + + "πλάγχθη, ἐπεὶ Τροίης ἱερὸν πτολίεθρον ἔπερσεν:\n" + + "πολλῶν δ᾽ ἀνθρώπων ἴδεν ἄστεα καὶ νόον ἔγνω,\n" + + "πολλὰ δ᾽ ὅ γ᾽ ἐν πόντῳ πάθεν ἄλγεα ὃν κατὰ θυμόν,\n" + + "5ἀρνύμενος ἥν τε ψυχὴν καὶ νόστον ἑταίρων.\n" + + "ἀλλ᾽ οὐδ᾽ ὣς ἑτάρους ἐρρύσατο, ἱέμενός περ:\n" + + "αὐτῶν γὰρ σφετέρῃσιν ἀτασθαλίῃσιν ὄλοντο,\n" + + "νήπιοι, οἳ κατὰ βοῦς Ὑπερίονος Ἠελίοιο\n" + + "ἤσθιον: αὐτὰρ ὁ τοῖσιν ἀφείλετο νόστιμον ἦμαρ.\n" + + "10τῶν ἁμόθεν γε, θεά, θύγατερ Διός, εἰπὲ καὶ ἡμῖν.\n" + }; + + for (String datum : data) + { + byte[] content = datum.getBytes(StandardCharsets.UTF_8); + SerialBlob sblob = new SerialBlob(content); + + String expected = datum; + String actual = tabular.stringify(sblob); + + Assert.assertEquals(expected, actual); + } + } + + private TabularMock createEmpTable() { TabularMock tabular = new TabularMock(); -- 2.39.2