2121import de .rwth .idsg .steve .repository .DataImportExportRepository ;
2222import lombok .RequiredArgsConstructor ;
2323import lombok .extern .slf4j .Slf4j ;
24+ import org .apache .commons .lang3 .StringUtils ;
2425import org .jooq .CSVFormat ;
26+ import org .jooq .Converter ;
2527import org .jooq .Cursor ;
2628import org .jooq .DSLContext ;
29+ import org .jooq .Field ;
2730import org .jooq .LoaderError ;
2831import org .jooq .SQLDialect ;
2932import org .jooq .Table ;
3033import org .jooq .TableField ;
34+ import org .jooq .impl .AbstractConverter ;
3135import org .jooq .impl .DSL ;
36+ import org .jooq .impl .SQLDataType ;
3237import org .springframework .stereotype .Repository ;
3338import org .springframework .util .CollectionUtils ;
3439
3540import java .io .IOException ;
3641import java .io .InputStream ;
3742import java .io .Writer ;
3843import java .nio .charset .StandardCharsets ;
44+ import java .sql .Timestamp ;
45+ import java .time .Instant ;
46+ import java .util .Arrays ;
47+ import java .util .List ;
3948
4049/**
4150 * @author Sevket Goekay <sevketgokay@gmail.com>
@@ -52,7 +61,12 @@ public class DataImportExportRepositoryImpl implements DataImportExportRepositor
5261
5362 private final CSVFormat csvFormatWithHeader = new CSVFormat ();
5463 private final CSVFormat csvFormatNoHeader = csvFormatWithHeader .header (false );
64+ private final Converter <String , Timestamp > isoTimestampConverter = new IsoTimestampConverter ();
5565
66+ /**
67+ * DateTime will be exported via its toString method in the else-block of {@link org.jooq.impl.AbstractResult#format0(Object, boolean, boolean)}
68+ * because nothing else matches. The serialized values will be ISO8601 format.
69+ */
5670 @ Override
5771 public void exportCsv (Writer writer , Table <?> table ) throws IOException {
5872 // write header line
@@ -105,7 +119,7 @@ public void importCsv(InputStream in, Table<?> table) throws IOException {
105119 .bulkAfter (BATCH_SIZE ) // Put up to X rows in a single bulk statement.
106120 .batchAfter (BATCH_SIZE ) // Put up to X statements (bulk or not) in a single statement batch.
107121 .loadCSV (in , StandardCharsets .UTF_8 )
108- .fieldsCorresponding ( )
122+ .fields ( getTableFields ( table ) )
109123 .execute ();
110124
111125 if (!CollectionUtils .isEmpty (loader .errors ())) {
@@ -170,4 +184,43 @@ private void resetAutoIncrement(Table<?> table) {
170184 ctx .execute (DSL .sql ("ALTER TABLE {0} AUTO_INCREMENT = {1}" , table , DSL .val (nextVal )));
171185 }
172186
187+ // -------------------------------------------------------------------------
188+ // Loader API cannot import temporal values in ISO8601 UTC format into a
189+ // Timestamp. More context: https://groups.google.com/g/jooq-user/c/VzZdIT7Xdnc
190+ //
191+ // Because of this, we are overriding the default converter of TIMESTAMP
192+ // table fields during the import.
193+ // -------------------------------------------------------------------------
194+
195+ private List <Field <?>> getTableFields (Table <?> table ) {
196+ return Arrays .stream (table .fields ())
197+ .map (it -> {
198+ if (it .getDataType ().isTimestamp ()) {
199+ return DSL .field (it .getName (), SQLDataType .VARCHAR (50 )).convert (isoTimestampConverter );
200+ } else {
201+ return it ;
202+ }
203+ }).toList ();
204+ }
205+
206+ private static class IsoTimestampConverter extends AbstractConverter <String , Timestamp > {
207+
208+ private IsoTimestampConverter () {
209+ super (String .class , Timestamp .class );
210+ }
211+
212+ @ Override
213+ public Timestamp from (String str ) {
214+ if (StringUtils .isEmpty (str )) {
215+ return null ;
216+ }
217+ return Timestamp .from (Instant .parse (str ));
218+ }
219+
220+ @ Override
221+ public String to (Timestamp ts ) {
222+ return ts == null ? null : ts .toString ();
223+ }
224+ }
225+
173226}
0 commit comments