[jOOQ/jOOQ#8755] Add Loader[CSV|JSON|Rows]Step#fieldsFromSource()

This new `fieldsFromSource()` method can be used when all or a subset of
the input field names exactly match the target table column names. The
load operation can then be executed without having to specify the fields
and will create rows with all matching fields (other fields are
ignored).

Since the fields are mapped by name this requires the input to specify
the field names. For CSV this means that there must be a header row,
otherwise using `fieldsFromSource()` will result in a runtime exception.
This commit is contained in:
Knut Wannheden 2020-01-10 10:12:25 +01:00
parent 93bfffea5c
commit 11a5422efc
4 changed files with 77 additions and 18 deletions

View File

@ -40,6 +40,7 @@ package org.jooq;
import java.util.Collection;
import org.jooq.LoaderFieldMapper.LoaderFieldContext;
import org.jooq.exception.LoaderConfigurationException;
/**
* The <code>Loader</code> API is used for configuring data loads.
@ -102,4 +103,16 @@ public interface LoaderCSVStep<R extends Record> {
*/
@Support
LoaderCSVOptionsStep<R> fields(LoaderFieldMapper mapper);
/**
* Indicate that all input fields which have a corresponding field in the
* target table (with the same name) should be loaded.
* <p>
* When {@link LoaderLoadStep#execute() executing the loader} input fields
* for which there is no match in the target table will be logged and if no
* field names can be derived for the input data a
* {@link LoaderConfigurationException} will be reported.
*/
@Support
LoaderCSVOptionsStep<R> fieldsFromSource();
}

View File

@ -40,6 +40,7 @@ package org.jooq;
import java.util.Collection;
import org.jooq.LoaderFieldMapper.LoaderFieldContext;
import org.jooq.exception.LoaderConfigurationException;
/**
* The <code>Loader</code> API is used for configuring data loads.
@ -53,7 +54,7 @@ import org.jooq.LoaderFieldMapper.LoaderFieldContext;
public interface LoaderJSONStep<R extends Record> {
/**
* Specify the the fields to be loaded into the table in the correct order.
* Specify the fields to be loaded into the table in the correct order.
* <p>
* The JSON column at index <code>i</code> is inserted into the table field
* at index <code>i</code>. If <code>fields[i] == null</code> or
@ -64,7 +65,7 @@ public interface LoaderJSONStep<R extends Record> {
LoaderJSONOptionsStep<R> fields(Field<?>... fields);
/**
* Specify the the fields to be loaded into the table in the correct order.
* Specify the fields to be loaded into the table in the correct order.
* <p>
* The JSON column at index <code>i</code> is inserted into the table field
* at index <code>i</code>. If
@ -86,4 +87,16 @@ public interface LoaderJSONStep<R extends Record> {
*/
@Support
LoaderJSONOptionsStep<R> fields(LoaderFieldMapper mapper);
/**
* Indicate that all input fields which have a corresponding field in the
* target table (with the same name) should be loaded.
* <p>
* When {@link LoaderLoadStep#execute() executing the loader} input fields
* for which there is no match in the target table will be logged and if no
* field names can be derived for the input data a
* {@link LoaderConfigurationException} will be reported.
*/
@Support
LoaderJSONOptionsStep<R> fieldsFromSource();
}

View File

@ -40,6 +40,7 @@ package org.jooq;
import java.util.Collection;
import org.jooq.LoaderFieldMapper.LoaderFieldContext;
import org.jooq.exception.LoaderConfigurationException;
/**
* The <code>Loader</code> API is used for configuring data loads.
@ -103,4 +104,15 @@ public interface LoaderRowsStep<R extends Record> {
@Support
LoaderListenerStep<R> fields(LoaderFieldMapper mapper);
/**
* Indicate that all input fields which have a corresponding field in the
* target table (with the same name) should be loaded.
* <p>
* When {@link LoaderLoadStep#execute() executing the loader} input fields
* for which there is no match in the target table will be logged and if no
* field names can be derived for the input data a
* {@link LoaderConfigurationException} will be reported.
*/
@Support
LoaderListenerStep<R> fieldsFromSource();
}

View File

@ -82,6 +82,7 @@ import org.jooq.Source;
import org.jooq.Table;
import org.jooq.exception.DataAccessException;
import org.jooq.exception.LoaderConfigurationException;
import org.jooq.tools.JooqLogger;
import org.jooq.tools.StringUtils;
import org.jooq.tools.csv.CSVParser;
import org.jooq.tools.csv.CSVReader;
@ -104,6 +105,8 @@ final class LoaderImpl<R extends Record> implements
LoaderJSONOptionsStep<R>,
Loader<R> {
private static final JooqLogger log = JooqLogger.getLogger(LoaderImpl.class);
// Configuration constants
// -----------------------
private static final int ON_DUPLICATE_KEY_ERROR = 0;
@ -156,6 +159,7 @@ final class LoaderImpl<R extends Record> implements
private Field<?>[] source;
private Field<?>[] fields;
private LoaderFieldMapper fieldMapper;
private boolean fieldsFromSource;
private boolean[] primaryKey;
// Result data
@ -547,29 +551,46 @@ final class LoaderImpl<R extends Record> implements
return this;
}
@Override
public LoaderImpl<R> fieldsFromSource() {
fieldsFromSource = true;
return this;
}
private final void fields0(Object[] row) {
Field<?>[] f = new Field[row.length];
// [#5145] When loading arrays, or when CSV headers are ignored,
// the source is still null at this stage.
if (source == null)
source = Tools.fields(row.length);
if (fieldsFromSource)
throw new LoaderConfigurationException("Using fieldsFromSource() requires field names to be available in source.");
else
source = Tools.fields(row.length);
for (int i = 0; i < row.length; i++) {
final int index = i;
if (fieldMapper != null)
for (int i = 0; i < row.length; i++) {
final int index = i;
f[i] = fieldMapper.map(new LoaderFieldContext() {
@Override
public int index() {
return index;
}
f[i] = fieldMapper.map(new LoaderFieldContext() {
@Override
public int index() {
return index;
}
@Override
public Field<?> field() {
return source[index];
}
});
}
@Override
public Field<?> field() {
return source[index];
}
});
}
else if (fieldsFromSource)
for (int i = 0; i < row.length; i++) {
f[i] = table.field(source[i]);
if (f[i] == null)
log.info("No column in target table " + table + " found for input field " + source[i]);
}
fields(f);
}
@ -721,8 +742,8 @@ final class LoaderImpl<R extends Record> implements
if (row.getClass() != Object[].class)
row = Arrays.copyOf(row, row.length, Object[].class);
// [#5145] Lazy initialisation of fields off the first row
// in case LoaderFieldMapper was used.
// [#5145][#8755] Lazy initialisation of fields from the first row
// in case fields(LoaderFieldMapper) or fieldsFromSource() was used
if (fields == null)
fields0(row);