embulk-output-kintone 0.4.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +63 -5
  3. data/build.gradle +1 -0
  4. data/classpath/commons-csv-1.9.0.jar +0 -0
  5. data/classpath/embulk-output-kintone-1.1.0.jar +0 -0
  6. data/classpath/externalsortinginjava-0.6.2.jar +0 -0
  7. data/classpath/{shadow-kintone-java-client-0.4.1-all.jar → shadow-kintone-java-client-1.1.0-all.jar} +0 -0
  8. data/src/main/java/org/embulk/output/kintone/KintoneColumnOption.java +6 -2
  9. data/src/main/java/org/embulk/output/kintone/KintoneColumnType.java +572 -0
  10. data/src/main/java/org/embulk/output/kintone/KintoneColumnVisitor.java +214 -135
  11. data/src/main/java/org/embulk/output/kintone/KintoneMode.java +0 -1
  12. data/src/main/java/org/embulk/output/kintone/KintoneOutputPlugin.java +12 -5
  13. data/src/main/java/org/embulk/output/kintone/KintonePageOutput.java +180 -160
  14. data/src/main/java/org/embulk/output/kintone/KintoneSortColumn.java +33 -0
  15. data/src/main/java/org/embulk/output/kintone/PluginTask.java +35 -0
  16. data/src/main/java/org/embulk/output/kintone/deserializer/DeserializeApplier.java +19 -0
  17. data/src/main/java/org/embulk/output/kintone/deserializer/DeserializeException.java +7 -0
  18. data/src/main/java/org/embulk/output/kintone/deserializer/Deserializer.java +279 -0
  19. data/src/main/java/org/embulk/output/kintone/reducer/CSVInputColumnVisitor.java +78 -0
  20. data/src/main/java/org/embulk/output/kintone/reducer/CSVOutputColumnVisitor.java +79 -0
  21. data/src/main/java/org/embulk/output/kintone/reducer/ReduceException.java +11 -0
  22. data/src/main/java/org/embulk/output/kintone/reducer/ReduceType.java +190 -0
  23. data/src/main/java/org/embulk/output/kintone/reducer/ReducedPageOutput.java +100 -0
  24. data/src/main/java/org/embulk/output/kintone/reducer/Reducer.java +355 -0
  25. data/src/test/java/org/embulk/output/kintone/KintoneColumnOptionBuilder.java +9 -3
  26. data/src/test/java/org/embulk/output/kintone/KintoneColumnTypeTest.java +194 -0
  27. data/src/test/java/org/embulk/output/kintone/KintoneColumnVisitorTest.java +703 -61
  28. data/src/test/java/org/embulk/output/kintone/KintoneColumnVisitorVerifier.java +45 -14
  29. data/src/test/java/org/embulk/output/kintone/KintonePageOutputVerifier.java +43 -5
  30. data/src/test/java/org/embulk/output/kintone/TestKintoneOutputPlugin.java +106 -16
  31. data/src/test/java/org/embulk/output/kintone/TestTaskMode.java +12 -0
  32. data/src/test/java/org/embulk/output/kintone/TestTaskReduce.java +46 -0
  33. data/src/test/java/org/embulk/output/kintone/TestTaskReduceException.java +50 -0
  34. data/src/test/java/org/embulk/output/kintone/TestTaskReduceSubtable.java +46 -0
  35. data/src/test/java/org/embulk/output/kintone/deserializer/DeserializerTest.java +165 -0
  36. data/src/test/java/org/embulk/output/kintone/reducer/ReduceTypeTest.java +154 -0
  37. data/src/test/resources/org/embulk/output/kintone/task/config.yml +1 -1
  38. data/src/test/resources/org/embulk/output/kintone/task/mode/config.yml +110 -0
  39. data/src/test/resources/org/embulk/output/kintone/task/mode/input.csv +7 -7
  40. data/src/test/resources/org/embulk/output/kintone/task/mode/insert_add_ignore_nulls_records.jsonl +6 -0
  41. data/src/test/resources/org/embulk/output/kintone/task/mode/insert_add_prefer_nulls_records.jsonl +6 -0
  42. data/src/test/resources/org/embulk/output/kintone/task/mode/insert_add_records.jsonl +6 -6
  43. data/src/test/resources/org/embulk/output/kintone/task/mode/update_update_ignore_nulls_records.jsonl +3 -0
  44. data/src/test/resources/org/embulk/output/kintone/task/mode/update_update_prefer_nulls_records.jsonl +3 -0
  45. data/src/test/resources/org/embulk/output/kintone/task/mode/update_update_records.jsonl +6 -3
  46. data/src/test/resources/org/embulk/output/kintone/task/mode/upsert_add_ignore_nulls_records.jsonl +3 -0
  47. data/src/test/resources/org/embulk/output/kintone/task/mode/upsert_add_prefer_nulls_records.jsonl +3 -0
  48. data/src/test/resources/org/embulk/output/kintone/task/mode/upsert_add_records.jsonl +2 -2
  49. data/src/test/resources/org/embulk/output/kintone/task/mode/upsert_update_ignore_nulls_records.jsonl +3 -0
  50. data/src/test/resources/org/embulk/output/kintone/task/mode/upsert_update_prefer_nulls_records.jsonl +3 -0
  51. data/src/test/resources/org/embulk/output/kintone/task/mode/upsert_update_records.jsonl +4 -4
  52. data/src/test/resources/org/embulk/output/kintone/task/mode/values_ignore_nulls.json +1 -0
  53. data/src/test/resources/org/embulk/output/kintone/task/mode/values_prefer_nulls.json +1 -0
  54. data/src/test/resources/org/embulk/output/kintone/task/reduce/config.yml +171 -0
  55. data/src/test/resources/org/embulk/output/kintone/task/reduce/input.csv +7 -0
  56. data/src/test/resources/org/embulk/output/kintone/task/reduce/insert_add_ignore_nulls_records.jsonl +6 -0
  57. data/src/test/resources/org/embulk/output/kintone/task/reduce/insert_add_prefer_nulls_records.jsonl +6 -0
  58. data/src/test/resources/org/embulk/output/kintone/task/reduce/insert_add_records.jsonl +6 -0
  59. data/src/test/resources/org/embulk/output/kintone/task/reduce/update_update_ignore_nulls_records.jsonl +3 -0
  60. data/src/test/resources/org/embulk/output/kintone/task/reduce/update_update_prefer_nulls_records.jsonl +3 -0
  61. data/src/test/resources/org/embulk/output/kintone/task/reduce/update_update_records.jsonl +6 -0
  62. data/src/test/resources/org/embulk/output/kintone/task/reduce/upsert_add_ignore_nulls_records.jsonl +3 -0
  63. data/src/test/resources/org/embulk/output/kintone/task/reduce/upsert_add_prefer_nulls_records.jsonl +3 -0
  64. data/src/test/resources/org/embulk/output/kintone/task/reduce/upsert_add_records.jsonl +2 -0
  65. data/src/test/resources/org/embulk/output/kintone/task/reduce/upsert_update_ignore_nulls_records.jsonl +3 -0
  66. data/src/test/resources/org/embulk/output/kintone/task/reduce/upsert_update_prefer_nulls_records.jsonl +3 -0
  67. data/src/test/resources/org/embulk/output/kintone/task/reduce/upsert_update_records.jsonl +4 -0
  68. data/src/test/resources/org/embulk/output/kintone/task/reduce/values.json +1 -0
  69. data/src/test/resources/org/embulk/output/kintone/task/reduce/values_ignore_nulls.json +1 -0
  70. data/src/test/resources/org/embulk/output/kintone/task/reduce/values_prefer_nulls.json +1 -0
  71. data/src/test/resources/org/embulk/output/kintone/task/reduce_exception/config.yml +36 -0
  72. data/src/test/resources/org/embulk/output/kintone/task/reduce_exception/derived_columns.json +1 -0
  73. data/src/test/resources/org/embulk/output/kintone/task/reduce_exception/input.csv +13 -0
  74. data/src/test/resources/org/embulk/output/kintone/task/reduce_exception/insert_add_records.jsonl +2 -0
  75. data/src/test/resources/org/embulk/output/kintone/task/reduce_exception/update_update_records.jsonl +2 -0
  76. data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/config.yml +343 -0
  77. data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/derived_columns.json +1 -0
  78. data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/input.csv +13 -0
  79. data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/insert_add_ignore_nulls_records.jsonl +6 -0
  80. data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/insert_add_prefer_nulls_records.jsonl +6 -0
  81. data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/insert_add_records.jsonl +6 -0
  82. data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/update_update_ignore_nulls_records.jsonl +3 -0
  83. data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/update_update_prefer_nulls_records.jsonl +3 -0
  84. data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/update_update_records.jsonl +6 -0
  85. data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/upsert_add_ignore_nulls_records.jsonl +3 -0
  86. data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/upsert_add_prefer_nulls_records.jsonl +3 -0
  87. data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/upsert_add_records.jsonl +0 -0
  88. data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/upsert_update_ignore_nulls_records.jsonl +3 -0
  89. data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/upsert_update_prefer_nulls_records.jsonl +3 -0
  90. data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/upsert_update_records.jsonl +6 -0
  91. data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/values.json +1 -0
  92. data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/values_ignore_nulls.json +1 -0
  93. data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/values_prefer_nulls.json +1 -0
  94. metadata +73 -4
  95. data/classpath/embulk-output-kintone-0.4.1.jar +0 -0
@@ -0,0 +1,100 @@
1
+ package org.embulk.output.kintone.reducer;
2
+
3
+ import java.io.Closeable;
4
+ import java.io.File;
5
+ import java.io.IOException;
6
+ import java.io.OutputStreamWriter;
7
+ import java.lang.invoke.MethodHandles;
8
+ import java.nio.charset.StandardCharsets;
9
+ import java.nio.file.Files;
10
+ import org.apache.commons.csv.CSVPrinter;
11
+ import org.embulk.config.TaskReport;
12
+ import org.embulk.output.kintone.KintoneOutputPlugin;
13
+ import org.embulk.spi.ColumnVisitor;
14
+ import org.embulk.spi.Exec;
15
+ import org.embulk.spi.Page;
16
+ import org.embulk.spi.PageReader;
17
+ import org.embulk.spi.Schema;
18
+ import org.embulk.spi.TransactionalPageOutput;
19
+ import org.slf4j.Logger;
20
+ import org.slf4j.LoggerFactory;
21
+
22
+ public class ReducedPageOutput implements TransactionalPageOutput {
23
+ private static final Logger LOGGER =
24
+ LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
25
+ private final PageReader reader;
26
+ private final File file;
27
+ private final CSVPrinter printer;
28
+ private final ColumnVisitor visitor;
29
+
30
+ public ReducedPageOutput(Schema schema, int taskIndex) {
31
+ reader = new PageReader(schema);
32
+ file = file(taskIndex);
33
+ printer = printer(file);
34
+ visitor = new CSVOutputColumnVisitor(reader, printer);
35
+ }
36
+
37
+ @Override
38
+ public void add(Page page) {
39
+ reader.setPage(page);
40
+ while (reader.nextRecord()) visitColumns();
41
+ }
42
+
43
+ @Override
44
+ public void finish() {}
45
+
46
+ @Override
47
+ public void close() {
48
+ reader.close();
49
+ close(printer);
50
+ }
51
+
52
+ @Override
53
+ public void abort() {}
54
+
55
+ @Override
56
+ public TaskReport commit() {
57
+ return Exec.newTaskReport().set("path", file.getPath());
58
+ }
59
+
60
+ private void visitColumns() {
61
+ reader.getSchema().visitColumns(visitor);
62
+ println(printer);
63
+ }
64
+
65
+ private static File file(int taskIndex) {
66
+ try {
67
+ return File.createTempFile(
68
+ String.format("%s.", KintoneOutputPlugin.class.getName()),
69
+ String.format(".%d", taskIndex));
70
+ } catch (IOException e) {
71
+ throw new ReduceException(e);
72
+ }
73
+ }
74
+
75
+ private static CSVPrinter printer(File file) {
76
+ try {
77
+ return new CSVPrinter(
78
+ new OutputStreamWriter(Files.newOutputStream(file.toPath()), StandardCharsets.UTF_8),
79
+ Reducer.FORMAT);
80
+ } catch (IOException e) {
81
+ throw new ReduceException(e);
82
+ }
83
+ }
84
+
85
+ private static void println(CSVPrinter printer) {
86
+ try {
87
+ printer.println();
88
+ } catch (IOException e) {
89
+ throw new ReduceException(e);
90
+ }
91
+ }
92
+
93
+ private static void close(Closeable closeable) {
94
+ try {
95
+ closeable.close();
96
+ } catch (IOException e) {
97
+ LOGGER.warn("close error", e);
98
+ }
99
+ }
100
+ }
@@ -0,0 +1,355 @@
1
+ package org.embulk.output.kintone.reducer;
2
+
3
+ import com.google.code.externalsorting.csv.CsvExternalSort;
4
+ import com.google.code.externalsorting.csv.CsvSortOptions;
5
+ import java.io.File;
6
+ import java.io.IOException;
7
+ import java.io.OutputStream;
8
+ import java.lang.invoke.MethodHandles;
9
+ import java.nio.charset.StandardCharsets;
10
+ import java.nio.file.Files;
11
+ import java.nio.file.Path;
12
+ import java.util.ArrayList;
13
+ import java.util.Collections;
14
+ import java.util.Comparator;
15
+ import java.util.List;
16
+ import java.util.Objects;
17
+ import java.util.concurrent.atomic.AtomicInteger;
18
+ import java.util.function.Function;
19
+ import java.util.function.Predicate;
20
+ import java.util.stream.Collectors;
21
+ import java.util.stream.IntStream;
22
+ import org.apache.commons.csv.CSVFormat;
23
+ import org.apache.commons.csv.CSVParser;
24
+ import org.apache.commons.csv.CSVRecord;
25
+ import org.apache.commons.csv.QuoteMode;
26
+ import org.embulk.config.ConfigDiff;
27
+ import org.embulk.config.TaskReport;
28
+ import org.embulk.output.kintone.KintoneColumnOption;
29
+ import org.embulk.output.kintone.KintoneColumnType;
30
+ import org.embulk.output.kintone.KintoneOutputPlugin;
31
+ import org.embulk.output.kintone.KintonePageOutput;
32
+ import org.embulk.output.kintone.KintoneSortColumn;
33
+ import org.embulk.output.kintone.PluginTask;
34
+ import org.embulk.spi.Column;
35
+ import org.embulk.spi.Exec;
36
+ import org.embulk.spi.PageBuilder;
37
+ import org.embulk.spi.Schema;
38
+ import org.embulk.spi.json.JsonParser;
39
+ import org.embulk.spi.type.Type;
40
+ import org.embulk.spi.type.Types;
41
+ import org.msgpack.value.ArrayValue;
42
+ import org.msgpack.value.MapValue;
43
+ import org.msgpack.value.Value;
44
+ import org.msgpack.value.ValueFactory;
45
+ import org.slf4j.Logger;
46
+ import org.slf4j.LoggerFactory;
47
+
48
+ public class Reducer {
49
+ protected static final CSVFormat FORMAT =
50
+ CSVFormat.DEFAULT.builder().setNullString("").setQuoteMode(QuoteMode.ALL_NON_NULL).build();
51
+ protected static final JsonParser PARSER = new JsonParser();
52
+ private static final Logger LOGGER =
53
+ LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
54
+ private final PluginTask task;
55
+ private final List<Integer> indices;
56
+ private final int size;
57
+ private final Schema schema;
58
+
59
+ public Reducer(PluginTask task, Schema schema) {
60
+ this.task = task;
61
+ indices =
62
+ schema.getColumns().stream()
63
+ .filter(column -> !column.getName().matches("^.*\\..*$"))
64
+ .map(Column::getIndex)
65
+ .collect(Collectors.toList());
66
+ size = schema.size();
67
+ this.schema = schema(task, schema);
68
+ this.task.setDerivedColumns(
69
+ range().mapToObj(this.schema::getColumn).collect(Collectors.toSet()));
70
+ }
71
+
72
+ public ConfigDiff reduce(List<TaskReport> taskReports, Column column) {
73
+ File merged = file(".merged");
74
+ merge(taskReports, merged);
75
+ File sorted = file(".sorted");
76
+ sort(merged, sorted, sortOptions(task, schema, column));
77
+ AtomicInteger reduced = new AtomicInteger();
78
+ try (CSVParser parser = parser(sorted);
79
+ PageBuilder builder = builder(task, schema)) {
80
+ addRecords(column, reduced, parser, builder);
81
+ } catch (IOException e) {
82
+ throw new ReduceException(e);
83
+ }
84
+ if (reduced.get() % task.getChunkSize() != 0) {
85
+ LOGGER.info(String.format("Number of records reduced: %d", reduced.get()));
86
+ }
87
+ return Exec.newConfigDiff();
88
+ }
89
+
90
+ private void addRecords(
91
+ Column column, AtomicInteger reduced, CSVParser parser, PageBuilder builder) {
92
+ List<String> values = null;
93
+ for (CSVRecord record : parser) {
94
+ values = addRecord(column, reduced, builder, values, record);
95
+ }
96
+ if (values != null) {
97
+ addRecord(column, reduced, builder, values, null);
98
+ }
99
+ builder.finish();
100
+ }
101
+
102
+ private List<String> addRecord(
103
+ Column column,
104
+ AtomicInteger reduced,
105
+ PageBuilder builder,
106
+ List<String> values,
107
+ CSVRecord record) {
108
+ if (values == null && record == null) {
109
+ return null;
110
+ }
111
+ if (values == null) {
112
+ return values(record);
113
+ }
114
+ int index = column.getIndex();
115
+ if (record != null
116
+ && values.get(index) != null
117
+ && record.get(index) != null
118
+ && values.get(index).equals(record.get(index))) {
119
+ return values(column, values, record);
120
+ }
121
+ schema.visitColumns(new CSVInputColumnVisitor(builder, values(values)));
122
+ builder.addRecord();
123
+ reduced.getAndIncrement();
124
+ if (reduced.get() % task.getChunkSize() == 0) {
125
+ LOGGER.info(String.format("Number of records reduced: %d", reduced.get()));
126
+ }
127
+ return record == null ? null : values(record);
128
+ }
129
+
130
+ private List<String> values(CSVRecord record) {
131
+ List<String> values = new ArrayList<>(record.toList());
132
+ range().forEach(index -> values.add(value(record, index).toJson()));
133
+ return values;
134
+ }
135
+
136
+ private ArrayValue value(CSVRecord record, int index) {
137
+ ValueFactory.MapBuilder builder = ValueFactory.newMapBuilder();
138
+ String name = schema.getColumnName(index);
139
+ Predicate<Column> isId = column -> column.getName().equals(String.format("%s.$id", name));
140
+ Long id =
141
+ schema.getColumns().stream()
142
+ .filter(isId)
143
+ .findFirst()
144
+ .map(column -> record.get(column.getIndex()))
145
+ .filter(value -> !value.isEmpty())
146
+ .map(Long::parseLong)
147
+ .orElse(null);
148
+ Predicate<Column> predicate =
149
+ column -> column.getName().matches(String.format("^%s\\..*$", name));
150
+ Function<Column, String> function =
151
+ column -> column.getName().replaceFirst(String.format("^%s\\.", name), "");
152
+ schema.getColumns().stream()
153
+ .filter(isId.negate().and(predicate))
154
+ .forEach(column -> builder.put(key(function, column), value(record, column)));
155
+ MapValue value = builder.build();
156
+ return id == null && ReduceType.isEmpty(value)
157
+ ? ValueFactory.emptyArray()
158
+ : ValueFactory.newArray(ReduceType.value(id, value, sortValue(record, index)));
159
+ }
160
+
161
+ private Value key(Function<Column, String> function, Column column) {
162
+ KintoneColumnOption option = task.getColumnOptions().get(column.getName());
163
+ return ReduceType.value(option != null ? option.getFieldCode() : function.apply(column));
164
+ }
165
+
166
+ private MapValue value(CSVRecord record, Column column) {
167
+ return ReduceType.value(column, record.toList(), task.getColumnOptions().get(column.getName()));
168
+ }
169
+
170
+ private MapValue sortValue(CSVRecord record, int index) {
171
+ ValueFactory.MapBuilder builder = ValueFactory.newMapBuilder();
172
+ String name = schema.getColumnName(index);
173
+ Function<KintoneSortColumn, Column> column = sortColumn -> lookupColumn(name, sortColumn);
174
+ Function<KintoneSortColumn, Value> key = sortColumn -> ReduceType.value(sortColumn.getName());
175
+ Function<KintoneSortColumn, Value> value =
176
+ sortColumn -> ReduceType.value(record.get(column.apply(sortColumn).getIndex()));
177
+ getSortColumns(index)
178
+ .forEach(sortColumn -> builder.put(key.apply(sortColumn), value.apply(sortColumn)));
179
+ return builder.build();
180
+ }
181
+
182
+ private List<String> values(Column column, List<String> values, CSVRecord record) {
183
+ if (!indices.stream().allMatch(index -> Objects.equals(values.get(index), record.get(index)))) {
184
+ throw new ReduceException(
185
+ String.format(
186
+ "Couldn't reduce because column %s is not unique to %s\n%s expected %s but actual %s",
187
+ column.getName(),
188
+ range().mapToObj(schema::getColumnName).collect(Collectors.toList()),
189
+ indices.stream().map(schema::getColumnName).collect(Collectors.toList()),
190
+ indices.stream().map(values::get).collect(Collectors.toList()),
191
+ indices.stream().map(record::get).collect(Collectors.toList())));
192
+ }
193
+ range().forEach(index -> values.set(index, value(values, record, index).toJson()));
194
+ return values;
195
+ }
196
+
197
+ private ArrayValue value(List<String> values, CSVRecord record, int index) {
198
+ List<Value> list = new ArrayList<>(list(values, index));
199
+ list.addAll(value(record, index).list());
200
+ return list.isEmpty() ? ValueFactory.emptyArray() : ValueFactory.newArray(list);
201
+ }
202
+
203
+ private List<String> values(List<String> values) {
204
+ range().forEach(index -> values.set(index, value(values, index).toJson()));
205
+ return values;
206
+ }
207
+
208
+ private ArrayValue value(List<String> values, int index) {
209
+ List<Value> list =
210
+ list(values, index).stream()
211
+ .sorted(comparator(index))
212
+ .map(ReduceType::value)
213
+ .collect(Collectors.toList());
214
+ return list.isEmpty() ? ValueFactory.emptyArray() : ValueFactory.newArray(list);
215
+ }
216
+
217
+ private Comparator<Value> comparator(int index) {
218
+ String name = schema.getColumnName(index);
219
+ return getSortColumns(index).stream()
220
+ .map(sortColumn -> comparator(name, sortColumn))
221
+ .reduce(Comparator::thenComparing)
222
+ .orElse(Comparator.comparing(value -> 0));
223
+ }
224
+
225
+ private Comparator<Value> comparator(String name, KintoneSortColumn sortColumn) {
226
+ Column column = lookupColumn(name, sortColumn);
227
+ return Comparator.comparing(
228
+ value -> ReduceType.asString(value, sortColumn),
229
+ Comparator.nullsLast(ReduceType.comparator(column, sortColumn.getOrder())));
230
+ }
231
+
232
+ private List<KintoneSortColumn> getSortColumns(int index) {
233
+ KintoneColumnOption option = task.getColumnOptions().get(schema.getColumnName(index));
234
+ return option != null ? option.getSortColumns() : Collections.emptyList();
235
+ }
236
+
237
+ private Column lookupColumn(String name, KintoneSortColumn sortColumn) {
238
+ return schema.lookupColumn(String.format("%s.%s", name, sortColumn.getName()));
239
+ }
240
+
241
+ private IntStream range() {
242
+ return IntStream.range(size, schema.size());
243
+ }
244
+
245
+ private static Schema schema(PluginTask task, Schema schema) {
246
+ Schema.Builder builder = Schema.builder();
247
+ schema.getColumns().forEach(column -> builder.add(column.getName(), column.getType()));
248
+ schema.getColumns().stream()
249
+ .map(Column::getName)
250
+ .filter(name -> name.matches("^.*\\..*$"))
251
+ .map(name -> name.replaceFirst("\\..*$", ""))
252
+ .distinct()
253
+ .forEach(name -> builder.add(name, type(task, name)));
254
+ return builder.build();
255
+ }
256
+
257
+ private static Type type(PluginTask task, String name) {
258
+ return KintoneColumnType.getType(task.getColumnOptions().get(name), KintoneColumnType.SUBTABLE)
259
+ == KintoneColumnType.SUBTABLE
260
+ ? Types.JSON
261
+ : Types.STRING;
262
+ }
263
+
264
+ private static File file(String suffix) {
265
+ try {
266
+ return File.createTempFile(String.format("%s.", KintoneOutputPlugin.class.getName()), suffix);
267
+ } catch (IOException e) {
268
+ throw new ReduceException(e);
269
+ }
270
+ }
271
+
272
+ private static void merge(List<TaskReport> taskReports, File merged) {
273
+ try (OutputStream out = Files.newOutputStream(merged.toPath())) {
274
+ long bytes =
275
+ taskReports.stream()
276
+ .map(taskReport -> new File(taskReport.get(String.class, "path")).toPath())
277
+ .mapToLong(source -> copy(source, out))
278
+ .sum();
279
+ LOGGER.info(String.format("Number of bytes merged: %d", bytes));
280
+ } catch (IOException e) {
281
+ throw new ReduceException(e);
282
+ }
283
+ }
284
+
285
+ private static long copy(Path source, OutputStream out) {
286
+ try {
287
+ long bytes = Files.copy(source, out);
288
+ LOGGER.info(String.format("Number of bytes copied: %d", bytes));
289
+ return bytes;
290
+ } catch (IOException e) {
291
+ throw new ReduceException(e);
292
+ }
293
+ }
294
+
295
+ private static void sort(File merged, File sorted, CsvSortOptions sortOptions) {
296
+ try {
297
+ int lines =
298
+ CsvExternalSort.mergeSortedFiles(
299
+ CsvExternalSort.sortInBatch(merged, null, sortOptions, new ArrayList<>()),
300
+ sorted,
301
+ sortOptions,
302
+ false,
303
+ Collections.emptyList());
304
+ LOGGER.info(String.format("Number of lines sorted: %d", lines));
305
+ } catch (IOException | ClassNotFoundException e) {
306
+ throw new ReduceException(e);
307
+ }
308
+ }
309
+
310
+ private static CsvSortOptions sortOptions(PluginTask task, Schema schema, Column column) {
311
+ List<KintoneSortColumn> sortColumns = new ArrayList<>();
312
+ sortColumns.add(new KintoneSortColumn(column.getName(), KintoneSortColumn.Order.ASC));
313
+ sortColumns.addAll(task.getSortColumns());
314
+ return new CsvSortOptions.Builder(
315
+ comparator(schema, sortColumns),
316
+ task.getMaxSortTmpFiles().orElse(CsvExternalSort.DEFAULTMAXTEMPFILES),
317
+ task.getMaxSortMemory().orElse(CsvExternalSort.estimateAvailableMemory()))
318
+ .charset(StandardCharsets.UTF_8)
319
+ .format(FORMAT)
320
+ .build();
321
+ }
322
+
323
+ private static Comparator<CSVRecord> comparator(
324
+ Schema schema, List<KintoneSortColumn> sortColumns) {
325
+ Function<KintoneSortColumn, Comparator<CSVRecord>> function =
326
+ sortColumn -> comparator(schema, sortColumn);
327
+ return sortColumns.stream()
328
+ .skip(1)
329
+ .map(function)
330
+ .reduce(function.apply(sortColumns.get(0)), Comparator::thenComparing);
331
+ }
332
+
333
+ private static Comparator<CSVRecord> comparator(Schema schema, KintoneSortColumn sortColumn) {
334
+ Column column = schema.lookupColumn(sortColumn.getName());
335
+ return Comparator.comparing(
336
+ record -> record.get(column.getIndex()),
337
+ Comparator.nullsLast(ReduceType.comparator(column, sortColumn.getOrder())));
338
+ }
339
+
340
+ private static CSVParser parser(File sorted) {
341
+ try {
342
+ return CSVParser.parse(sorted, StandardCharsets.UTF_8, FORMAT);
343
+ } catch (IOException e) {
344
+ throw new ReduceException(e);
345
+ }
346
+ }
347
+
348
+ private static PageBuilder builder(PluginTask task, Schema schema) {
349
+ return new PageBuilder(Exec.getBufferAllocator(), schema, new KintonePageOutput(task, schema));
350
+ }
351
+
352
+ private static List<Value> list(List<String> values, int index) {
353
+ return PARSER.parse(values.get(index)).asArrayValue().list();
354
+ }
355
+ }
@@ -1,6 +1,7 @@
1
1
  package org.embulk.output.kintone;
2
2
 
3
- import java.util.Optional;
3
+ import java.util.Collections;
4
+ import java.util.List;
4
5
  import org.embulk.config.TaskSource;
5
6
 
6
7
  public class KintoneColumnOptionBuilder {
@@ -42,8 +43,8 @@ public class KintoneColumnOptionBuilder {
42
43
  }
43
44
 
44
45
  @Override
45
- public Optional<String> getTimezone() {
46
- return Optional.ofNullable(timezone);
46
+ public String getTimezone() {
47
+ return timezone;
47
48
  }
48
49
 
49
50
  @Override
@@ -51,6 +52,11 @@ public class KintoneColumnOptionBuilder {
51
52
  return valueSeparator;
52
53
  }
53
54
 
55
+ @Override
56
+ public List<KintoneSortColumn> getSortColumns() {
57
+ return Collections.emptyList();
58
+ }
59
+
54
60
  @Override
55
61
  public void validate() {}
56
62
 
@@ -0,0 +1,194 @@
1
+ package org.embulk.output.kintone;
2
+
3
+ import static org.embulk.output.kintone.KintoneColumnType.CHECK_BOX;
4
+ import static org.embulk.output.kintone.KintoneColumnType.DATE;
5
+ import static org.embulk.output.kintone.KintoneColumnType.DATETIME;
6
+ import static org.embulk.output.kintone.KintoneColumnType.DROP_DOWN;
7
+ import static org.embulk.output.kintone.KintoneColumnType.FILE;
8
+ import static org.embulk.output.kintone.KintoneColumnType.GROUP_SELECT;
9
+ import static org.embulk.output.kintone.KintoneColumnType.LINK;
10
+ import static org.embulk.output.kintone.KintoneColumnType.MULTI_LINE_TEXT;
11
+ import static org.embulk.output.kintone.KintoneColumnType.MULTI_SELECT;
12
+ import static org.embulk.output.kintone.KintoneColumnType.NUMBER;
13
+ import static org.embulk.output.kintone.KintoneColumnType.ORGANIZATION_SELECT;
14
+ import static org.embulk.output.kintone.KintoneColumnType.RADIO_BUTTON;
15
+ import static org.embulk.output.kintone.KintoneColumnType.RICH_TEXT;
16
+ import static org.embulk.output.kintone.KintoneColumnType.SINGLE_LINE_TEXT;
17
+ import static org.embulk.output.kintone.KintoneColumnType.SUBTABLE;
18
+ import static org.embulk.output.kintone.KintoneColumnType.TIME;
19
+ import static org.embulk.output.kintone.KintoneColumnType.USER_SELECT;
20
+ import static org.hamcrest.MatcherAssert.assertThat;
21
+ import static org.hamcrest.Matchers.is;
22
+ import static org.junit.Assert.assertThrows;
23
+
24
+ import com.kintone.client.model.record.CheckBoxFieldValue;
25
+ import com.kintone.client.model.record.DateFieldValue;
26
+ import com.kintone.client.model.record.DateTimeFieldValue;
27
+ import com.kintone.client.model.record.DropDownFieldValue;
28
+ import com.kintone.client.model.record.LinkFieldValue;
29
+ import com.kintone.client.model.record.MultiLineTextFieldValue;
30
+ import com.kintone.client.model.record.MultiSelectFieldValue;
31
+ import com.kintone.client.model.record.NumberFieldValue;
32
+ import com.kintone.client.model.record.RadioButtonFieldValue;
33
+ import com.kintone.client.model.record.RichTextFieldValue;
34
+ import com.kintone.client.model.record.SingleLineTextFieldValue;
35
+ import com.kintone.client.model.record.SubtableFieldValue;
36
+ import com.kintone.client.model.record.TableRow;
37
+ import com.kintone.client.model.record.TimeFieldValue;
38
+ import java.math.BigDecimal;
39
+ import java.time.Instant;
40
+ import java.time.LocalDate;
41
+ import java.time.LocalTime;
42
+ import java.time.ZonedDateTime;
43
+ import java.util.Arrays;
44
+ import java.util.List;
45
+ import java.util.stream.Collectors;
46
+ import org.embulk.output.kintone.deserializer.DeserializerTest;
47
+ import org.embulk.spi.time.Timestamp;
48
+ import org.junit.Test;
49
+ import org.msgpack.value.Value;
50
+ import org.msgpack.value.ValueFactory;
51
+
52
+ public class KintoneColumnTypeTest {
53
+ private static final Timestamp EPOCH = Timestamp.ofInstant(Instant.EPOCH);
54
+ private static final Value EMPTY = ValueFactory.newString("");
55
+
56
+ @Test
57
+ public void testSupportedTypes() {
58
+ // spotless:off
59
+ assertThat(((SingleLineTextFieldValue) SINGLE_LINE_TEXT.getFieldValue(false, null)).getValue(), is("false"));
60
+ assertThat(((SingleLineTextFieldValue) SINGLE_LINE_TEXT.getFieldValue(0L, null)).getValue(), is("0"));
61
+ assertThat(((SingleLineTextFieldValue) SINGLE_LINE_TEXT.getFieldValue(0.0d, null)).getValue(), is("0.0"));
62
+ assertThat(((SingleLineTextFieldValue) SINGLE_LINE_TEXT.getFieldValue("", null)).getValue(), is(""));
63
+ assertThat(((SingleLineTextFieldValue) SINGLE_LINE_TEXT.getFieldValue(EPOCH, null)).getValue(), is("1970-01-01T00:00:00Z"));
64
+ assertThat(((SingleLineTextFieldValue) SINGLE_LINE_TEXT.getFieldValue(EMPTY, null)).getValue(), is("\"\""));
65
+ assertThat(((MultiLineTextFieldValue) MULTI_LINE_TEXT.getFieldValue(false, null)).getValue(), is("false"));
66
+ assertThat(((MultiLineTextFieldValue) MULTI_LINE_TEXT.getFieldValue(0L, null)).getValue(), is("0"));
67
+ assertThat(((MultiLineTextFieldValue) MULTI_LINE_TEXT.getFieldValue(0.0d, null)).getValue(), is("0.0"));
68
+ assertThat(((MultiLineTextFieldValue) MULTI_LINE_TEXT.getFieldValue("", null)).getValue(), is(""));
69
+ assertThat(((MultiLineTextFieldValue) MULTI_LINE_TEXT.getFieldValue(EPOCH, null)).getValue(), is("1970-01-01T00:00:00Z"));
70
+ assertThat(((MultiLineTextFieldValue) MULTI_LINE_TEXT.getFieldValue(EMPTY, null)).getValue(), is("\"\""));
71
+ assertThat(((RichTextFieldValue) RICH_TEXT.getFieldValue(false, null)).getValue(), is("false"));
72
+ assertThat(((RichTextFieldValue) RICH_TEXT.getFieldValue(0L, null)).getValue(), is("0"));
73
+ assertThat(((RichTextFieldValue) RICH_TEXT.getFieldValue(0.0d, null)).getValue(), is("0.0"));
74
+ assertThat(((RichTextFieldValue) RICH_TEXT.getFieldValue("", null)).getValue(), is(""));
75
+ assertThat(((RichTextFieldValue) RICH_TEXT.getFieldValue(EPOCH, null)).getValue(), is("1970-01-01T00:00:00Z"));
76
+ assertThat(((RichTextFieldValue) RICH_TEXT.getFieldValue(EMPTY, null)).getValue(), is("\"\""));
77
+ assertThat(((NumberFieldValue) NUMBER.getFieldValue(false, null)).getValue(), is(number("0")));
78
+ assertThat(((NumberFieldValue) NUMBER.getFieldValue(0L, null)).getValue(), is(number("0")));
79
+ assertThat(((NumberFieldValue) NUMBER.getFieldValue(0.0d, null)).getValue(), is(number("0.0")));
80
+ assertThat(((NumberFieldValue) NUMBER.getFieldValue("", null)).getValue(), is(number("0")));
81
+ assertThat(((NumberFieldValue) NUMBER.getFieldValue(EPOCH, null)).getValue(), is(number("0")));
82
+ assertThat(((CheckBoxFieldValue) CHECK_BOX.getFieldValue("", null)).getValues(), is(list()));
83
+ assertThat(((RadioButtonFieldValue) RADIO_BUTTON.getFieldValue("", null)).getValue(), is(""));
84
+ assertThat(((MultiSelectFieldValue) MULTI_SELECT.getFieldValue("", null)).getValues(), is(list()));
85
+ assertThat(((DropDownFieldValue) DROP_DOWN.getFieldValue("", null)).getValue(), is(""));
86
+ assertThat(((DateFieldValue) DATE.getFieldValue(0L, null)).getValue(), is(date("1970-01-01")));
87
+ assertThat(((DateFieldValue) DATE.getFieldValue(0.0d, null)).getValue(), is(date("1970-01-01")));
88
+ assertThat(((DateFieldValue) DATE.getFieldValue("", null)).getValue(), is(date("1970-01-01")));
89
+ assertThat(((DateFieldValue) DATE.getFieldValue(EPOCH, null)).getValue(), is(date("1970-01-01")));
90
+ assertThat(((TimeFieldValue) TIME.getFieldValue(0L, null)).getValue(), is(time("00:00:00")));
91
+ assertThat(((TimeFieldValue) TIME.getFieldValue(0.0d, null)).getValue(), is(time("00:00:00")));
92
+ assertThat(((TimeFieldValue) TIME.getFieldValue("", null)).getValue(), is(time("00:00:00")));
93
+ assertThat(((TimeFieldValue) TIME.getFieldValue(EPOCH, null)).getValue(), is(time("00:00:00")));
94
+ assertThat(((DateTimeFieldValue) DATETIME.getFieldValue(0L, null)).getValue(), is(dateTime("1970-01-01T00:00:00Z")));
95
+ assertThat(((DateTimeFieldValue) DATETIME.getFieldValue(0.0d, null)).getValue(), is(dateTime("1970-01-01T00:00:00Z")));
96
+ assertThat(((DateTimeFieldValue) DATETIME.getFieldValue("", null)).getValue(), is(dateTime("1970-01-01T00:00:00Z")));
97
+ assertThat(((DateTimeFieldValue) DATETIME.getFieldValue(EPOCH, null)).getValue(), is(dateTime("1970-01-01T00:00:00Z")));
98
+ assertThat(((LinkFieldValue) LINK.getFieldValue("", null)).getValue(), is(""));
99
+ assertThat(((SubtableFieldValue) SUBTABLE.getFieldValue("", null)).getRows(), is(rows()));
100
+ assertThat(((SubtableFieldValue) SUBTABLE.getFieldValue(EMPTY, null)).getRows(), is(rows()));
101
+ // spotless:on
102
+ }
103
+
104
+ @Test
105
+ public void testUnsupportedTypes() {
106
+ // spotless:off
107
+ assertThrows(UnsupportedOperationException.class, () -> NUMBER.getFieldValue(EMPTY, null));
108
+ assertThrows(UnsupportedOperationException.class, () -> CHECK_BOX.getFieldValue(false, null));
109
+ assertThrows(UnsupportedOperationException.class, () -> CHECK_BOX.getFieldValue(0L, null));
110
+ assertThrows(UnsupportedOperationException.class, () -> CHECK_BOX.getFieldValue(0.0d, null));
111
+ assertThrows(UnsupportedOperationException.class, () -> CHECK_BOX.getFieldValue(EPOCH, null));
112
+ assertThrows(UnsupportedOperationException.class, () -> CHECK_BOX.getFieldValue(EMPTY, null));
113
+ assertThrows(UnsupportedOperationException.class, () -> RADIO_BUTTON.getFieldValue(false, null));
114
+ assertThrows(UnsupportedOperationException.class, () -> RADIO_BUTTON.getFieldValue(0L, null));
115
+ assertThrows(UnsupportedOperationException.class, () -> RADIO_BUTTON.getFieldValue(0.0d, null));
116
+ assertThrows(UnsupportedOperationException.class, () -> RADIO_BUTTON.getFieldValue(EPOCH, null));
117
+ assertThrows(UnsupportedOperationException.class, () -> RADIO_BUTTON.getFieldValue(EMPTY, null));
118
+ assertThrows(UnsupportedOperationException.class, () -> MULTI_SELECT.getFieldValue(false, null));
119
+ assertThrows(UnsupportedOperationException.class, () -> MULTI_SELECT.getFieldValue(0L, null));
120
+ assertThrows(UnsupportedOperationException.class, () -> MULTI_SELECT.getFieldValue(0.0d, null));
121
+ assertThrows(UnsupportedOperationException.class, () -> MULTI_SELECT.getFieldValue(EPOCH, null));
122
+ assertThrows(UnsupportedOperationException.class, () -> MULTI_SELECT.getFieldValue(EMPTY, null));
123
+ assertThrows(UnsupportedOperationException.class, () -> DROP_DOWN.getFieldValue(false, null));
124
+ assertThrows(UnsupportedOperationException.class, () -> DROP_DOWN.getFieldValue(0L, null));
125
+ assertThrows(UnsupportedOperationException.class, () -> DROP_DOWN.getFieldValue(0.0d, null));
126
+ assertThrows(UnsupportedOperationException.class, () -> DROP_DOWN.getFieldValue(EPOCH, null));
127
+ assertThrows(UnsupportedOperationException.class, () -> DROP_DOWN.getFieldValue(EMPTY, null));
128
+ assertThrows(UnsupportedOperationException.class, () -> USER_SELECT.getFieldValue(false, null));
129
+ assertThrows(UnsupportedOperationException.class, () -> USER_SELECT.getFieldValue(0L, null));
130
+ assertThrows(UnsupportedOperationException.class, () -> USER_SELECT.getFieldValue(0.0d, null));
131
+ assertThrows(UnsupportedOperationException.class, () -> USER_SELECT.getFieldValue("", null));
132
+ assertThrows(UnsupportedOperationException.class, () -> USER_SELECT.getFieldValue(EPOCH, null));
133
+ assertThrows(UnsupportedOperationException.class, () -> USER_SELECT.getFieldValue(EMPTY, null));
134
+ assertThrows(UnsupportedOperationException.class, () -> ORGANIZATION_SELECT.getFieldValue(false, null));
135
+ assertThrows(UnsupportedOperationException.class, () -> ORGANIZATION_SELECT.getFieldValue(0L, null));
136
+ assertThrows(UnsupportedOperationException.class, () -> ORGANIZATION_SELECT.getFieldValue(0.0d, null));
137
+ assertThrows(UnsupportedOperationException.class, () -> ORGANIZATION_SELECT.getFieldValue("", null));
138
+ assertThrows(UnsupportedOperationException.class, () -> ORGANIZATION_SELECT.getFieldValue(EPOCH, null));
139
+ assertThrows(UnsupportedOperationException.class, () -> ORGANIZATION_SELECT.getFieldValue(EMPTY, null));
140
+ assertThrows(UnsupportedOperationException.class, () -> GROUP_SELECT.getFieldValue(false, null));
141
+ assertThrows(UnsupportedOperationException.class, () -> GROUP_SELECT.getFieldValue(0L, null));
142
+ assertThrows(UnsupportedOperationException.class, () -> GROUP_SELECT.getFieldValue(0.0d, null));
143
+ assertThrows(UnsupportedOperationException.class, () -> GROUP_SELECT.getFieldValue("", null));
144
+ assertThrows(UnsupportedOperationException.class, () -> GROUP_SELECT.getFieldValue(EPOCH, null));
145
+ assertThrows(UnsupportedOperationException.class, () -> GROUP_SELECT.getFieldValue(EMPTY, null));
146
+ assertThrows(UnsupportedOperationException.class, () -> DATE.getFieldValue(false, null));
147
+ assertThrows(UnsupportedOperationException.class, () -> DATE.getFieldValue(EMPTY, null));
148
+ assertThrows(UnsupportedOperationException.class, () -> TIME.getFieldValue(false, null));
149
+ assertThrows(UnsupportedOperationException.class, () -> TIME.getFieldValue(EMPTY, null));
150
+ assertThrows(UnsupportedOperationException.class, () -> DATETIME.getFieldValue(false, null));
151
+ assertThrows(UnsupportedOperationException.class, () -> DATETIME.getFieldValue(EMPTY, null));
152
+ assertThrows(UnsupportedOperationException.class, () -> LINK.getFieldValue(false, null));
153
+ assertThrows(UnsupportedOperationException.class, () -> LINK.getFieldValue(0L, null));
154
+ assertThrows(UnsupportedOperationException.class, () -> LINK.getFieldValue(0.0d, null));
155
+ assertThrows(UnsupportedOperationException.class, () -> LINK.getFieldValue(EPOCH, null));
156
+ assertThrows(UnsupportedOperationException.class, () -> LINK.getFieldValue(EMPTY, null));
157
+ assertThrows(UnsupportedOperationException.class, () -> FILE.getFieldValue(false, null));
158
+ assertThrows(UnsupportedOperationException.class, () -> FILE.getFieldValue(0L, null));
159
+ assertThrows(UnsupportedOperationException.class, () -> FILE.getFieldValue(0.0d, null));
160
+ assertThrows(UnsupportedOperationException.class, () -> FILE.getFieldValue("", null));
161
+ assertThrows(UnsupportedOperationException.class, () -> FILE.getFieldValue(EPOCH, null));
162
+ assertThrows(UnsupportedOperationException.class, () -> FILE.getFieldValue(EMPTY, null));
163
+ assertThrows(UnsupportedOperationException.class, () -> SUBTABLE.getFieldValue(false, null));
164
+ assertThrows(UnsupportedOperationException.class, () -> SUBTABLE.getFieldValue(0L, null));
165
+ assertThrows(UnsupportedOperationException.class, () -> SUBTABLE.getFieldValue(0.0d, null));
166
+ assertThrows(UnsupportedOperationException.class, () -> SUBTABLE.getFieldValue(EPOCH, null));
167
+ // spotless:on
168
+ }
169
+
170
+ public static BigDecimal number(String value) {
171
+ return new BigDecimal(value);
172
+ }
173
+
174
+ @SafeVarargs
175
+ public static <T> List<T> list(T... a) {
176
+ return Arrays.asList(a);
177
+ }
178
+
179
+ public static List<TableRow> rows(Long... ids) {
180
+ return Arrays.stream(ids).map(DeserializerTest::tableRow).collect(Collectors.toList());
181
+ }
182
+
183
+ public static LocalDate date(CharSequence text) {
184
+ return LocalDate.parse(text);
185
+ }
186
+
187
+ public static LocalTime time(CharSequence text) {
188
+ return LocalTime.parse(text);
189
+ }
190
+
191
+ public static ZonedDateTime dateTime(CharSequence text) {
192
+ return ZonedDateTime.parse(text);
193
+ }
194
+ }