embulk-input-td 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +6 -0
- data/CHANGELOG.md +5 -0
- data/build.gradle +34 -10
- data/config/checkstyle/README.md +6 -0
- data/config/checkstyle/checkstyle-suppressions.xml +8 -0
- data/config/checkstyle/checkstyle.xml +195 -104
- data/config/checkstyle/google_checks.xml +218 -0
- data/embulk-input-td.gemspec +1 -1
- data/src/main/java/org/embulk/input/td/TdInputPlugin.java +301 -239
- data/src/main/java/org/embulk/input/td/writer/AbstractValueWriter.java +5 -8
- data/src/main/java/org/embulk/input/td/writer/BooleanValueWriter.java +4 -6
- data/src/main/java/org/embulk/input/td/writer/DoubleValueWriter.java +4 -6
- data/src/main/java/org/embulk/input/td/writer/JsonValueWriter.java +4 -6
- data/src/main/java/org/embulk/input/td/writer/LongValueWriter.java +4 -6
- data/src/main/java/org/embulk/input/td/writer/StringValueWriter.java +4 -6
- data/src/main/java/org/embulk/input/td/writer/ValueWriter.java +2 -2
- data/src/test/java/org/embulk/input/td/TestTdInputPlugin.java +1 -2
- metadata +7 -4
- data/config/checkstyle/default.xml +0 -108
data/embulk-input-td.gemspec
CHANGED
@@ -1,11 +1,5 @@
|
|
1
1
|
package org.embulk.input.td;
|
2
2
|
|
3
|
-
import java.io.IOException;
|
4
|
-
import java.io.InputStream;
|
5
|
-
import java.util.List;
|
6
|
-
import java.util.Properties;
|
7
|
-
import java.util.zip.GZIPInputStream;
|
8
|
-
|
9
3
|
import com.fasterxml.jackson.databind.JsonNode;
|
10
4
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
11
5
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
@@ -19,17 +13,25 @@ import com.treasuredata.client.TDClientBuilder;
|
|
19
13
|
import com.treasuredata.client.model.TDJob;
|
20
14
|
import com.treasuredata.client.model.TDJobRequest;
|
21
15
|
import com.treasuredata.client.model.TDJobSummary;
|
22
|
-
import
|
23
|
-
import
|
24
|
-
import
|
16
|
+
import com.treasuredata.client.model.TDResultFormat;
|
17
|
+
import java.io.IOException;
|
18
|
+
import java.io.InputStream;
|
19
|
+
import java.util.List;
|
20
|
+
import java.util.Locale;
|
21
|
+
import java.util.Properties;
|
22
|
+
import java.util.zip.GZIPInputStream;
|
25
23
|
import org.embulk.config.Config;
|
26
24
|
import org.embulk.config.ConfigDefault;
|
27
25
|
import org.embulk.config.ConfigDiff;
|
26
|
+
import org.embulk.config.ConfigException;
|
27
|
+
import org.embulk.config.ConfigInject;
|
28
28
|
import org.embulk.config.ConfigSource;
|
29
29
|
import org.embulk.config.Task;
|
30
|
+
import org.embulk.config.TaskReport;
|
30
31
|
import org.embulk.config.TaskSource;
|
31
32
|
import org.embulk.input.td.writer.BooleanValueWriter;
|
32
33
|
import org.embulk.input.td.writer.DoubleValueWriter;
|
34
|
+
import org.embulk.input.td.writer.JsonValueWriter;
|
33
35
|
import org.embulk.input.td.writer.LongValueWriter;
|
34
36
|
import org.embulk.input.td.writer.StringValueWriter;
|
35
37
|
import org.embulk.input.td.writer.ValueWriter;
|
@@ -42,130 +44,62 @@ import org.embulk.spi.PageBuilder;
|
|
42
44
|
import org.embulk.spi.PageOutput;
|
43
45
|
import org.embulk.spi.Schema;
|
44
46
|
import org.embulk.spi.type.Type;
|
47
|
+
import org.embulk.spi.type.Types;
|
45
48
|
import org.msgpack.core.MessagePack;
|
46
49
|
import org.msgpack.core.MessageUnpacker;
|
47
50
|
import org.msgpack.value.ArrayValue;
|
48
51
|
import org.msgpack.value.Value;
|
49
52
|
import org.slf4j.Logger;
|
50
53
|
|
51
|
-
import static com.google.common.base.Optional.fromNullable;
|
52
|
-
import static com.treasuredata.client.model.TDResultFormat.MESSAGE_PACK_GZ;
|
53
|
-
import static java.lang.Integer.parseInt;
|
54
|
-
import static java.util.Locale.ENGLISH;
|
55
|
-
import static org.embulk.spi.Exec.getLogger;
|
56
|
-
import static org.embulk.spi.Exec.newConfigDiff;
|
57
|
-
import static org.embulk.spi.Exec.newTaskReport;
|
58
|
-
import static org.embulk.spi.type.Types.BOOLEAN;
|
59
|
-
import static org.embulk.spi.type.Types.DOUBLE;
|
60
|
-
import static org.embulk.spi.type.Types.JSON;
|
61
|
-
import static org.embulk.spi.type.Types.LONG;
|
62
|
-
import static org.embulk.spi.type.Types.STRING;
|
63
|
-
import static org.embulk.spi.type.Types.TIMESTAMP;
|
64
|
-
|
65
54
|
public class TdInputPlugin
|
66
|
-
implements InputPlugin
|
67
|
-
{
|
68
|
-
public interface PluginTask
|
69
|
-
extends Task
|
70
|
-
{
|
71
|
-
@Config("apikey")
|
72
|
-
public String getApiKey();
|
73
|
-
|
74
|
-
@Config("endpoint")
|
75
|
-
@ConfigDefault("\"api.treasuredata.com\"")
|
76
|
-
public String getEndpoint();
|
77
|
-
|
78
|
-
@Config("use_ssl")
|
79
|
-
@ConfigDefault("true")
|
80
|
-
public boolean getUseSsl();
|
81
|
-
|
82
|
-
@Config("http_proxy")
|
83
|
-
@ConfigDefault("null")
|
84
|
-
public Optional<HttpProxyTask> getHttpProxy();
|
85
|
-
|
86
|
-
// TODO timeout
|
87
|
-
// TODO query, database
|
88
|
-
|
89
|
-
@Config("query")
|
90
|
-
@ConfigDefault("null")
|
91
|
-
public Optional<String> getQuery();
|
92
|
-
|
93
|
-
@Config("database")
|
94
|
-
@ConfigDefault("null")
|
95
|
-
public Optional<String> getDatabase();
|
96
|
-
|
97
|
-
@Config("job_id")
|
98
|
-
@ConfigDefault("null")
|
99
|
-
public Optional<String> getJobId();
|
100
|
-
|
101
|
-
@Config("stop_on_invalid_record")
|
102
|
-
@ConfigDefault("false")
|
103
|
-
public boolean getStopOnInvalidRecord();
|
104
|
-
|
105
|
-
// TODO column_options
|
106
|
-
|
107
|
-
@ConfigInject
|
108
|
-
BufferAllocator getBufferAllocator();
|
109
|
-
}
|
110
|
-
|
111
|
-
public interface HttpProxyTask
|
112
|
-
extends Task
|
113
|
-
{
|
114
|
-
@Config("host")
|
115
|
-
public String getHost();
|
116
|
-
|
117
|
-
@Config("port")
|
118
|
-
public int getPort();
|
119
|
-
|
120
|
-
@Config("use_ssl")
|
121
|
-
@ConfigDefault("false")
|
122
|
-
public boolean getUseSsl();
|
123
|
-
|
124
|
-
@Config("user")
|
125
|
-
@ConfigDefault("null")
|
126
|
-
public Optional<String> getUser();
|
127
|
-
|
128
|
-
@Config("password")
|
129
|
-
@ConfigDefault("null")
|
130
|
-
public Optional<String> getPassword();
|
131
|
-
}
|
55
|
+
implements InputPlugin {
|
132
56
|
|
133
57
|
private final Logger log;
|
134
58
|
|
135
59
|
@Inject
|
136
|
-
public TdInputPlugin()
|
137
|
-
|
138
|
-
|
60
|
+
public TdInputPlugin() {
|
61
|
+
this.log = Exec.getLogger(this.getClass());
|
62
|
+
}
|
63
|
+
|
64
|
+
private static JsonNode toJsonNode(final String schema) {
|
65
|
+
try {
|
66
|
+
return new ObjectMapper().readTree(schema);
|
67
|
+
} catch (IOException e) {
|
68
|
+
throw new ConfigException(String.format(Locale.ENGLISH,
|
69
|
+
"Failed to parse job result schema as JSON: %s", schema));
|
70
|
+
}
|
139
71
|
}
|
140
72
|
|
141
73
|
@Override
|
142
|
-
public ConfigDiff transaction(ConfigSource config, InputPlugin.Control control)
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
TDJob job = getTDJob(task, client);
|
74
|
+
public ConfigDiff transaction(final ConfigSource config, final InputPlugin.Control control) {
|
75
|
+
final PluginTask task = config.loadConfig(PluginTask.class);
|
76
|
+
try (final TDClient client = newTdClient(task)) {
|
77
|
+
final TDJob job = getTdJob(task, client);
|
147
78
|
|
148
|
-
Optional<String> jobResultSchema = job.getResultSchema();
|
79
|
+
final Optional<String> jobResultSchema = job.getResultSchema();
|
149
80
|
if (!jobResultSchema.isPresent()) {
|
150
|
-
throw new ConfigException(String.format(
|
81
|
+
throw new ConfigException(String.format(
|
82
|
+
"Not found result schema of job %s", job.getJobId()));
|
151
83
|
}
|
152
84
|
|
153
|
-
|
154
|
-
|
85
|
+
final JsonNode jobResultSchemaJsonNode = toJsonNode(jobResultSchema.get());
|
86
|
+
final Schema inputSchema = convertSchema(job.getType(), jobResultSchemaJsonNode);
|
87
|
+
// validate if value writers can be created according to the input schema
|
88
|
+
newValueWriters(inputSchema);
|
155
89
|
|
156
|
-
|
90
|
+
// overwrite job_id
|
91
|
+
final TaskSource taskSource = task.dump().set("job_id", job.getJobId());
|
157
92
|
return resume(taskSource, inputSchema, 1, control);
|
158
93
|
}
|
159
94
|
}
|
160
95
|
|
161
|
-
private TDClient
|
162
|
-
|
163
|
-
TDClientBuilder builder = TDClient.newBuilder();
|
96
|
+
private TDClient newTdClient(final PluginTask task) {
|
97
|
+
final TDClientBuilder builder = TDClient.newBuilder();
|
164
98
|
builder.setApiKey(task.getApiKey());
|
165
99
|
builder.setEndpoint(task.getEndpoint());
|
166
100
|
builder.setUseSSL(task.getUseSsl());
|
167
101
|
|
168
|
-
Optional<ProxyConfig>proxyConfig = newProxyConfig(task.getHttpProxy());
|
102
|
+
final Optional<ProxyConfig> proxyConfig = newProxyConfig(task.getHttpProxy());
|
169
103
|
if (proxyConfig.isPresent()) {
|
170
104
|
builder.setProxy(proxyConfig.get());
|
171
105
|
}
|
@@ -173,42 +107,48 @@ public class TdInputPlugin
|
|
173
107
|
return builder.build();
|
174
108
|
}
|
175
109
|
|
176
|
-
private Optional<ProxyConfig> newProxyConfig(Optional<HttpProxyTask> task)
|
177
|
-
|
178
|
-
//
|
110
|
+
private Optional<ProxyConfig> newProxyConfig(final Optional<HttpProxyTask> task) {
|
111
|
+
// This plugin searches http proxy settings and configures them to TDClient.
|
112
|
+
// The order of proxy setting searching is:
|
179
113
|
// 1. System properties
|
180
114
|
// 2. http_proxy config option provided by this plugin
|
181
115
|
|
182
|
-
Properties props = System.getProperties();
|
116
|
+
final Properties props = System.getProperties();
|
183
117
|
if (props.containsKey("http.proxyHost") || props.containsKey("https.proxyHost")) {
|
184
|
-
boolean useSsl = props.containsKey("https.proxyHost");
|
185
|
-
String proto = !useSsl ? "http" : "https";
|
186
|
-
String
|
187
|
-
|
188
|
-
|
189
|
-
|
118
|
+
final boolean useSsl = props.containsKey("https.proxyHost");
|
119
|
+
final String proto = !useSsl ? "http" : "https";
|
120
|
+
final String hostKey = proto + ".proxyHost";
|
121
|
+
final String portKey = proto + ".proxyPort";
|
122
|
+
final String userKey = proto + ".proxyUser";
|
123
|
+
final String passwordKey = proto + ".proxyPassword";
|
124
|
+
final String host = props.getProperty(hostKey);
|
125
|
+
final String defaultPort = !useSsl ? "80" : "443";
|
126
|
+
final int port = Integer.parseInt(props.getProperty(portKey, defaultPort));
|
127
|
+
final Optional<String> user = Optional.fromNullable(props.getProperty(userKey));
|
128
|
+
final Optional<String> password = Optional.fromNullable(props.getProperty(passwordKey));
|
190
129
|
return Optional.of(new ProxyConfig(host, port, useSsl, user, password));
|
191
|
-
}
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
130
|
+
} else if (task.isPresent()) {
|
131
|
+
final HttpProxyTask proxyTask = task.get();
|
132
|
+
final String host = proxyTask.getHost();
|
133
|
+
final int port = proxyTask.getPort();
|
134
|
+
final boolean useSsl = proxyTask.getUseSsl();
|
135
|
+
final Optional<String> user = proxyTask.getUser();
|
136
|
+
final Optional<String> password = proxyTask.getPassword();
|
137
|
+
return Optional.of(new ProxyConfig(host, port, useSsl, user, password));
|
138
|
+
} else {
|
198
139
|
return Optional.absent();
|
199
140
|
}
|
200
141
|
}
|
201
142
|
|
202
|
-
private TDJob
|
203
|
-
|
204
|
-
String jobId;
|
143
|
+
private TDJob getTdJob(final PluginTask task, final TDClient client) {
|
144
|
+
final String jobId;
|
205
145
|
if (!task.getJobId().isPresent()) {
|
206
146
|
if (!task.getQuery().isPresent() || !task.getDatabase().isPresent()) {
|
207
|
-
throw new ConfigException("Must specify both of 'query' and 'database' options
|
147
|
+
throw new ConfigException("Must specify both of 'query' and 'database' options "
|
148
|
+
+ "if 'job_id' option is not used.");
|
208
149
|
}
|
209
150
|
jobId = submitJob(task, client);
|
210
|
-
}
|
211
|
-
else {
|
151
|
+
} else {
|
212
152
|
jobId = task.getJobId().get();
|
213
153
|
}
|
214
154
|
|
@@ -216,24 +156,23 @@ public class TdInputPlugin
|
|
216
156
|
return client.jobInfo(jobId);
|
217
157
|
}
|
218
158
|
|
219
|
-
private String submitJob(PluginTask task, TDClient client)
|
220
|
-
|
221
|
-
String
|
222
|
-
String database = task.getDatabase().get();
|
159
|
+
private String submitJob(final PluginTask task, final TDClient client) {
|
160
|
+
final String query = task.getQuery().get();
|
161
|
+
final String database = task.getDatabase().get();
|
223
162
|
|
224
|
-
log.info(String.format(ENGLISH,
|
225
|
-
|
226
|
-
|
163
|
+
log.info(String.format(Locale.ENGLISH,
|
164
|
+
"Submit a query for database '%s': %s", database, query));
|
165
|
+
final String jobId = client.submit(TDJobRequest.newPrestoQuery(database, query));
|
166
|
+
log.info(String.format(Locale.ENGLISH, "Job %s is queued.", jobId));
|
227
167
|
return jobId;
|
228
168
|
}
|
229
169
|
|
230
|
-
private void waitJobCompletion(PluginTask task, TDClient client, String jobId)
|
231
|
-
{
|
170
|
+
private void waitJobCompletion(final PluginTask task, final TDClient client, String jobId) {
|
232
171
|
TDJobSummary js;
|
233
172
|
long waitTime = 5 * 1000; // 5 secs
|
234
173
|
|
235
174
|
// wait for job finish
|
236
|
-
log.info(String.format(ENGLISH, "Confirm that job %s finished", jobId));
|
175
|
+
log.info(String.format(Locale.ENGLISH, "Confirm that job %s finished", jobId));
|
237
176
|
while (true) {
|
238
177
|
js = client.jobStatus(jobId);
|
239
178
|
if (js.getStatus().isFinished()) {
|
@@ -243,93 +182,154 @@ public class TdInputPlugin
|
|
243
182
|
log.debug("Wait for job finished");
|
244
183
|
try {
|
245
184
|
Thread.sleep(waitTime);
|
246
|
-
}
|
247
|
-
|
185
|
+
} catch (InterruptedException ignored) {
|
186
|
+
// can ignore
|
248
187
|
}
|
249
188
|
}
|
250
189
|
|
251
190
|
// confirm if the job status is 'success'
|
252
191
|
if (js.getStatus() != TDJob.Status.SUCCESS) {
|
253
|
-
throw new ConfigException(String.format(ENGLISH,
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
private static JsonNode toJsonNode(String schema)
|
258
|
-
{
|
259
|
-
try {
|
260
|
-
return new ObjectMapper().readTree(schema);
|
261
|
-
}
|
262
|
-
catch (IOException e) {
|
263
|
-
throw new ConfigException(String.format(ENGLISH, "Failed to parse job result schema as JSON: %s", schema));
|
192
|
+
throw new ConfigException(String.format(Locale.ENGLISH,
|
193
|
+
"Cannot download job result because the job was '%s'.",
|
194
|
+
js.getStatus()));
|
264
195
|
}
|
265
196
|
}
|
266
197
|
|
267
|
-
private Schema convertSchema(TDJob.Type jobType, JsonNode from)
|
268
|
-
|
269
|
-
|
270
|
-
ArrayNode a = (ArrayNode) from;
|
198
|
+
private Schema convertSchema(final TDJob.Type jobType, final JsonNode from) {
|
199
|
+
final Schema.Builder schema = new Schema.Builder();
|
200
|
+
final ArrayNode a = (ArrayNode) from;
|
271
201
|
for (int i = 0; i < a.size(); i++) {
|
272
|
-
ArrayNode column = (ArrayNode)a.get(i);
|
273
|
-
String name = column.get(0).asText();
|
274
|
-
Type type = convertColumnType(jobType, column.get(1).asText());
|
202
|
+
final ArrayNode column = (ArrayNode) a.get(i);
|
203
|
+
final String name = column.get(0).asText();
|
204
|
+
final Type type = convertColumnType(jobType, column.get(1).asText());
|
275
205
|
schema.add(name, type);
|
276
206
|
}
|
277
207
|
return schema.build();
|
278
208
|
}
|
279
209
|
|
280
|
-
private Type convertColumnType(TDJob.Type jobType, String from)
|
281
|
-
{
|
210
|
+
private Type convertColumnType(final TDJob.Type jobType, final String from) {
|
282
211
|
switch (jobType) {
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
212
|
+
case PRESTO:
|
213
|
+
return convertPrestoColumnType(new Parser(from));
|
214
|
+
case HIVE:
|
215
|
+
default:
|
216
|
+
throw new ConfigException(String.format(Locale.ENGLISH,
|
217
|
+
"Unsupported job type '%s'. Supported types are [presto].",
|
218
|
+
jobType)); // TODO hive
|
288
219
|
}
|
289
220
|
}
|
290
221
|
|
291
|
-
private Type convertPrestoColumnType(
|
292
|
-
|
293
|
-
|
294
|
-
if (
|
295
|
-
return
|
222
|
+
private Type convertPrestoColumnType(final Parser p) {
|
223
|
+
if (p.scan("BOOLEAN")) {
|
224
|
+
return Types.BOOLEAN;
|
225
|
+
} else if (p.scan("INTEGER")) {
|
226
|
+
return Types.LONG;
|
227
|
+
} else if (p.scan("BIGINT")) {
|
228
|
+
return Types.LONG;
|
229
|
+
} else if (p.scan("DOUBLE")) {
|
230
|
+
return Types.DOUBLE;
|
231
|
+
} else if (p.scan("DECIMAL")) {
|
232
|
+
return Types.DOUBLE;
|
233
|
+
} else if (p.scan("VARCHAR")) {
|
234
|
+
// TODO VARCHAR(n)
|
235
|
+
return Types.STRING;
|
236
|
+
} else if (p.scan("ARRAY")) {
|
237
|
+
if (!p.scan("(")) {
|
238
|
+
throw new IllegalArgumentException(
|
239
|
+
"Cannot parse type: expected '(' for array type: " + p.getOriginalString());
|
240
|
+
}
|
241
|
+
convertPrestoColumnType(p);
|
242
|
+
if (!p.scan(")")) {
|
243
|
+
throw new IllegalArgumentException(
|
244
|
+
"Cannot parse type: expected ')' for array type: " + p.getOriginalString());
|
245
|
+
}
|
246
|
+
return Types.JSON;
|
247
|
+
} else if (p.scan("MAP")) {
|
248
|
+
if (!p.scan("(")) {
|
249
|
+
throw new IllegalArgumentException(
|
250
|
+
"Cannot parse type: expected '(' for map type: " + p.getOriginalString());
|
251
|
+
}
|
252
|
+
convertPrestoColumnType(p);
|
253
|
+
if (!p.scan(",")) {
|
254
|
+
throw new IllegalArgumentException(
|
255
|
+
"Cannot parse type: expected ',' for map type: " + p.getOriginalString());
|
256
|
+
}
|
257
|
+
convertPrestoColumnType(p);
|
258
|
+
if (!p.scan(")")) {
|
259
|
+
throw new IllegalArgumentException(
|
260
|
+
"Cannot parse type: expected ')' for map type: " + p.getOriginalString());
|
261
|
+
}
|
262
|
+
return Types.JSON;
|
263
|
+
} else {
|
264
|
+
throw new ConfigException(String.format(Locale.ENGLISH,
|
265
|
+
"Unsupported presto type '%s'", p.getOriginalString())); // TODO other types
|
296
266
|
}
|
297
|
-
|
298
|
-
|
267
|
+
}
|
268
|
+
|
269
|
+
private static class Parser {
|
270
|
+
private final String original;
|
271
|
+
private final String string;
|
272
|
+
private int offset;
|
273
|
+
|
274
|
+
public Parser(final String original) {
|
275
|
+
this.original = original;
|
276
|
+
this.string = original.toUpperCase(Locale.ENGLISH);
|
299
277
|
}
|
300
|
-
|
301
|
-
|
278
|
+
|
279
|
+
public String getOriginalString() {
|
280
|
+
return original;
|
302
281
|
}
|
303
|
-
|
304
|
-
|
282
|
+
|
283
|
+
public String getString() {
|
284
|
+
return string;
|
305
285
|
}
|
306
|
-
|
307
|
-
|
286
|
+
|
287
|
+
public boolean scan(final String s) {
|
288
|
+
skipSpaces();
|
289
|
+
if (string.startsWith(s, offset)) {
|
290
|
+
offset += s.length();
|
291
|
+
return true;
|
292
|
+
}
|
293
|
+
return false;
|
294
|
+
}
|
295
|
+
|
296
|
+
public boolean eof() {
|
297
|
+
skipSpaces();
|
298
|
+
return string.length() <= offset;
|
299
|
+
}
|
300
|
+
|
301
|
+
private void skipSpaces() {
|
302
|
+
while (string.startsWith(" ", offset)) {
|
303
|
+
offset++;
|
304
|
+
}
|
308
305
|
}
|
309
306
|
}
|
310
307
|
|
311
308
|
@Override
|
312
|
-
public ConfigDiff resume(
|
313
|
-
|
314
|
-
|
315
|
-
|
309
|
+
public ConfigDiff resume(
|
310
|
+
final TaskSource taskSource,
|
311
|
+
final Schema schema,
|
312
|
+
final int taskCount,
|
313
|
+
final InputPlugin.Control control) {
|
316
314
|
control.run(taskSource, schema, taskCount);
|
317
|
-
return newConfigDiff();
|
315
|
+
return Exec.newConfigDiff();
|
318
316
|
}
|
319
317
|
|
320
318
|
@Override
|
321
|
-
public void cleanup(
|
322
|
-
|
323
|
-
|
324
|
-
|
319
|
+
public void cleanup(
|
320
|
+
final TaskSource taskSource,
|
321
|
+
final Schema schema,
|
322
|
+
final int taskCount,
|
323
|
+
final List<TaskReport> successTaskReports) {
|
325
324
|
// do nothing
|
326
325
|
}
|
327
326
|
|
328
327
|
@Override
|
329
|
-
public TaskReport run(
|
330
|
-
final
|
331
|
-
|
332
|
-
|
328
|
+
public TaskReport run(
|
329
|
+
final TaskSource taskSource,
|
330
|
+
final Schema schema,
|
331
|
+
final int taskIndex,
|
332
|
+
final PageOutput output) {
|
333
333
|
final PluginTask task = taskSource.loadTask(PluginTask.class);
|
334
334
|
final BufferAllocator allocator = task.getBufferAllocator();
|
335
335
|
final ValueWriter[] writers = newValueWriters(schema);
|
@@ -337,29 +337,33 @@ public class TdInputPlugin
|
|
337
337
|
final boolean stopOnInvalidRecord = task.getStopOnInvalidRecord();
|
338
338
|
|
339
339
|
try (final PageBuilder pageBuilder = new PageBuilder(allocator, schema, output);
|
340
|
-
|
341
|
-
|
340
|
+
final TDClient client = newTdClient(task)) {
|
341
|
+
final TDResultFormat resultFormat = TDResultFormat.MESSAGE_PACK_GZ;
|
342
|
+
client.jobResult(jobId, resultFormat, new Function<InputStream, Void>() {
|
342
343
|
@Override
|
343
|
-
public Void apply(InputStream input)
|
344
|
-
|
345
|
-
|
344
|
+
public Void apply(InputStream input) {
|
345
|
+
try (final MessageUnpacker unpacker = MessagePack
|
346
|
+
.newDefaultUnpacker(new GZIPInputStream(input))) {
|
346
347
|
while (unpacker.hasNext()) {
|
347
348
|
try {
|
348
|
-
Value v;
|
349
|
+
final Value v;
|
349
350
|
try {
|
350
351
|
v = unpacker.unpackValue();
|
351
|
-
}
|
352
|
-
catch (IOException e) {
|
352
|
+
} catch (IOException e) {
|
353
353
|
throw new InvalidRecordException("Cannot unpack value", e);
|
354
354
|
}
|
355
355
|
|
356
356
|
if (!v.isArrayValue()) {
|
357
|
-
throw new InvalidRecordException(
|
357
|
+
throw new InvalidRecordException(
|
358
|
+
String.format(Locale.ENGLISH,
|
359
|
+
"Must be array value: (%s)", v.toString()));
|
358
360
|
}
|
359
361
|
|
360
|
-
ArrayValue record = v.asArrayValue();
|
362
|
+
final ArrayValue record = v.asArrayValue();
|
361
363
|
if (record.size() != schema.size()) {
|
362
|
-
throw new InvalidRecordException(String.format(ENGLISH,
|
364
|
+
throw new InvalidRecordException(String.format(Locale.ENGLISH,
|
365
|
+
"The size (%d) of the record is invalid",
|
366
|
+
record.size()));
|
363
367
|
}
|
364
368
|
|
365
369
|
// write records to the page
|
@@ -368,16 +372,16 @@ public class TdInputPlugin
|
|
368
372
|
}
|
369
373
|
|
370
374
|
pageBuilder.addRecord();
|
371
|
-
}
|
372
|
-
catch (InvalidRecordException e) {
|
375
|
+
} catch (InvalidRecordException e) {
|
373
376
|
if (stopOnInvalidRecord) {
|
374
|
-
throw new DataException(String.format(ENGLISH,
|
377
|
+
throw new DataException(String.format(Locale.ENGLISH,
|
378
|
+
"Invalid record (%s)", e.getMessage()), e);
|
375
379
|
}
|
376
|
-
log.warn(String.format(ENGLISH,
|
380
|
+
log.warn(String.format(Locale.ENGLISH,
|
381
|
+
"Skipped record (%s)", e.getMessage()));
|
377
382
|
}
|
378
383
|
}
|
379
|
-
}
|
380
|
-
catch (IOException e) {
|
384
|
+
} catch (IOException e) {
|
381
385
|
throw Throwables.propagate(e);
|
382
386
|
}
|
383
387
|
|
@@ -388,58 +392,116 @@ public class TdInputPlugin
|
|
388
392
|
pageBuilder.finish();
|
389
393
|
}
|
390
394
|
|
391
|
-
return newTaskReport();
|
395
|
+
return Exec.newTaskReport();
|
392
396
|
}
|
393
397
|
|
394
|
-
private ValueWriter[] newValueWriters(Schema schema)
|
395
|
-
|
396
|
-
ValueWriter[] writers = new ValueWriter[schema.size()];
|
398
|
+
private ValueWriter[] newValueWriters(final Schema schema) {
|
399
|
+
final ValueWriter[] writers = new ValueWriter[schema.size()];
|
397
400
|
for (int i = 0; i < schema.size(); i++) {
|
398
401
|
writers[i] = newValueWriter(schema.getColumn(i));
|
399
402
|
}
|
400
403
|
return writers;
|
401
404
|
}
|
402
405
|
|
403
|
-
private ValueWriter newValueWriter(Column column)
|
404
|
-
|
405
|
-
|
406
|
-
if (type.equals(BOOLEAN)) {
|
406
|
+
private ValueWriter newValueWriter(final Column column) {
|
407
|
+
final Type type = column.getType();
|
408
|
+
if (type.equals(Types.BOOLEAN)) {
|
407
409
|
return new BooleanValueWriter(column);
|
408
|
-
}
|
409
|
-
else if (type.equals(DOUBLE)) {
|
410
|
+
} else if (type.equals(Types.DOUBLE)) {
|
410
411
|
return new DoubleValueWriter(column);
|
411
|
-
}
|
412
|
-
|
413
|
-
|
414
|
-
}
|
415
|
-
else if (type.equals(LONG)) {
|
412
|
+
} else if (type.equals(Types.JSON)) {
|
413
|
+
return new JsonValueWriter(column);
|
414
|
+
} else if (type.equals(Types.LONG)) {
|
416
415
|
return new LongValueWriter(column);
|
417
|
-
}
|
418
|
-
else if (type.equals(STRING)) {
|
416
|
+
} else if (type.equals(Types.STRING)) {
|
419
417
|
return new StringValueWriter(column);
|
420
|
-
}
|
421
|
-
|
422
|
-
|
423
|
-
}
|
424
|
-
|
425
|
-
|
418
|
+
} else if (type.equals(Types.TIMESTAMP)) {
|
419
|
+
throw new ConfigException(String.format(Locale.ENGLISH,
|
420
|
+
"Unsupported column type (%s:%s)", column.getName(), type)); // TODO
|
421
|
+
} else {
|
422
|
+
throw new ConfigException(String.format(Locale.ENGLISH,
|
423
|
+
"Unsupported column type (%s:%s)", column.getName(), type)); // TODO
|
426
424
|
}
|
427
425
|
}
|
428
426
|
|
429
427
|
@Override
|
430
|
-
public ConfigDiff guess(ConfigSource config)
|
431
|
-
|
432
|
-
|
428
|
+
public ConfigDiff guess(final ConfigSource config) {
|
429
|
+
return Exec.newConfigDiff(); // do nothing
|
430
|
+
}
|
431
|
+
|
432
|
+
public interface PluginTask
|
433
|
+
extends Task {
|
434
|
+
|
435
|
+
@Config("apikey")
|
436
|
+
public String getApiKey();
|
437
|
+
|
438
|
+
@Config("endpoint")
|
439
|
+
@ConfigDefault("\"api.treasuredata.com\"")
|
440
|
+
public String getEndpoint();
|
441
|
+
|
442
|
+
@Config("use_ssl")
|
443
|
+
@ConfigDefault("true")
|
444
|
+
public boolean getUseSsl();
|
445
|
+
|
446
|
+
@Config("http_proxy")
|
447
|
+
@ConfigDefault("null")
|
448
|
+
public Optional<HttpProxyTask> getHttpProxy();
|
449
|
+
|
450
|
+
// TODO timeout
|
451
|
+
// TODO query, database
|
452
|
+
|
453
|
+
@Config("query")
|
454
|
+
@ConfigDefault("null")
|
455
|
+
public Optional<String> getQuery();
|
456
|
+
|
457
|
+
@Config("database")
|
458
|
+
@ConfigDefault("null")
|
459
|
+
public Optional<String> getDatabase();
|
460
|
+
|
461
|
+
@Config("job_id")
|
462
|
+
@ConfigDefault("null")
|
463
|
+
public Optional<String> getJobId();
|
464
|
+
|
465
|
+
@Config("stop_on_invalid_record")
|
466
|
+
@ConfigDefault("false")
|
467
|
+
public boolean getStopOnInvalidRecord();
|
468
|
+
|
469
|
+
// TODO column_options
|
470
|
+
|
471
|
+
@ConfigInject
|
472
|
+
BufferAllocator getBufferAllocator();
|
473
|
+
}
|
474
|
+
|
475
|
+
public interface HttpProxyTask
|
476
|
+
extends Task {
|
477
|
+
|
478
|
+
@Config("host")
|
479
|
+
public String getHost();
|
480
|
+
|
481
|
+
@Config("port")
|
482
|
+
public int getPort();
|
483
|
+
|
484
|
+
@Config("use_ssl")
|
485
|
+
@ConfigDefault("false")
|
486
|
+
public boolean getUseSsl();
|
487
|
+
|
488
|
+
@Config("user")
|
489
|
+
@ConfigDefault("null")
|
490
|
+
public Optional<String> getUser();
|
491
|
+
|
492
|
+
@Config("password")
|
493
|
+
@ConfigDefault("null")
|
494
|
+
public Optional<String> getPassword();
|
433
495
|
}
|
434
496
|
|
435
497
|
static class InvalidRecordException
|
436
|
-
extends RuntimeException
|
437
|
-
|
438
|
-
InvalidRecordException(String cause) {
|
498
|
+
extends RuntimeException {
|
499
|
+
|
500
|
+
InvalidRecordException(final String cause) {
|
439
501
|
super(cause);
|
440
502
|
}
|
441
503
|
|
442
|
-
InvalidRecordException(String cause, Throwable t) {
|
504
|
+
InvalidRecordException(final String cause, final Throwable t) {
|
443
505
|
super(cause, t);
|
444
506
|
}
|
445
507
|
}
|