[#1627] Handle NULL in CSV imports/exports
This commit is contained in:
parent
02ad697a47
commit
0c4e9f00e4
@ -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());
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user