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;
// 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;
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("+");
}
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("|");
}
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";
}
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;
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);
}
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();