embulk-output-kintone 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -0
  3. data/classpath/embulk-output-kintone-1.2.0.jar +0 -0
  4. data/classpath/{shadow-kintone-java-client-1.1.0-all.jar → shadow-kintone-java-client-1.2.0-all.jar} +0 -0
  5. data/src/main/java/org/embulk/output/kintone/KintoneClient.java +117 -0
  6. data/src/main/java/org/embulk/output/kintone/KintoneColumnType.java +30 -13
  7. data/src/main/java/org/embulk/output/kintone/KintoneColumnVisitor.java +54 -4
  8. data/src/main/java/org/embulk/output/kintone/KintoneMode.java +52 -4
  9. data/src/main/java/org/embulk/output/kintone/KintoneOutputPlugin.java +0 -17
  10. data/src/main/java/org/embulk/output/kintone/KintonePageOutput.java +106 -118
  11. data/src/main/java/org/embulk/output/kintone/PluginTask.java +5 -0
  12. data/src/main/java/org/embulk/output/kintone/record/Id.java +18 -0
  13. data/src/main/java/org/embulk/output/kintone/record/IdOrUpdateKey.java +45 -0
  14. data/src/main/java/org/embulk/output/kintone/record/Skip.java +14 -0
  15. data/src/main/java/org/embulk/output/kintone/util/Lazy.java +20 -0
  16. data/src/test/java/org/embulk/output/kintone/KintoneClientTest.java +139 -0
  17. data/src/test/java/org/embulk/output/kintone/KintoneColumnVisitorTest.java +33 -33
  18. data/src/test/java/org/embulk/output/kintone/KintoneColumnVisitorVerifier.java +6 -6
  19. data/src/test/java/org/embulk/output/kintone/KintonePageOutputVerifier.java +79 -53
  20. data/src/test/java/org/embulk/output/kintone/MockClient.java +112 -0
  21. data/src/test/java/org/embulk/output/kintone/TestKintoneOutputPlugin.java +64 -16
  22. data/src/test/java/org/embulk/output/kintone/TestTaskMode.java +1 -0
  23. data/src/test/java/org/embulk/output/kintone/TestTaskReduce.java +1 -0
  24. data/src/test/java/org/embulk/output/kintone/TestTaskReduceSubtable.java +1 -0
  25. data/src/test/java/org/embulk/output/kintone/TestTaskSkip.java +80 -0
  26. data/src/test/java/org/embulk/output/kintone/TestTaskSkipId.java +80 -0
  27. data/src/test/resources/org/embulk/output/kintone/client/config.yml +1 -0
  28. data/src/test/resources/org/embulk/output/kintone/task/config.yml +1 -0
  29. data/src/test/resources/org/embulk/output/kintone/task/mode/upsert_never_skip_double_single_line_text_add_values.json +1 -0
  30. data/src/test/resources/org/embulk/output/kintone/task/reduce/upsert_never_skip_double_single_line_text_add_values.json +1 -0
  31. data/src/test/resources/org/embulk/output/kintone/task/skip/config.yml +1 -0
  32. data/src/test/resources/org/embulk/output/kintone/task/skip/input.csv +7 -0
  33. data/src/test/resources/org/embulk/output/kintone/task/skip/insert_add_records.jsonl +6 -0
  34. data/src/test/resources/org/embulk/output/kintone/task/skip/insert_always_skip_add_records.jsonl +6 -0
  35. data/src/test/resources/org/embulk/output/kintone/task/skip/insert_always_skip_prefer_nulls_add_records.jsonl +6 -0
  36. data/src/test/resources/org/embulk/output/kintone/task/skip/insert_never_skip_add_records.jsonl +6 -0
  37. data/src/test/resources/org/embulk/output/kintone/task/skip/insert_never_skip_prefer_nulls_add_records.jsonl +6 -0
  38. data/src/test/resources/org/embulk/output/kintone/task/skip/insert_prefer_nulls_add_records.jsonl +6 -0
  39. data/src/test/resources/org/embulk/output/kintone/task/skip/update_always_skip_long_number_add_values.json +1 -0
  40. data/src/test/resources/org/embulk/output/kintone/task/skip/update_always_skip_long_number_values.json +1 -0
  41. data/src/test/resources/org/embulk/output/kintone/task/skip/update_always_skip_prefer_nulls_long_number_add_values.json +1 -0
  42. data/src/test/resources/org/embulk/output/kintone/task/skip/update_always_skip_prefer_nulls_long_number_values.json +1 -0
  43. data/src/test/resources/org/embulk/output/kintone/task/skip/update_always_skip_prefer_nulls_update_records.jsonl +2 -0
  44. data/src/test/resources/org/embulk/output/kintone/task/skip/update_always_skip_update_records.jsonl +5 -0
  45. data/src/test/resources/org/embulk/output/kintone/task/skip/update_never_skip_update_records.jsonl +6 -0
  46. data/src/test/resources/org/embulk/output/kintone/task/skip/update_prefer_nulls_update_records.jsonl +3 -0
  47. data/src/test/resources/org/embulk/output/kintone/task/skip/update_update_records.jsonl +6 -0
  48. data/src/test/resources/org/embulk/output/kintone/task/skip/upsert_add_records.jsonl +1 -0
  49. data/src/test/resources/org/embulk/output/kintone/task/skip/upsert_always_skip_prefer_nulls_string_single_line_text_add_values.json +1 -0
  50. data/src/test/resources/org/embulk/output/kintone/task/skip/upsert_always_skip_prefer_nulls_string_single_line_text_values.json +1 -0
  51. data/src/test/resources/org/embulk/output/kintone/task/skip/upsert_always_skip_prefer_nulls_update_records.jsonl +2 -0
  52. data/src/test/resources/org/embulk/output/kintone/task/skip/upsert_always_skip_string_single_line_text_add_values.json +1 -0
  53. data/src/test/resources/org/embulk/output/kintone/task/skip/upsert_always_skip_string_single_line_text_values.json +1 -0
  54. data/src/test/resources/org/embulk/output/kintone/task/skip/upsert_always_skip_update_records.jsonl +2 -0
  55. data/src/test/resources/org/embulk/output/kintone/task/skip/upsert_never_skip_add_records.jsonl +4 -0
  56. data/src/test/resources/org/embulk/output/kintone/task/skip/upsert_never_skip_prefer_nulls_add_records.jsonl +4 -0
  57. data/src/test/resources/org/embulk/output/kintone/task/skip/upsert_never_skip_prefer_nulls_string_single_line_text_add_values.json +1 -0
  58. data/src/test/resources/org/embulk/output/kintone/task/skip/upsert_never_skip_prefer_nulls_string_single_line_text_values.json +1 -0
  59. data/src/test/resources/org/embulk/output/kintone/task/skip/upsert_never_skip_prefer_nulls_update_records.jsonl +2 -0
  60. data/src/test/resources/org/embulk/output/kintone/task/skip/upsert_never_skip_string_single_line_text_add_values.json +1 -0
  61. data/src/test/resources/org/embulk/output/kintone/task/skip/upsert_never_skip_string_single_line_text_values.json +1 -0
  62. data/src/test/resources/org/embulk/output/kintone/task/skip/upsert_never_skip_update_records.jsonl +2 -0
  63. data/src/test/resources/org/embulk/output/kintone/task/skip/upsert_prefer_nulls_add_records.jsonl +1 -0
  64. data/src/test/resources/org/embulk/output/kintone/task/skip/upsert_prefer_nulls_string_single_line_text_add_values.json +1 -0
  65. data/src/test/resources/org/embulk/output/kintone/task/skip/upsert_prefer_nulls_string_single_line_text_values.json +1 -0
  66. data/src/test/resources/org/embulk/output/kintone/task/skip/upsert_prefer_nulls_update_records.jsonl +2 -0
  67. data/src/test/resources/org/embulk/output/kintone/task/skip/upsert_string_single_line_text_add_values.json +1 -0
  68. data/src/test/resources/org/embulk/output/kintone/task/skip/upsert_string_single_line_text_values.json +1 -0
  69. data/src/test/resources/org/embulk/output/kintone/task/skip/upsert_update_records.jsonl +2 -0
  70. data/src/test/resources/org/embulk/output/kintone/task/skip_id/config.yml +1 -0
  71. data/src/test/resources/org/embulk/output/kintone/task/skip_id/input.csv +7 -0
  72. data/src/test/resources/org/embulk/output/kintone/task/skip_id/insert_add_records.jsonl +6 -0
  73. data/src/test/resources/org/embulk/output/kintone/task/skip_id/insert_always_skip_add_records.jsonl +6 -0
  74. data/src/test/resources/org/embulk/output/kintone/task/skip_id/insert_always_skip_prefer_nulls_add_records.jsonl +6 -0
  75. data/src/test/resources/org/embulk/output/kintone/task/skip_id/insert_never_skip_add_records.jsonl +6 -0
  76. data/src/test/resources/org/embulk/output/kintone/task/skip_id/insert_never_skip_prefer_nulls_add_records.jsonl +6 -0
  77. data/src/test/resources/org/embulk/output/kintone/task/skip_id/insert_prefer_nulls_add_records.jsonl +6 -0
  78. data/src/test/resources/org/embulk/output/kintone/task/skip_id/update_always_skip__id_add_values.json +1 -0
  79. data/src/test/resources/org/embulk/output/kintone/task/skip_id/update_always_skip__id_values.json +1 -0
  80. data/src/test/resources/org/embulk/output/kintone/task/skip_id/update_always_skip_prefer_nulls__id_add_values.json +1 -0
  81. data/src/test/resources/org/embulk/output/kintone/task/skip_id/update_always_skip_prefer_nulls__id_values.json +1 -0
  82. data/src/test/resources/org/embulk/output/kintone/task/skip_id/update_always_skip_prefer_nulls_update_records.jsonl +2 -0
  83. data/src/test/resources/org/embulk/output/kintone/task/skip_id/update_always_skip_update_records.jsonl +5 -0
  84. data/src/test/resources/org/embulk/output/kintone/task/skip_id/update_never_skip_update_records.jsonl +6 -0
  85. data/src/test/resources/org/embulk/output/kintone/task/skip_id/update_prefer_nulls_update_records.jsonl +3 -0
  86. data/src/test/resources/org/embulk/output/kintone/task/skip_id/update_update_records.jsonl +6 -0
  87. data/src/test/resources/org/embulk/output/kintone/task/skip_id/upsert__id_add_values.json +1 -0
  88. data/src/test/resources/org/embulk/output/kintone/task/skip_id/upsert__id_values.json +1 -0
  89. data/src/test/resources/org/embulk/output/kintone/task/skip_id/upsert_always_skip__id_add_values.json +1 -0
  90. data/src/test/resources/org/embulk/output/kintone/task/skip_id/upsert_always_skip__id_values.json +1 -0
  91. data/src/test/resources/org/embulk/output/kintone/task/skip_id/upsert_always_skip_prefer_nulls__id_add_values.json +1 -0
  92. data/src/test/resources/org/embulk/output/kintone/task/skip_id/upsert_always_skip_prefer_nulls__id_values.json +1 -0
  93. data/src/test/resources/org/embulk/output/kintone/task/skip_id/upsert_always_skip_prefer_nulls_update_records.jsonl +2 -0
  94. data/src/test/resources/org/embulk/output/kintone/task/skip_id/upsert_always_skip_update_records.jsonl +5 -0
  95. data/src/test/resources/org/embulk/output/kintone/task/skip_id/upsert_never_skip__id_add_values.json +1 -0
  96. data/src/test/resources/org/embulk/output/kintone/task/skip_id/upsert_never_skip__id_values.json +1 -0
  97. data/src/test/resources/org/embulk/output/kintone/task/skip_id/upsert_never_skip_add_records.jsonl +1 -0
  98. data/src/test/resources/org/embulk/output/kintone/task/skip_id/upsert_never_skip_prefer_nulls__id_add_values.json +1 -0
  99. data/src/test/resources/org/embulk/output/kintone/task/skip_id/upsert_never_skip_prefer_nulls__id_values.json +1 -0
  100. data/src/test/resources/org/embulk/output/kintone/task/skip_id/upsert_never_skip_prefer_nulls_add_records.jsonl +4 -0
  101. data/src/test/resources/org/embulk/output/kintone/task/skip_id/upsert_never_skip_prefer_nulls_update_records.jsonl +2 -0
  102. data/src/test/resources/org/embulk/output/kintone/task/skip_id/upsert_never_skip_update_records.jsonl +5 -0
  103. data/src/test/resources/org/embulk/output/kintone/task/skip_id/upsert_prefer_nulls__id_add_values.json +1 -0
  104. data/src/test/resources/org/embulk/output/kintone/task/skip_id/upsert_prefer_nulls__id_values.json +1 -0
  105. data/src/test/resources/org/embulk/output/kintone/task/skip_id/upsert_prefer_nulls_add_records.jsonl +3 -0
  106. data/src/test/resources/org/embulk/output/kintone/task/skip_id/upsert_prefer_nulls_update_records.jsonl +2 -0
  107. data/src/test/resources/org/embulk/output/kintone/task/skip_id/upsert_update_records.jsonl +5 -0
  108. metadata +131 -43
  109. data/classpath/embulk-output-kintone-1.1.0.jar +0 -0
  110. data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/upsert_add_records.jsonl +0 -0
  111. /data/src/test/resources/org/embulk/output/kintone/task/mode/{insert_add_ignore_nulls_records.jsonl → insert_ignore_nulls_add_records.jsonl} +0 -0
  112. /data/src/test/resources/org/embulk/output/kintone/task/mode/{insert_add_prefer_nulls_records.jsonl → insert_prefer_nulls_add_records.jsonl} +0 -0
  113. /data/src/test/resources/org/embulk/output/kintone/task/mode/{update_update_ignore_nulls_records.jsonl → update_ignore_nulls_update_records.jsonl} +0 -0
  114. /data/src/test/resources/org/embulk/output/kintone/task/mode/{update_update_prefer_nulls_records.jsonl → update_prefer_nulls_update_records.jsonl} +0 -0
  115. /data/src/test/resources/org/embulk/output/kintone/task/mode/{upsert_add_records.jsonl → upsert_never_skip_add_records.jsonl} +0 -0
  116. /data/src/test/resources/org/embulk/output/kintone/task/mode/{values.json → upsert_never_skip_double_single_line_text_values.json} +0 -0
  117. /data/src/test/resources/org/embulk/output/kintone/task/mode/{upsert_add_ignore_nulls_records.jsonl → upsert_never_skip_ignore_nulls_add_records.jsonl} +0 -0
  118. /data/src/test/resources/org/embulk/output/kintone/task/mode/{values_ignore_nulls.json → upsert_never_skip_ignore_nulls_double_single_line_text_values.json} +0 -0
  119. /data/src/test/resources/org/embulk/output/kintone/task/mode/{upsert_update_ignore_nulls_records.jsonl → upsert_never_skip_ignore_nulls_update_records.jsonl} +0 -0
  120. /data/src/test/resources/org/embulk/output/kintone/task/mode/{upsert_add_prefer_nulls_records.jsonl → upsert_never_skip_prefer_nulls_add_records.jsonl} +0 -0
  121. /data/src/test/resources/org/embulk/output/kintone/task/mode/{values_prefer_nulls.json → upsert_never_skip_prefer_nulls_double_single_line_text_values.json} +0 -0
  122. /data/src/test/resources/org/embulk/output/kintone/task/mode/{upsert_update_prefer_nulls_records.jsonl → upsert_never_skip_prefer_nulls_update_records.jsonl} +0 -0
  123. /data/src/test/resources/org/embulk/output/kintone/task/mode/{upsert_update_records.jsonl → upsert_never_skip_update_records.jsonl} +0 -0
  124. /data/src/test/resources/org/embulk/output/kintone/task/reduce/{insert_add_ignore_nulls_records.jsonl → insert_ignore_nulls_add_records.jsonl} +0 -0
  125. /data/src/test/resources/org/embulk/output/kintone/task/reduce/{insert_add_prefer_nulls_records.jsonl → insert_prefer_nulls_add_records.jsonl} +0 -0
  126. /data/src/test/resources/org/embulk/output/kintone/task/reduce/{update_update_ignore_nulls_records.jsonl → update_ignore_nulls_update_records.jsonl} +0 -0
  127. /data/src/test/resources/org/embulk/output/kintone/task/reduce/{update_update_prefer_nulls_records.jsonl → update_prefer_nulls_update_records.jsonl} +0 -0
  128. /data/src/test/resources/org/embulk/output/kintone/task/reduce/{upsert_add_records.jsonl → upsert_never_skip_add_records.jsonl} +0 -0
  129. /data/src/test/resources/org/embulk/output/kintone/task/reduce/{values.json → upsert_never_skip_double_single_line_text_values.json} +0 -0
  130. /data/src/test/resources/org/embulk/output/kintone/task/reduce/{upsert_add_ignore_nulls_records.jsonl → upsert_never_skip_ignore_nulls_add_records.jsonl} +0 -0
  131. /data/src/test/resources/org/embulk/output/kintone/task/reduce/{values_ignore_nulls.json → upsert_never_skip_ignore_nulls_double_single_line_text_values.json} +0 -0
  132. /data/src/test/resources/org/embulk/output/kintone/task/reduce/{upsert_update_ignore_nulls_records.jsonl → upsert_never_skip_ignore_nulls_update_records.jsonl} +0 -0
  133. /data/src/test/resources/org/embulk/output/kintone/task/reduce/{upsert_add_prefer_nulls_records.jsonl → upsert_never_skip_prefer_nulls_add_records.jsonl} +0 -0
  134. /data/src/test/resources/org/embulk/output/kintone/task/reduce/{values_prefer_nulls.json → upsert_never_skip_prefer_nulls_double_single_line_text_values.json} +0 -0
  135. /data/src/test/resources/org/embulk/output/kintone/task/reduce/{upsert_update_prefer_nulls_records.jsonl → upsert_never_skip_prefer_nulls_update_records.jsonl} +0 -0
  136. /data/src/test/resources/org/embulk/output/kintone/task/reduce/{upsert_update_records.jsonl → upsert_never_skip_update_records.jsonl} +0 -0
  137. /data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/{insert_add_ignore_nulls_records.jsonl → insert_ignore_nulls_add_records.jsonl} +0 -0
  138. /data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/{insert_add_prefer_nulls_records.jsonl → insert_prefer_nulls_add_records.jsonl} +0 -0
  139. /data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/{update_update_ignore_nulls_records.jsonl → update_ignore_nulls_update_records.jsonl} +0 -0
  140. /data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/{update_update_prefer_nulls_records.jsonl → update_prefer_nulls_update_records.jsonl} +0 -0
  141. /data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/{values.json → upsert_never_skip_double_single_line_text_values.json} +0 -0
  142. /data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/{upsert_add_ignore_nulls_records.jsonl → upsert_never_skip_ignore_nulls_add_records.jsonl} +0 -0
  143. /data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/{values_ignore_nulls.json → upsert_never_skip_ignore_nulls_double_single_line_text_values.json} +0 -0
  144. /data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/{upsert_update_ignore_nulls_records.jsonl → upsert_never_skip_ignore_nulls_update_records.jsonl} +0 -0
  145. /data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/{upsert_add_prefer_nulls_records.jsonl → upsert_never_skip_prefer_nulls_add_records.jsonl} +0 -0
  146. /data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/{values_prefer_nulls.json → upsert_never_skip_prefer_nulls_double_single_line_text_values.json} +0 -0
  147. /data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/{upsert_update_prefer_nulls_records.jsonl → upsert_never_skip_prefer_nulls_update_records.jsonl} +0 -0
  148. /data/src/test/resources/org/embulk/output/kintone/task/reduce_subtable/{upsert_update_records.jsonl → upsert_never_skip_update_records.jsonl} +0 -0
@@ -5,15 +5,11 @@ import static org.embulk.spi.util.RetryExecutor.retryExecutor;
5
5
  import com.fasterxml.jackson.databind.JsonNode;
6
6
  import com.fasterxml.jackson.databind.ObjectMapper;
7
7
  import com.google.common.collect.Maps;
8
- import com.kintone.client.KintoneClient;
9
- import com.kintone.client.KintoneClientBuilder;
10
8
  import com.kintone.client.api.record.GetRecordsByCursorResponseBody;
11
9
  import com.kintone.client.exception.KintoneApiRuntimeException;
12
- import com.kintone.client.model.app.field.FieldProperty;
13
10
  import com.kintone.client.model.record.FieldType;
14
11
  import com.kintone.client.model.record.Record;
15
12
  import com.kintone.client.model.record.RecordForUpdate;
16
- import com.kintone.client.model.record.UpdateKey;
17
13
  import java.io.IOException;
18
14
  import java.lang.invoke.MethodHandles;
19
15
  import java.math.BigDecimal;
@@ -28,8 +24,11 @@ import java.util.function.Function;
28
24
  import java.util.function.Supplier;
29
25
  import java.util.stream.Collectors;
30
26
  import org.apache.commons.lang3.tuple.Pair;
31
- import org.embulk.config.ConfigException;
32
27
  import org.embulk.config.TaskReport;
28
+ import org.embulk.output.kintone.record.Id;
29
+ import org.embulk.output.kintone.record.IdOrUpdateKey;
30
+ import org.embulk.output.kintone.record.Skip;
31
+ import org.embulk.output.kintone.util.Lazy;
33
32
  import org.embulk.spi.Exec;
34
33
  import org.embulk.spi.Page;
35
34
  import org.embulk.spi.PageReader;
@@ -53,30 +52,17 @@ public class KintonePageOutput implements TransactionalPageOutput {
53
52
  private final Map<String, Pair<FieldType, FieldType>> wrongTypeFields = new TreeMap<>();
54
53
  private final PluginTask task;
55
54
  private final PageReader reader;
56
- private KintoneClient client;
57
- private Map<String, FieldProperty> formFields;
55
+ private final Lazy<KintoneClient> client;
58
56
 
59
57
  public KintonePageOutput(PluginTask task, Schema schema) {
60
58
  this.task = task;
61
59
  reader = new PageReader(schema);
60
+ client = KintoneClient.lazy(() -> task, schema);
62
61
  }
63
62
 
64
63
  @Override
65
64
  public void add(Page page) {
66
- KintoneMode mode = KintoneMode.getKintoneModeByValue(task.getMode());
67
- switch (mode) {
68
- case INSERT:
69
- insertPage(page);
70
- break;
71
- case UPDATE:
72
- updatePage(page);
73
- break;
74
- case UPSERT:
75
- upsertPage(page);
76
- break;
77
- default:
78
- throw new UnsupportedOperationException(String.format("Unknown mode '%s'", task.getMode()));
79
- }
65
+ KintoneMode.of(task).add(page, task.getSkipIfNonExistingIdOrUpdateKey(), this);
80
66
  }
81
67
 
82
68
  @Override
@@ -86,14 +72,7 @@ public class KintonePageOutput implements TransactionalPageOutput {
86
72
 
87
73
  @Override
88
74
  public void close() {
89
- if (client == null) {
90
- return; // Not connected
91
- }
92
- try {
93
- client.close();
94
- } catch (Exception e) {
95
- throw new RuntimeException("kintone throw exception", e);
96
- }
75
+ client.get().close();
97
76
  }
98
77
 
99
78
  @Override
@@ -112,38 +91,15 @@ public class KintonePageOutput implements TransactionalPageOutput {
112
91
  return Exec.newTaskReport();
113
92
  }
114
93
 
115
- private void connectIfNeeded() {
116
- if (client != null) {
117
- return; // Already connected
118
- }
119
- KintoneClientBuilder builder = KintoneClientBuilder.create("https://" + task.getDomain());
120
- if (task.getGuestSpaceId().isPresent()) {
121
- builder.setGuestSpaceId(task.getGuestSpaceId().get());
122
- }
123
- if (task.getBasicAuthUsername().isPresent() && task.getBasicAuthPassword().isPresent()) {
124
- builder.withBasicAuth(task.getBasicAuthUsername().get(), task.getBasicAuthPassword().get());
125
- }
126
- if (task.getUsername().isPresent() && task.getPassword().isPresent()) {
127
- builder.authByPassword(task.getUsername().get(), task.getPassword().get());
128
- } else if (task.getToken().isPresent()) {
129
- builder.authByApiToken(task.getToken().get());
130
- } else {
131
- throw new ConfigException("Username and password or token must be configured.");
132
- }
133
- client = builder.build();
134
- formFields = client.app().getFormFields(task.getAppId());
135
- }
136
-
137
94
  private void insert(List<Record> records) {
138
- executeWithRetry(() -> client.record().addRecords(task.getAppId(), records));
95
+ executeWithRetry(() -> client.get().record().addRecords(task.getAppId(), records));
139
96
  }
140
97
 
141
98
  private void update(List<RecordForUpdate> records) {
142
- executeWithRetry(() -> client.record().updateRecords(task.getAppId(), records));
99
+ executeWithRetry(() -> client.get().record().updateRecords(task.getAppId(), records));
143
100
  }
144
101
 
145
102
  private <T> T executeWithRetry(Supplier<T> operation) {
146
- connectIfNeeded();
147
103
  KintoneRetryOption retryOption = task.getRetryOptions();
148
104
  try {
149
105
  return retryExecutor()
@@ -153,7 +109,7 @@ public class KintonePageOutput implements TransactionalPageOutput {
153
109
  .runInterruptible(
154
110
  new Retryable<T>() {
155
111
  @Override
156
- public T call() throws Exception {
112
+ public T call() {
157
113
  return operation.get();
158
114
  }
159
115
 
@@ -175,8 +131,7 @@ public class KintonePageOutput implements TransactionalPageOutput {
175
131
 
176
132
  @Override
177
133
  public void onRetry(
178
- Exception exception, int retryCount, int retryLimit, int retryWait)
179
- throws RetryGiveupException {
134
+ Exception exception, int retryCount, int retryLimit, int retryWait) {
180
135
  String message =
181
136
  String.format(
182
137
  "Retrying %d/%d after %d seconds. Message: %s",
@@ -189,15 +144,14 @@ public class KintonePageOutput implements TransactionalPageOutput {
189
144
  }
190
145
 
191
146
  @Override
192
- public void onGiveup(Exception firstException, Exception lastException)
193
- throws RetryGiveupException {}
147
+ public void onGiveup(Exception firstException, Exception lastException) {}
194
148
  });
195
149
  } catch (RetryGiveupException | InterruptedException e) {
196
150
  throw new RuntimeException("kintone throw exception", e);
197
151
  }
198
152
  }
199
153
 
200
- private void insertPage(Page page) {
154
+ public void insertPage(Page page) {
201
155
  List<Record> records = new ArrayList<>();
202
156
  reader.setPage(page);
203
157
  KintoneColumnVisitor visitor =
@@ -224,7 +178,8 @@ public class KintonePageOutput implements TransactionalPageOutput {
224
178
  }
225
179
  }
226
180
 
227
- private void updatePage(Page page) {
181
+ public void updatePage(Page page) {
182
+ Skip skip = task.getSkipIfNonExistingIdOrUpdateKey();
228
183
  List<RecordForUpdate> records = new ArrayList<>();
229
184
  reader.setPage(page);
230
185
  KintoneColumnVisitor visitor =
@@ -235,20 +190,21 @@ public class KintonePageOutput implements TransactionalPageOutput {
235
190
  task.getPreferNulls(),
236
191
  task.getIgnoreNulls(),
237
192
  task.getReduceKeyName().orElse(null),
238
- task.getUpdateKeyName()
239
- .orElseThrow(() -> new RuntimeException("unreachable"))); // Already validated
193
+ task.getUpdateKeyName().orElse(Id.FIELD));
240
194
  while (reader.nextRecord()) {
241
195
  Record record = new Record();
242
- UpdateKey updateKey = new UpdateKey();
196
+ IdOrUpdateKey idOrUpdateKey = new IdOrUpdateKey();
243
197
  visitor.setRecord(record);
244
- visitor.setUpdateKey(updateKey);
198
+ visitor.setIdOrUpdateKey(idOrUpdateKey);
245
199
  reader.getSchema().visitColumns(visitor);
246
200
  putWrongTypeFields(record);
247
- if (updateKey.getValue() == null || updateKey.getValue().toString().isEmpty()) {
248
- LOGGER.warn("Record skipped because no update key value was specified");
201
+ if (skip == Skip.NEVER && !idOrUpdateKey.isPresent()) {
202
+ throw new RuntimeException("No id or update key value was specified");
203
+ } else if (!idOrUpdateKey.isPresent()) {
204
+ LOGGER.warn("Record skipped because no id or update key value was specified");
249
205
  continue;
250
206
  }
251
- records.add(new RecordForUpdate(updateKey, record.removeField(updateKey.getField())));
207
+ records.add(idOrUpdateKey.forUpdate(record));
252
208
  if (records.size() == task.getChunkSize()) {
253
209
  update(records);
254
210
  records.clear();
@@ -259,9 +215,9 @@ public class KintonePageOutput implements TransactionalPageOutput {
259
215
  }
260
216
  }
261
217
 
262
- private void upsertPage(Page page) {
218
+ public void upsertPage(Page page) {
263
219
  List<Record> records = new ArrayList<>();
264
- List<UpdateKey> updateKeys = new ArrayList<>();
220
+ List<IdOrUpdateKey> idOrUpdateKeys = new ArrayList<>();
265
221
  reader.setPage(page);
266
222
  KintoneColumnVisitor visitor =
267
223
  new KintoneColumnVisitor(
@@ -271,40 +227,72 @@ public class KintonePageOutput implements TransactionalPageOutput {
271
227
  task.getPreferNulls(),
272
228
  task.getIgnoreNulls(),
273
229
  task.getReduceKeyName().orElse(null),
274
- task.getUpdateKeyName()
275
- .orElseThrow(() -> new RuntimeException("unreachable"))); // Already validated
230
+ task.getUpdateKeyName().orElse(Id.FIELD));
276
231
  while (reader.nextRecord()) {
277
232
  Record record = new Record();
278
- UpdateKey updateKey = new UpdateKey();
233
+ IdOrUpdateKey idOrUpdateKey = new IdOrUpdateKey();
279
234
  visitor.setRecord(record);
280
- visitor.setUpdateKey(updateKey);
235
+ visitor.setIdOrUpdateKey(idOrUpdateKey);
281
236
  reader.getSchema().visitColumns(visitor);
282
237
  putWrongTypeFields(record);
283
238
  records.add(record);
284
- updateKeys.add(updateKey);
239
+ idOrUpdateKeys.add(idOrUpdateKey);
285
240
  if (records.size() == UPSERT_BATCH_SIZE) {
286
- upsert(records, updateKeys);
241
+ upsert(records, idOrUpdateKeys);
287
242
  records.clear();
288
- updateKeys.clear();
243
+ idOrUpdateKeys.clear();
289
244
  }
290
245
  }
291
246
  if (!records.isEmpty()) {
292
- upsert(records, updateKeys);
247
+ upsert(records, idOrUpdateKeys);
293
248
  }
294
249
  }
295
250
 
296
- private void upsert(List<Record> records, List<UpdateKey> updateKeys) {
297
- if (records.size() != updateKeys.size()) {
298
- throw new RuntimeException("records.size() != updateKeys.size()");
251
+ private void upsert(List<Record> records, List<IdOrUpdateKey> idOrUpdateKeys) {
252
+ if (records.size() != idOrUpdateKeys.size()) {
253
+ throw new RuntimeException("records.size() != idOrUpdateKeys.size()");
299
254
  }
300
- List<String> existingValues = executeWithRetry(() -> getExistingValuesByUpdateKey(updateKeys));
255
+ Skip skip = task.getSkipIfNonExistingIdOrUpdateKey();
256
+ String columnName = task.getUpdateKeyName().orElse(Id.FIELD);
257
+ boolean isId = columnName.equals(Id.FIELD);
258
+ List<String> existingValues =
259
+ executeWithRetry(() -> getExistingValuesByIdOrUpdateKey(idOrUpdateKeys, columnName));
301
260
  List<Record> insertRecords = new ArrayList<>();
302
261
  List<RecordForUpdate> updateRecords = new ArrayList<>();
303
262
  for (int i = 0; i < records.size(); i++) {
263
+ RecordForUpdate recordForUpdate = null;
304
264
  Record record = records.get(i);
305
- UpdateKey updateKey = updateKeys.get(i);
306
- if (existsRecord(existingValues, updateKey)) {
307
- updateRecords.add(new RecordForUpdate(updateKey, record.removeField(updateKey.getField())));
265
+ IdOrUpdateKey idOrUpdateKey = idOrUpdateKeys.get(i);
266
+ if (existsRecord(existingValues, idOrUpdateKey)) {
267
+ recordForUpdate = idOrUpdateKey.forUpdate(record);
268
+ } else if (skip == Skip.ALWAYS && idOrUpdateKey.isPresent()) {
269
+ LOGGER.warn(
270
+ "Record skipped because non existing id or update key '"
271
+ + idOrUpdateKey.getValue()
272
+ + "' was specified");
273
+ continue;
274
+ } else if (skip == Skip.ALWAYS && !idOrUpdateKey.isPresent()) {
275
+ LOGGER.warn("Record skipped because no id or update key value was specified");
276
+ continue;
277
+ } else if (skip == Skip.AUTO && idOrUpdateKey.isIdPresent()) {
278
+ LOGGER.warn(
279
+ "Record skipped because non existing id '"
280
+ + idOrUpdateKey.getValue()
281
+ + "' was specified");
282
+ continue;
283
+ } else if (skip == Skip.AUTO && !isId && !idOrUpdateKey.isUpdateKeyPresent()) {
284
+ LOGGER.warn("Record skipped because no update key value was specified");
285
+ continue;
286
+ } else if (idOrUpdateKey.isIdPresent()) {
287
+ LOGGER.warn(
288
+ "Record inserted though non existing id '"
289
+ + idOrUpdateKey.getValue()
290
+ + "' was specified");
291
+ } else if (!isId && !idOrUpdateKey.isUpdateKeyPresent()) {
292
+ LOGGER.warn("Record inserted though no update key value was specified");
293
+ }
294
+ if (recordForUpdate != null) {
295
+ updateRecords.add(recordForUpdate);
308
296
  } else {
309
297
  insertRecords.add(record);
310
298
  }
@@ -324,51 +312,53 @@ public class KintonePageOutput implements TransactionalPageOutput {
324
312
  }
325
313
  }
326
314
 
327
- private List<String> getExistingValuesByUpdateKey(List<UpdateKey> updateKeys) {
328
- String fieldCode =
329
- updateKeys.stream()
330
- .map(UpdateKey::getField)
331
- .filter(Objects::nonNull)
332
- .findFirst()
333
- .orElse(null);
334
- if (fieldCode == null) {
335
- return Collections.emptyList();
336
- }
337
- Function<Record, String> fieldValueAsString;
338
- FieldType fieldType = getFieldType(fieldCode);
339
- if (fieldType == FieldType.SINGLE_LINE_TEXT) {
340
- fieldValueAsString = record -> record.getSingleLineTextFieldValue(fieldCode);
341
- } else if (fieldType == FieldType.NUMBER) {
342
- fieldValueAsString = record -> toString(record.getNumberFieldValue(fieldCode));
343
- } else {
344
- throw new ConfigException("The update_key must be 'SINGLE_LINE_TEXT' or 'NUMBER'.");
345
- }
315
+ private List<String> getExistingValuesByIdOrUpdateKey(
316
+ List<IdOrUpdateKey> idOrUpdateKeys, String columnName) {
346
317
  List<String> queryValues =
347
- updateKeys.stream()
348
- .filter(k -> k.getValue() != null && !k.getValue().toString().isEmpty())
318
+ idOrUpdateKeys.stream()
319
+ .filter(IdOrUpdateKey::isPresent)
349
320
  .map(k -> "\"" + k.getValue() + "\"")
350
321
  .collect(Collectors.toList());
351
322
  if (queryValues.isEmpty()) {
352
323
  return Collections.emptyList();
353
324
  }
325
+ return columnName.equals(Id.FIELD)
326
+ ? getExistingValuesById(queryValues)
327
+ : getExistingValuesByUpdateKey(columnName, queryValues);
328
+ }
329
+
330
+ private List<String> getExistingValuesById(List<String> queryValues) {
331
+ return getExistingValues(Id.FIELD, Record::getId, queryValues);
332
+ }
333
+
334
+ private List<String> getExistingValuesByUpdateKey(String columnName, List<String> queryValues) {
335
+ KintoneColumnOption option = task.getColumnOptions().get(columnName);
336
+ String fieldCode = option != null ? option.getFieldCode() : columnName;
337
+ KintoneColumnType type = KintoneColumnType.valueOf(getFieldType(fieldCode).name());
338
+ return getExistingValues(fieldCode, record -> type.getValue(record, fieldCode), queryValues);
339
+ }
340
+
341
+ private List<String> getExistingValues(
342
+ String fieldCode, Function<Record, Object> toValue, List<String> queryValues) {
354
343
  String cursorId =
355
344
  client
345
+ .get()
356
346
  .record()
357
347
  .createCursor(
358
348
  task.getAppId(),
359
349
  Collections.singletonList(fieldCode),
360
350
  fieldCode + " in (" + String.join(",", queryValues) + ")");
361
- List<Record> allRecords = new ArrayList<>();
351
+ List<Record> records = new ArrayList<>();
362
352
  while (true) {
363
- GetRecordsByCursorResponseBody resp = client.record().getRecordsByCursor(cursorId);
364
- List<Record> records = resp.getRecords();
365
- allRecords.addAll(records);
366
- if (!resp.hasNext()) {
353
+ GetRecordsByCursorResponseBody cursor = client.get().record().getRecordsByCursor(cursorId);
354
+ records.addAll(cursor.getRecords());
355
+ if (!cursor.hasNext()) {
367
356
  break;
368
357
  }
369
358
  }
370
- return allRecords.stream()
371
- .map(fieldValueAsString)
359
+ return records.stream()
360
+ .map(toValue)
361
+ .map(KintonePageOutput::toString)
372
362
  .filter(Objects::nonNull)
373
363
  .collect(Collectors.toList());
374
364
  }
@@ -384,13 +374,11 @@ public class KintonePageOutput implements TransactionalPageOutput {
384
374
  }
385
375
 
386
376
  private FieldType getFieldType(String fieldCode) {
387
- connectIfNeeded();
388
- FieldProperty field = formFields.get(fieldCode);
389
- return field == null ? null : field.getType();
377
+ return client.get().getFieldType(fieldCode);
390
378
  }
391
379
 
392
- private static boolean existsRecord(List<String> existingValues, UpdateKey updateKey) {
393
- String value = toString(updateKey.getValue());
380
+ private static boolean existsRecord(List<String> existingValues, IdOrUpdateKey idOrUpdateKey) {
381
+ String value = toString(idOrUpdateKey.getValue());
394
382
  return value != null && existingValues.stream().anyMatch(v -> v.equals(value));
395
383
  }
396
384
 
@@ -7,6 +7,7 @@ import java.util.Set;
7
7
  import org.embulk.config.Config;
8
8
  import org.embulk.config.ConfigDefault;
9
9
  import org.embulk.config.Task;
10
+ import org.embulk.output.kintone.record.Skip;
10
11
  import org.embulk.spi.Column;
11
12
 
12
13
  public interface PluginTask extends Task {
@@ -52,6 +53,10 @@ public interface PluginTask extends Task {
52
53
  @ConfigDefault("\"false\"")
53
54
  boolean getIgnoreNulls();
54
55
 
56
+ @Config("skip_if_non_existing_id_or_update_key")
57
+ @ConfigDefault("\"auto\"")
58
+ Skip getSkipIfNonExistingIdOrUpdateKey();
59
+
55
60
  @Config("mode")
56
61
  @ConfigDefault("\"insert\"")
57
62
  String getMode();
@@ -0,0 +1,18 @@
1
+ package org.embulk.output.kintone.record;
2
+
3
+ public class Id {
4
+ public static final String FIELD = "$id";
5
+ private Long value;
6
+
7
+ public void setValue(Long value) {
8
+ this.value = value;
9
+ }
10
+
11
+ public Long getValue() {
12
+ return value;
13
+ }
14
+
15
+ public boolean isPresent() {
16
+ return value != null;
17
+ }
18
+ }
@@ -0,0 +1,45 @@
1
+ package org.embulk.output.kintone.record;
2
+
3
+ import com.kintone.client.model.record.Record;
4
+ import com.kintone.client.model.record.RecordForUpdate;
5
+ import com.kintone.client.model.record.UpdateKey;
6
+
7
+ public class IdOrUpdateKey {
8
+ private final Id id = new Id();
9
+ private final UpdateKey updateKey = new UpdateKey();
10
+
11
+ public Id getId() {
12
+ return id;
13
+ }
14
+
15
+ public UpdateKey getUpdateKey() {
16
+ return updateKey;
17
+ }
18
+
19
+ public String getField() {
20
+ return isIdPresent() ? Id.FIELD : updateKey.getField();
21
+ }
22
+
23
+ public Object getValue() {
24
+ return isIdPresent() ? id.getValue() : updateKey.getValue();
25
+ }
26
+
27
+ public RecordForUpdate forUpdate(Record record) {
28
+ return isIdPresent()
29
+ ? new RecordForUpdate(id.getValue(), record)
30
+ : new RecordForUpdate(updateKey, record.removeField(updateKey.getField()));
31
+ }
32
+
33
+ public boolean isPresent() {
34
+ return isIdPresent() || isUpdateKeyPresent();
35
+ }
36
+
37
+ public boolean isIdPresent() {
38
+ return id.isPresent();
39
+ }
40
+
41
+ public boolean isUpdateKeyPresent() {
42
+ Object value = updateKey.getValue();
43
+ return value != null && !value.toString().isEmpty();
44
+ }
45
+ }
@@ -0,0 +1,14 @@
1
+ package org.embulk.output.kintone.record;
2
+
3
+ import com.fasterxml.jackson.annotation.JsonCreator;
4
+
5
+ public enum Skip {
6
+ AUTO,
7
+ NEVER,
8
+ ALWAYS;
9
+
10
+ @JsonCreator
11
+ public static Skip of(String name) {
12
+ return valueOf(name.toUpperCase());
13
+ }
14
+ }
@@ -0,0 +1,20 @@
1
+ package org.embulk.output.kintone.util;
2
+
3
+ public abstract class Lazy<T extends AutoCloseable> implements AutoCloseable {
4
+ private T value;
5
+
6
+ public T get() {
7
+ if (value == null) {
8
+ value = initialValue();
9
+ }
10
+ return value;
11
+ }
12
+
13
+ public void close() throws Exception {
14
+ if (value != null) {
15
+ value.close();
16
+ }
17
+ }
18
+
19
+ protected abstract T initialValue();
20
+ }
@@ -0,0 +1,139 @@
1
+ package org.embulk.output.kintone;
2
+
3
+ import static org.hamcrest.MatcherAssert.assertThat;
4
+ import static org.hamcrest.Matchers.is;
5
+ import static org.junit.Assert.assertThrows;
6
+
7
+ import java.util.Collections;
8
+ import org.embulk.config.ConfigException;
9
+ import org.embulk.config.ConfigSource;
10
+ import org.embulk.output.kintone.util.Lazy;
11
+ import org.embulk.spi.Schema;
12
+ import org.embulk.spi.type.Type;
13
+ import org.embulk.spi.type.Types;
14
+ import org.junit.Before;
15
+ import org.junit.Test;
16
+
17
+ public class KintoneClientTest extends TestKintoneOutputPlugin {
18
+ private ConfigSource config;
19
+
20
+ @Before
21
+ public void before() {
22
+ config = loadConfigYaml("client/config.yml");
23
+ }
24
+
25
+ @Test
26
+ public void testInsert() {
27
+ merge(config("mode: insert"));
28
+ merge(config("update_key: null"));
29
+ runWithMockClient(Lazy::get);
30
+ merge(config("update_key: long_number"));
31
+ assertConfigException("When mode is insert, require no update_key.");
32
+ merge(config("update_key: string_single_line_text"));
33
+ assertConfigException("When mode is insert, require no update_key.");
34
+ merge(config("update_key: $id"));
35
+ assertConfigException("When mode is insert, require no update_key.", id(Types.LONG));
36
+ merge(config("update_key: null"));
37
+ runWithMockClient(Lazy::get, id(Types.STRING));
38
+ }
39
+
40
+ @Test
41
+ public void testUpdate() {
42
+ merge(config("mode: update"));
43
+ merge(config("update_key: null"));
44
+ assertConfigException("When mode is update, require update_key or id column.");
45
+ merge(config("update_key: non_existing_column"));
46
+ assertConfigException("The column 'non_existing_column' for update does not exist.");
47
+ merge(config("update_key: non_existing_field"));
48
+ assertConfigException("The field 'non_existing_field' for update does not exist.");
49
+ merge(config("update_key: invalid_type_field_multi_line_text"));
50
+ assertConfigException("The update_key must be 'SINGLE_LINE_TEXT' or 'NUMBER'.");
51
+ merge(config("update_key: long_number"));
52
+ runWithMockClient(Lazy::get);
53
+ merge(config("update_key: string_single_line_text"));
54
+ runWithMockClient(Lazy::get);
55
+ merge(config("update_key: $id"));
56
+ runWithMockClient(Lazy::get, id(Types.LONG));
57
+ merge(config("update_key: null"));
58
+ assertConfigException("The id column must be 'long'.", id(Types.STRING));
59
+ }
60
+
61
+ @Test
62
+ public void testUpsert() {
63
+ merge(config("mode: upsert"));
64
+ merge(config("update_key: null"));
65
+ assertConfigException("When mode is upsert, require update_key or id column.");
66
+ merge(config("update_key: non_existing_column"));
67
+ assertConfigException("The column 'non_existing_column' for update does not exist.");
68
+ merge(config("update_key: non_existing_field"));
69
+ assertConfigException("The field 'non_existing_field' for update does not exist.");
70
+ merge(config("update_key: invalid_type_field_multi_line_text"));
71
+ assertConfigException("The update_key must be 'SINGLE_LINE_TEXT' or 'NUMBER'.");
72
+ merge(config("update_key: long_number"));
73
+ runWithMockClient(Lazy::get);
74
+ merge(config("update_key: string_single_line_text"));
75
+ runWithMockClient(Lazy::get);
76
+ merge(config("update_key: $id"));
77
+ runWithMockClient(Lazy::get, id(Types.LONG));
78
+ merge(config("update_key: null"));
79
+ assertConfigException("The id column must be 'long'.", id(Types.STRING));
80
+ }
81
+
82
+ private void assertConfigException(String message) {
83
+ assertConfigException(message, builder());
84
+ }
85
+
86
+ private void assertConfigException(String message, Schema.Builder builder) {
87
+ runWithMockClient(
88
+ client ->
89
+ assertThat(assertThrows(ConfigException.class, client::get).getMessage(), is(message)),
90
+ builder);
91
+ }
92
+
93
+ private void runWithMockClient(Consumer<Lazy<KintoneClient>> consumer) {
94
+ runWithMockClient(consumer, builder());
95
+ }
96
+
97
+ private void runWithMockClient(Consumer<Lazy<KintoneClient>> consumer, Schema.Builder builder) {
98
+ MockClient mockClient =
99
+ new MockClient(
100
+ config.get(String.class, "domain"),
101
+ Collections.emptyList(),
102
+ Collections.emptyList(),
103
+ "");
104
+ try (Lazy<KintoneClient> client = KintoneClient.lazy(this::task, schema(builder))) {
105
+ mockClient.run(() -> consumer.accept(client));
106
+ } catch (Exception e) {
107
+ throw new RuntimeException(e);
108
+ }
109
+ }
110
+
111
+ private void merge(ConfigSource config) {
112
+ this.config.merge(config);
113
+ }
114
+
115
+ private PluginTask task() {
116
+ return config.loadConfig(PluginTask.class);
117
+ }
118
+
119
+ private static Schema schema(Schema.Builder builder) {
120
+ return builder
121
+ .add("non_existing_field", Types.LONG)
122
+ .add("invalid_type_field_multi_line_text", Types.STRING)
123
+ .add("long_number", Types.LONG)
124
+ .add("string_single_line_text", Types.STRING)
125
+ .build();
126
+ }
127
+
128
+ private static Schema.Builder id(Type type) {
129
+ return builder().add("$id", type);
130
+ }
131
+
132
+ private static Schema.Builder builder() {
133
+ return Schema.builder();
134
+ }
135
+
136
+ private interface Consumer<T> {
137
+ void accept(T t) throws Exception;
138
+ }
139
+ }