[#1627] Handle NULL in CSV imports/exports

This commit is contained in:
Lukas Eder 2012-07-27 17:03:26 +02:00
parent 02ad697a47
commit 0c4e9f00e4
5 changed files with 76 additions and 18 deletions

View File

@ -38,6 +38,7 @@ package org.jooq.test._.testcases;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static org.jooq.SQLDialect.ORACLE;
import static org.jooq.impl.Factory.count;
import java.sql.SQLException;
@ -191,16 +192,16 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658,
assertEquals(2, loader.ignored());
assertEquals(2, (int) create().select(count).from(TAuthor()).fetchOne(count));
// Two records
// -----------
// Two records with different NULL representations for FIRST_NAME
// --------------------------------------------------------------
loader =
create().loadInto(TAuthor())
.loadCSV(
"####Some Data####\n" +
"\"ID\",\"Last Qualifier\"\r" +
"3,Hesse\n" +
"4,Frisch")
.fields(TAuthor_ID(), TAuthor_LAST_NAME())
"3,\"\",Hesse\n" +
"4,,Frisch")
.fields(TAuthor_ID(), TAuthor_FIRST_NAME(), TAuthor_LAST_NAME())
.quote('"')
.separator(',')
.ignoreRows(2)
@ -214,19 +215,23 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658,
.from(TAuthor())
.where(TAuthor_ID().in(3, 4))
.and(TAuthor_LAST_NAME().in("Hesse", "Frisch"))
.and(getDialect() == ORACLE ?
TAuthor_FIRST_NAME().isNull() :
TAuthor_FIRST_NAME().equal(""))
.fetchOne(count));
assertEquals(2, create().delete(TAuthor()).where(TAuthor_ID().in(3, 4)).execute());
// Two records but don't load one column
// -------------------------------------
// Two records but don't load one column, and specify a value for NULL
// -------------------------------------------------------------------
loader =
create().loadInto(TAuthor())
.loadCSV(
"\"ID\",\"First Qualifier\",\"Last Qualifier\"\r" +
"5,Hermann,Hesse\n" +
"6,\"Max\",Frisch")
.fields(TAuthor_ID(), null, TAuthor_LAST_NAME())
"\"ID\",ignore,\"First Qualifier\",\"Last Qualifier\"\r" +
"5,asdf,{null},Hesse\n" +
"6,asdf,\"\",Frisch")
.fields(TAuthor_ID(), null, TAuthor_FIRST_NAME(), TAuthor_LAST_NAME())
.nullString("{null}")
.execute();
assertEquals(2, loader.processed());
@ -247,7 +252,7 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658,
assertEquals("Hesse", result.getValue(0, TAuthor_LAST_NAME()));
assertEquals("Frisch", result.getValue(1, TAuthor_LAST_NAME()));
assertEquals(null, result.getValue(0, TAuthor_FIRST_NAME()));
assertEquals(null, result.getValue(1, TAuthor_FIRST_NAME()));
assertEquals(getDialect() == ORACLE ? null : "", result.getValue(1, TAuthor_FIRST_NAME()));
assertEquals(2, create().delete(TAuthor()).where(TAuthor_ID().in(5, 6)).execute());

View File

@ -68,4 +68,20 @@ public interface LoaderCSVOptionsStep<R extends TableRecord<R>> extends LoaderLo
*/
@Support
LoaderCSVOptionsStep<R> separator(char separator);
/**
* Specify the input string representation of <code>NULL</code>.
* <p>
* By default, this is set to <code>null</code>, which means that all empty
* strings are loaded into the database as such. In some databases (e.g.
* {@link SQLDialect#ORACLE}), this is effectively the same as loading
* <code>NULL</code>.
* <p>
* In order to treat empty strings as <code>null</code>, you can set the
* <code>nullString</code> to <code>""</code>. If the null string is
* overridden with something like <code>{null}</code>, for instance, then
* empty strings will also be loaded as such by jOOQ.
*/
@Support
LoaderCSVOptionsStep<R> nullString(String nullString);
}

View File

@ -1724,7 +1724,7 @@ public interface Result<R extends Record> extends FieldProvider, List<R>, Attach
/**
* Get a simple formatted representation of this result as CSV.
* <p>
* This is the same as calling <code>formatCSV(',')</code>
* This is the same as calling <code>formatCSV(',', "")</code>
*
* @return The formatted result
*/
@ -1732,12 +1732,23 @@ public interface Result<R extends Record> extends FieldProvider, List<R>, Attach
/**
* Get a simple formatted representation of this result as CSV.
* <p>
* This is the same as calling <code>formatCSV(delimiter, "")</code>
*
* @param delimiter The delimiter to use between records
* @return The formatted result
*/
String formatCSV(char delimiter);
/**
* Get a simple formatted representation of this result as CSV.
*
* @param delimiter The delimiter to use between records
* @param nullString A special string for encoding <code>NULL</code> values.
* @return The formatted result
*/
String formatCSV(char delimiter, String nullString);
/**
* Get a simple formatted representation of this result as a JSON array of
* array. The format is the following: <code><pre>

View File

@ -63,6 +63,7 @@ import org.jooq.Table;
import org.jooq.TableRecord;
import org.jooq.UpdatableTable;
import org.jooq.exception.DataAccessException;
import org.jooq.tools.StringUtils;
import org.jooq.tools.csv.CSVParser;
import org.jooq.tools.csv.CSVReader;
@ -113,6 +114,7 @@ class LoaderImpl<R extends TableRecord<R>> implements
private int ignoreRows = 1;
private char quote = CSVParser.DEFAULT_QUOTE_CHARACTER;
private char separator = CSVParser.DEFAULT_SEPARATOR;
private String nullString = null;
private Field<?>[] fields;
private boolean[] mainKey;
@ -306,6 +308,12 @@ class LoaderImpl<R extends TableRecord<R>> implements
return this;
}
@Override
public final LoaderImpl<R> nullString(String n) {
this.nullString = n;
return this;
}
// -------------------------------------------------------------------------
// XML configuration
// -------------------------------------------------------------------------
@ -340,6 +348,14 @@ class LoaderImpl<R extends TableRecord<R>> implements
// TODO: When running in COMMIT_AFTER > 1 or COMMIT_ALL mode, then
// it might be better to bulk load / merge n records
rowloop: while ((row = reader.readNext()) != null) {
// [#1627] Handle NULL values
for (int i = 0; i < row.length; i++) {
if (StringUtils.equals(nullString, row[i])) {
row[i] = null;
}
}
processed++;
InsertQuery<R> insert = create.insertQuery(table);

View File

@ -1057,17 +1057,22 @@ class ResultImpl<R extends Record> implements Result<R>, AttachableInternal {
@Override
public final String formatCSV() {
return formatCSV(',');
return formatCSV(',', "");
}
@Override
public final String formatCSV(char delimiter) {
return formatCSV(delimiter, "");
}
@Override
public final String formatCSV(char delimiter, String nullString) {
StringBuilder sb = new StringBuilder();
String sep1 = "";
for (Field<?> field : getFields()) {
sb.append(sep1);
sb.append(formatCSV0(field.getName()));
sb.append(formatCSV0(field.getName(), ""));
sep1 = Character.toString(delimiter);
}
@ -1078,7 +1083,7 @@ class ResultImpl<R extends Record> implements Result<R>, AttachableInternal {
String sep2 = "";
for (Field<?> field : getFields()) {
sb.append(sep2);
sb.append(formatCSV0(record.getValue(field)));
sb.append(formatCSV0(record.getValue(field), nullString));
sep2 = Character.toString(delimiter);
}
@ -1089,11 +1094,16 @@ class ResultImpl<R extends Record> implements Result<R>, AttachableInternal {
return sb.toString();
}
private final String formatCSV0(Object value) {
private final String formatCSV0(Object value, String nullString) {
// Escape null and empty strings
if (value == null || "".equals(value)) {
return "\"\"";
if (StringUtils.isEmpty(nullString)) {
return "\"\"";
}
else {
return nullString;
}
}
String result = format0(value);