From 74afb17d34e9f9a880bfe898633609068fc338e1 Mon Sep 17 00:00:00 2001 From: Gary Gregory Date: Mon, 27 Mar 2017 13:06:53 -0700 Subject: [PATCH] [CSV-207] Provide a CSV Format for printing PostgreSQL CSV and Text formats. --- src/changes/changes.xml | 1 + .../org/apache/commons/csv/CSVFormat.java | 86 +++++++ .../commons/csv/CSVFormatPredefinedTest.java | 124 +++++----- .../apache/commons/csv/CSVPrinterTest.java | 228 ++++++++++++++++++ 4 files changed, 382 insertions(+), 57 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 557e4df6..95ad0b51 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -50,6 +50,7 @@ Add convenience API CSVFormat.print(Path, Charset) Add convenience API CSVParser.parse(Path, Charset, CSVFormat) Add convenience API CSVFormat#printer() to print to System.out + Provide a CSV Format for printing PostgreSQL CSV and Text formats. Make CSVPrinter.print(Object) GC-free. diff --git a/src/main/java/org/apache/commons/csv/CSVFormat.java b/src/main/java/org/apache/commons/csv/CSVFormat.java index 88b6da33..ea22885a 100644 --- a/src/main/java/org/apache/commons/csv/CSVFormat.java +++ b/src/main/java/org/apache/commons/csv/CSVFormat.java @@ -20,6 +20,7 @@ package org.apache.commons.csv; import static org.apache.commons.csv.Constants.BACKSLASH; import static org.apache.commons.csv.Constants.COMMA; import static org.apache.commons.csv.Constants.COMMENT; +import static org.apache.commons.csv.Constants.EMPTY; import static org.apache.commons.csv.Constants.CR; import static org.apache.commons.csv.Constants.CRLF; import static org.apache.commons.csv.Constants.DOUBLE_QUOTE_CHAR; @@ -190,6 +191,17 @@ public final class CSVFormat implements Serializable { */ MySQL(CSVFormat.MYSQL), + /** + * @see CSVFormat#POSTGRESQL_CSV + * @since 1.5 + */ + PostgreSQLCsv(CSVFormat.POSTGRESQL_CSV), + + /** + * @see CSVFormat#POSTGRESQL_CSV + */ + PostgreSQLText(CSVFormat.POSTGRESQL_TEXT), + /** * @see CSVFormat#RFC4180 */ @@ -367,6 +379,80 @@ public final class CSVFormat implements Serializable { .withQuoteMode(QuoteMode.ALL_NON_NULL); // @formatter:off + /** + * Default PostgreSQL CSV format used by the {@code COPY} operation. + * + *

+ * This is a comma-delimited format with a LF character as the line separator. Values are double quoted and special + * characters are escaped with {@code '"'}. The default NULL string is {@code ""}. + *

+ * + *

+ * Settings are: + *

+ * + * + * @see Predefined#MySQL + * @see http://dev.mysql.com/doc/refman/5.1/en/load + * -data.html + * @since 1.5 + */ + // @formatter:off + public static final CSVFormat POSTGRESQL_CSV = DEFAULT + .withDelimiter(COMMA) + .withEscape(DOUBLE_QUOTE_CHAR) + .withIgnoreEmptyLines(false) + .withQuote(DOUBLE_QUOTE_CHAR) + .withRecordSeparator(LF) + .withNullString(EMPTY) + .withQuoteMode(QuoteMode.ALL_NON_NULL); + // @formatter:off + + /** + * Default PostgreSQL text format used by the {@code COPY} operation. + * + *

+ * This is a tab-delimited format with a LF character as the line separator. Values are double quoted and special + * characters are escaped with {@code '"'}. The default NULL string is {@code "\\N"}. + *

+ * + *

+ * Settings are: + *

+ * + * + * @see Predefined#MySQL + * @see http://dev.mysql.com/doc/refman/5.1/en/load + * -data.html + * @since 1.5 + */ + // @formatter:off + public static final CSVFormat POSTGRESQL_TEXT = DEFAULT + .withDelimiter(TAB) + .withEscape(DOUBLE_QUOTE_CHAR) + .withIgnoreEmptyLines(false) + .withQuote(DOUBLE_QUOTE_CHAR) + .withRecordSeparator(LF) + .withNullString("\\N") + .withQuoteMode(QuoteMode.ALL_NON_NULL); + // @formatter:off + /** * Comma separated format as defined by RFC 4180. * diff --git a/src/test/java/org/apache/commons/csv/CSVFormatPredefinedTest.java b/src/test/java/org/apache/commons/csv/CSVFormatPredefinedTest.java index 1340534a..e4492ff2 100644 --- a/src/test/java/org/apache/commons/csv/CSVFormatPredefinedTest.java +++ b/src/test/java/org/apache/commons/csv/CSVFormatPredefinedTest.java @@ -1,57 +1,67 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.csv; - -import org.junit.Assert; -import org.junit.Test; - -/** - * Tests {@link CSVFormat.Predefined}. - */ -public class CSVFormatPredefinedTest { - - private void test(final CSVFormat format, final String enumName) { - Assert.assertEquals(format, CSVFormat.Predefined.valueOf(enumName).getFormat()); - Assert.assertEquals(format, CSVFormat.valueOf(enumName)); - } - - @Test - public void testDefault() { - test(CSVFormat.DEFAULT, "Default"); - } - - @Test - public void testExcel() { - test(CSVFormat.EXCEL, "Excel"); - } - - @Test - public void testMySQL() { - test(CSVFormat.MYSQL, "MySQL"); - } - - @Test - public void testRFC4180() { - test(CSVFormat.RFC4180, "RFC4180"); - } - - @Test - public void testTDF() { - test(CSVFormat.TDF, "TDF"); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.csv; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests {@link CSVFormat.Predefined}. + */ +public class CSVFormatPredefinedTest { + + private void test(final CSVFormat format, final String enumName) { + Assert.assertEquals(format, CSVFormat.Predefined.valueOf(enumName).getFormat()); + Assert.assertEquals(format, CSVFormat.valueOf(enumName)); + } + + @Test + public void testDefault() { + test(CSVFormat.DEFAULT, "Default"); + } + + @Test + public void testExcel() { + test(CSVFormat.EXCEL, "Excel"); + } + + @Test + public void testMySQL() { + test(CSVFormat.MYSQL, "MySQL"); + } + + @Test + public void testPostgreSqlCsv() { + test(CSVFormat.POSTGRESQL_CSV, "PostgreSQLCsv"); + } + + @Test + public void testPostgreSqlText() { + test(CSVFormat.POSTGRESQL_TEXT, "PostgreSQLText"); + } + + @Test + public void testRFC4180() { + test(CSVFormat.RFC4180, "RFC4180"); + } + + @Test + public void testTDF() { + test(CSVFormat.TDF, "TDF"); + } +} diff --git a/src/test/java/org/apache/commons/csv/CSVPrinterTest.java b/src/test/java/org/apache/commons/csv/CSVPrinterTest.java index 3ee2438f..a74ed250 100644 --- a/src/test/java/org/apache/commons/csv/CSVPrinterTest.java +++ b/src/test/java/org/apache/commons/csv/CSVPrinterTest.java @@ -713,11 +713,227 @@ public class CSVPrinterTest { assertArrayEquals(expectNulls(s, format), record0); } + @Test + @Ignore + public void testPostgreSqlCsvNullOutput() throws IOException { + Object[] s = new String[] { "NULL", null }; + CSVFormat format = CSVFormat.POSTGRESQL_CSV.withQuote(DQUOTE_CHAR).withNullString("NULL").withQuoteMode(QuoteMode.ALL_NON_NULL); + StringWriter writer = new StringWriter(); + try (final CSVPrinter printer = new CSVPrinter(writer, format)) { + printer.printRecord(s); + } + String expected = "\"NULL\",NULL\n"; + assertEquals(expected, writer.toString()); + String[] record0 = toFirstRecordValues(expected, format); + assertArrayEquals(new Object[2], record0); + + s = new String[] { "\\N", null }; + format = CSVFormat.POSTGRESQL_CSV.withNullString("\\N"); + writer = new StringWriter(); + try (final CSVPrinter printer = new CSVPrinter(writer, format)) { + printer.printRecord(s); + } + expected = "\\\\N\t\\N\n"; + assertEquals(expected, writer.toString()); + record0 = toFirstRecordValues(expected, format); + assertArrayEquals(expectNulls(s, format), record0); + + s = new String[] { "\\N", "A" }; + format = CSVFormat.POSTGRESQL_CSV.withNullString("\\N"); + writer = new StringWriter(); + try (final CSVPrinter printer = new CSVPrinter(writer, format)) { + printer.printRecord(s); + } + expected = "\\\\N\tA\n"; + assertEquals(expected, writer.toString()); + record0 = toFirstRecordValues(expected, format); + assertArrayEquals(expectNulls(s, format), record0); + + s = new String[] { "\n", "A" }; + format = CSVFormat.POSTGRESQL_CSV.withNullString("\\N"); + writer = new StringWriter(); + try (final CSVPrinter printer = new CSVPrinter(writer, format)) { + printer.printRecord(s); + } + expected = "\\n\tA\n"; + assertEquals(expected, writer.toString()); + record0 = toFirstRecordValues(expected, format); + assertArrayEquals(expectNulls(s, format), record0); + + s = new String[] { "", null }; + format = CSVFormat.POSTGRESQL_CSV.withNullString("NULL"); + writer = new StringWriter(); + try (final CSVPrinter printer = new CSVPrinter(writer, format)) { + printer.printRecord(s); + } + expected = "\tNULL\n"; + assertEquals(expected, writer.toString()); + record0 = toFirstRecordValues(expected, format); + assertArrayEquals(expectNulls(s, format), record0); + + s = new String[] { "", null }; + format = CSVFormat.POSTGRESQL_CSV; + writer = new StringWriter(); + try (final CSVPrinter printer = new CSVPrinter(writer, format)) { + printer.printRecord(s); + } + expected = "\t\\N\n"; + assertEquals(expected, writer.toString()); + record0 = toFirstRecordValues(expected, format); + assertArrayEquals(expectNulls(s, format), record0); + + s = new String[] { "\\N", "", "\u000e,\\\r" }; + format = CSVFormat.POSTGRESQL_CSV; + writer = new StringWriter(); + try (final CSVPrinter printer = new CSVPrinter(writer, format)) { + printer.printRecord(s); + } + expected = "\\\\N\t\t\u000e,\\\\\\r\n"; + assertEquals(expected, writer.toString()); + record0 = toFirstRecordValues(expected, format); + assertArrayEquals(expectNulls(s, format), record0); + + s = new String[] { "NULL", "\\\r" }; + format = CSVFormat.POSTGRESQL_CSV; + writer = new StringWriter(); + try (final CSVPrinter printer = new CSVPrinter(writer, format)) { + printer.printRecord(s); + } + expected = "NULL\t\\\\\\r\n"; + assertEquals(expected, writer.toString()); + record0 = toFirstRecordValues(expected, format); + assertArrayEquals(expectNulls(s, format), record0); + + s = new String[] { "\\\r" }; + format = CSVFormat.POSTGRESQL_CSV; + writer = new StringWriter(); + try (final CSVPrinter printer = new CSVPrinter(writer, format)) { + printer.printRecord(s); + } + expected = "\\\\\\r\n"; + assertEquals(expected, writer.toString()); + record0 = toFirstRecordValues(expected, format); + assertArrayEquals(expectNulls(s, format), record0); + } + + @Test + @Ignore + public void testPostgreSqlCsvTextOutput() throws IOException { + Object[] s = new String[] { "NULL", null }; + CSVFormat format = CSVFormat.POSTGRESQL_TEXT.withQuote(DQUOTE_CHAR).withNullString("NULL").withQuoteMode(QuoteMode.ALL_NON_NULL); + StringWriter writer = new StringWriter(); + try (final CSVPrinter printer = new CSVPrinter(writer, format)) { + printer.printRecord(s); + } + String expected = "\"NULL\"\tNULL\n"; + assertEquals(expected, writer.toString()); + String[] record0 = toFirstRecordValues(expected, format); + assertArrayEquals(new Object[2], record0); + + s = new String[] { "\\N", null }; + format = CSVFormat.POSTGRESQL_TEXT.withNullString("\\N"); + writer = new StringWriter(); + try (final CSVPrinter printer = new CSVPrinter(writer, format)) { + printer.printRecord(s); + } + expected = "\\\\N\t\\N\n"; + assertEquals(expected, writer.toString()); + record0 = toFirstRecordValues(expected, format); + assertArrayEquals(expectNulls(s, format), record0); + + s = new String[] { "\\N", "A" }; + format = CSVFormat.POSTGRESQL_TEXT.withNullString("\\N"); + writer = new StringWriter(); + try (final CSVPrinter printer = new CSVPrinter(writer, format)) { + printer.printRecord(s); + } + expected = "\\\\N\tA\n"; + assertEquals(expected, writer.toString()); + record0 = toFirstRecordValues(expected, format); + assertArrayEquals(expectNulls(s, format), record0); + + s = new String[] { "\n", "A" }; + format = CSVFormat.POSTGRESQL_TEXT.withNullString("\\N"); + writer = new StringWriter(); + try (final CSVPrinter printer = new CSVPrinter(writer, format)) { + printer.printRecord(s); + } + expected = "\\n\tA\n"; + assertEquals(expected, writer.toString()); + record0 = toFirstRecordValues(expected, format); + assertArrayEquals(expectNulls(s, format), record0); + + s = new String[] { "", null }; + format = CSVFormat.POSTGRESQL_TEXT.withNullString("NULL"); + writer = new StringWriter(); + try (final CSVPrinter printer = new CSVPrinter(writer, format)) { + printer.printRecord(s); + } + expected = "\tNULL\n"; + assertEquals(expected, writer.toString()); + record0 = toFirstRecordValues(expected, format); + assertArrayEquals(expectNulls(s, format), record0); + + s = new String[] { "", null }; + format = CSVFormat.POSTGRESQL_TEXT; + writer = new StringWriter(); + try (final CSVPrinter printer = new CSVPrinter(writer, format)) { + printer.printRecord(s); + } + expected = "\t\\N\n"; + assertEquals(expected, writer.toString()); + record0 = toFirstRecordValues(expected, format); + assertArrayEquals(expectNulls(s, format), record0); + + s = new String[] { "\\N", "", "\u000e,\\\r" }; + format = CSVFormat.POSTGRESQL_TEXT; + writer = new StringWriter(); + try (final CSVPrinter printer = new CSVPrinter(writer, format)) { + printer.printRecord(s); + } + expected = "\\\\N\t\t\u000e,\\\\\\r\n"; + assertEquals(expected, writer.toString()); + record0 = toFirstRecordValues(expected, format); + assertArrayEquals(expectNulls(s, format), record0); + + s = new String[] { "NULL", "\\\r" }; + format = CSVFormat.POSTGRESQL_TEXT; + writer = new StringWriter(); + try (final CSVPrinter printer = new CSVPrinter(writer, format)) { + printer.printRecord(s); + } + expected = "NULL\t\\\\\\r\n"; + assertEquals(expected, writer.toString()); + record0 = toFirstRecordValues(expected, format); + assertArrayEquals(expectNulls(s, format), record0); + + s = new String[] { "\\\r" }; + format = CSVFormat.POSTGRESQL_TEXT; + writer = new StringWriter(); + try (final CSVPrinter printer = new CSVPrinter(writer, format)) { + printer.printRecord(s); + } + expected = "\\\\\\r\n"; + assertEquals(expected, writer.toString()); + record0 = toFirstRecordValues(expected, format); + assertArrayEquals(expectNulls(s, format), record0); + } + @Test public void testMySqlNullStringDefault() { assertEquals("\\N", CSVFormat.MYSQL.getNullString()); } + @Test + public void testPostgreSQLNullStringDefaultCsv() { + assertEquals("", CSVFormat.POSTGRESQL_CSV.getNullString()); + } + + @Test + public void testPostgreSQLNullStringDefaultText() { + assertEquals("\\N", CSVFormat.POSTGRESQL_TEXT.getNullString()); + } + @Test(expected = IllegalArgumentException.class) public void testNewCsvPrinterAppendableNullFormat() throws Exception { try (final CSVPrinter printer = new CSVPrinter(new StringWriter(), null)) { @@ -948,6 +1164,18 @@ public class CSVPrinterTest { doRandom(CSVFormat.MYSQL, ITERATIONS_FOR_RANDOM_TEST); } + @Test + @Ignore + public void testRandomPostgreSqlCsv() throws Exception { + doRandom(CSVFormat.POSTGRESQL_CSV, ITERATIONS_FOR_RANDOM_TEST); + } + + @Test + @Ignore + public void testRandomPostgreSqlText() throws Exception { + doRandom(CSVFormat.POSTGRESQL_TEXT, ITERATIONS_FOR_RANDOM_TEST); + } + @Test public void testRandomRfc4180() throws Exception { doRandom(CSVFormat.RFC4180, ITERATIONS_FOR_RANDOM_TEST);