dumps the content of blobs, and reworks table formatting to add spaces around lines
authorChris Jaekl <cejaekl@yahoo.com>
Mon, 31 Oct 2016 11:54:38 +0000 (20:54 +0900)
committerChris Jaekl <cejaekl@yahoo.com>
Mon, 31 Oct 2016 11:54:38 +0000 (20:54 +0900)
src/main/java/net/jaekl/squelch/stmt/Tabular.java
src/test/java/net/jaekl/squelch/stmt/DescribeTest.java
src/test/java/net/jaekl/squelch/stmt/TabularTest.java

index 130f4a563eefaef9977c7e56ccc12ea597368557..582b26ad1df1a6eee18dd4a2bcc5bf1ff449470e 100644 (file)
@@ -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("|");
                }
index f2111051530e20d12e64ba6fc6fdb277bb905dc1..607a80dfc6e5deef1c3b3d9a7b1bd4deb849c63b 100644 (file)
@@ -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";
        }
 
index f0e05bffd1f0e8500e96fc767f350dd257e84997..b72add390ab0aefcc64d8c59e20a96c0d1f71ff2 100644 (file)
@@ -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();