embulk-output-jdbc 0.1.0

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.
Files changed (25) hide show
  1. checksums.yaml +7 -0
  2. data/build.gradle +2 -0
  3. data/classpath/embulk-output-jdbc-0.1.0.jar +0 -0
  4. data/lib/embulk/output/jdbc.rb +3 -0
  5. data/src/main/java/org/embulk/output/JdbcOutputPlugin.java +104 -0
  6. data/src/main/java/org/embulk/output/jdbc/AbstractJdbcOutputPlugin.java +701 -0
  7. data/src/main/java/org/embulk/output/jdbc/BatchInsert.java +54 -0
  8. data/src/main/java/org/embulk/output/jdbc/JdbcColumn.java +71 -0
  9. data/src/main/java/org/embulk/output/jdbc/JdbcOutputConnection.java +423 -0
  10. data/src/main/java/org/embulk/output/jdbc/JdbcOutputConnector.java +8 -0
  11. data/src/main/java/org/embulk/output/jdbc/JdbcSchema.java +37 -0
  12. data/src/main/java/org/embulk/output/jdbc/JdbcUtils.java +155 -0
  13. data/src/main/java/org/embulk/output/jdbc/RetryExecutor.java +105 -0
  14. data/src/main/java/org/embulk/output/jdbc/StandardBatchInsert.java +180 -0
  15. data/src/main/java/org/embulk/output/jdbc/setter/BooleanColumnSetter.java +52 -0
  16. data/src/main/java/org/embulk/output/jdbc/setter/ColumnSetter.java +121 -0
  17. data/src/main/java/org/embulk/output/jdbc/setter/ColumnSetterFactory.java +137 -0
  18. data/src/main/java/org/embulk/output/jdbc/setter/DoubleColumnSetter.java +51 -0
  19. data/src/main/java/org/embulk/output/jdbc/setter/LongColumnSetter.java +62 -0
  20. data/src/main/java/org/embulk/output/jdbc/setter/NullColumnSetter.java +43 -0
  21. data/src/main/java/org/embulk/output/jdbc/setter/SkipColumnSetter.java +35 -0
  22. data/src/main/java/org/embulk/output/jdbc/setter/SqlTimestampColumnSetter.java +48 -0
  23. data/src/main/java/org/embulk/output/jdbc/setter/StringColumnSetter.java +48 -0
  24. data/src/test/java/org/embulk/output/TestJdbcOutputPlugin.java +5 -0
  25. metadata +67 -0
@@ -0,0 +1,54 @@
1
+ package org.embulk.output.jdbc;
2
+
3
+ import java.math.BigDecimal;
4
+ import java.io.IOException;
5
+ import java.sql.SQLException;
6
+ import java.sql.PreparedStatement;
7
+ import java.sql.Date;
8
+ import java.sql.Time;
9
+ import java.sql.Timestamp;
10
+
11
+ public interface BatchInsert
12
+ {
13
+ public void prepare(String loadTable, JdbcSchema insertSchema) throws SQLException;
14
+
15
+ public int getBatchWeight();
16
+
17
+ public void add() throws IOException, SQLException;
18
+
19
+ public void close() throws IOException, SQLException;
20
+
21
+ public void flush() throws IOException, SQLException;
22
+
23
+ public void finish() throws IOException, SQLException;
24
+
25
+ public void setNull(int sqlType) throws IOException, SQLException;
26
+
27
+ public void setBoolean(boolean v) throws IOException, SQLException;
28
+
29
+ public void setByte(byte v) throws IOException, SQLException;
30
+
31
+ public void setShort(short v) throws IOException, SQLException;
32
+
33
+ public void setInt(int v) throws IOException, SQLException;
34
+
35
+ public void setLong(long v) throws IOException, SQLException;
36
+
37
+ public void setFloat(float v) throws IOException, SQLException;
38
+
39
+ public void setDouble(double v) throws IOException, SQLException;
40
+
41
+ public void setBigDecimal(BigDecimal v) throws IOException, SQLException;
42
+
43
+ public void setString(String v) throws IOException, SQLException;
44
+
45
+ public void setNString(String v) throws IOException, SQLException;
46
+
47
+ public void setBytes(byte[] v) throws IOException, SQLException;
48
+
49
+ public void setSqlDate(Date v, int sqlType) throws IOException, SQLException;
50
+
51
+ public void setSqlTime(Time v, int sqlType) throws IOException, SQLException;
52
+
53
+ public void setSqlTimestamp(Timestamp v, int sqlType) throws IOException, SQLException;
54
+ }
@@ -0,0 +1,71 @@
1
+ package org.embulk.output.jdbc;
2
+
3
+ import com.fasterxml.jackson.annotation.JsonCreator;
4
+ import com.fasterxml.jackson.annotation.JsonProperty;
5
+ import com.fasterxml.jackson.annotation.JsonIgnore;
6
+
7
+ public class JdbcColumn
8
+ {
9
+ private String name;
10
+ private String typeName;
11
+ private int sqlType;
12
+ private int sizeTypeParameter;
13
+ private int scaleTypeParameter;
14
+
15
+ @JsonCreator
16
+ public JdbcColumn(
17
+ @JsonProperty("name") String name,
18
+ @JsonProperty("typeName") String typeName,
19
+ @JsonProperty("sqlType") int sqlType,
20
+ @JsonProperty("sizeTypeParameter") int sizeTypeParameter,
21
+ @JsonProperty("scaleTypeParameter") int scaleTypeParameter)
22
+ {
23
+ this.name = name;
24
+ this.typeName = typeName;
25
+ this.sqlType = sqlType;
26
+ this.sizeTypeParameter = sizeTypeParameter;
27
+ this.scaleTypeParameter = scaleTypeParameter;
28
+ }
29
+
30
+ @JsonIgnore
31
+ public static JdbcColumn skipColumn()
32
+ {
33
+ return new JdbcColumn(null, null, 0, 0, 0);
34
+ }
35
+
36
+ @JsonIgnore
37
+ public boolean isSkipColumn()
38
+ {
39
+ return name == null;
40
+ }
41
+
42
+ @JsonProperty("name")
43
+ public String getName()
44
+ {
45
+ return name;
46
+ }
47
+
48
+ @JsonProperty("typeName")
49
+ public String getTypeName()
50
+ {
51
+ return typeName;
52
+ }
53
+
54
+ @JsonProperty("sqlType")
55
+ public int getSqlType()
56
+ {
57
+ return sqlType;
58
+ }
59
+
60
+ @JsonProperty("sizeTypeParameter")
61
+ public int getSizeTypeParameter()
62
+ {
63
+ return sizeTypeParameter;
64
+ }
65
+
66
+ @JsonProperty("scaleTypeParameter")
67
+ public int getScaleTypeParameter()
68
+ {
69
+ return scaleTypeParameter;
70
+ }
71
+ }
@@ -0,0 +1,423 @@
1
+ package org.embulk.output.jdbc;
2
+
3
+ import java.util.List;
4
+ import java.sql.Connection;
5
+ import java.sql.DatabaseMetaData;
6
+ import java.sql.PreparedStatement;
7
+ import java.sql.SQLException;
8
+ import java.sql.Statement;
9
+ import org.slf4j.Logger;
10
+ import org.embulk.spi.Exec;
11
+
12
+ public class JdbcOutputConnection
13
+ implements AutoCloseable
14
+ {
15
+ private final Logger logger = Exec.getLogger(JdbcOutputConnection.class);
16
+ protected final Connection connection;
17
+ protected final String schemaName;
18
+ protected final DatabaseMetaData databaseMetaData;
19
+ protected String identifierQuoteString;
20
+
21
+ public JdbcOutputConnection(Connection connection, String schemaName)
22
+ throws SQLException
23
+ {
24
+ this.connection = connection;
25
+ this.schemaName = schemaName;
26
+ this.databaseMetaData = connection.getMetaData();
27
+ this.identifierQuoteString = databaseMetaData.getIdentifierQuoteString();
28
+ if (schemaName != null) {
29
+ setSearchPath(schemaName);
30
+ }
31
+ }
32
+
33
+ @Override
34
+ public void close() throws SQLException
35
+ {
36
+ connection.close();
37
+ }
38
+
39
+ public String getSchemaName()
40
+ {
41
+ return schemaName;
42
+ }
43
+
44
+ public DatabaseMetaData getMetaData() throws SQLException
45
+ {
46
+ return databaseMetaData;
47
+ }
48
+
49
+ protected void setSearchPath(String schema) throws SQLException
50
+ {
51
+ Statement stmt = connection.createStatement();
52
+ try {
53
+ String sql = "SET search_path TO " + quoteIdentifierString(schema);
54
+ executeUpdate(stmt, sql);
55
+ } finally {
56
+ stmt.close();
57
+ }
58
+ }
59
+
60
+ public void dropTableIfExists(String tableName) throws SQLException
61
+ {
62
+ Statement stmt = connection.createStatement();
63
+ try {
64
+ String sql = String.format("DROP TABLE IF EXISTS %s", quoteIdentifierString(tableName));
65
+ executeUpdate(stmt, sql);
66
+ connection.commit();
67
+ } catch (SQLException ex) {
68
+ connection.rollback();
69
+ throw ex;
70
+ } finally {
71
+ stmt.close();
72
+ }
73
+ }
74
+
75
+ public void createTableIfNotExists(String tableName, JdbcSchema schema) throws SQLException
76
+ {
77
+ Statement stmt = connection.createStatement();
78
+ try {
79
+ String sql = buildCreateTableIfNotExistsSql(tableName, schema);
80
+ executeUpdate(stmt, sql);
81
+ connection.commit();
82
+ } catch (SQLException ex) {
83
+ connection.rollback();
84
+ throw ex;
85
+ } finally {
86
+ stmt.close();
87
+ }
88
+ }
89
+
90
+ protected String buildCreateTableIfNotExistsSql(String name, JdbcSchema schema)
91
+ {
92
+ StringBuilder sb = new StringBuilder();
93
+
94
+ sb.append("CREATE TABLE IF NOT EXISTS ");
95
+ quoteIdentifierString(sb, name);
96
+ sb.append(" (");
97
+ boolean first = true;
98
+ for (JdbcColumn c : schema.getColumns()) {
99
+ if (first) { first = false; }
100
+ else { sb.append(", "); }
101
+ quoteIdentifierString(sb, c.getName());
102
+ sb.append(" ");
103
+ String typeName = getCreateTableTypeName(c);
104
+ sb.append(typeName);
105
+ }
106
+ sb.append(")");
107
+
108
+ return sb.toString();
109
+ }
110
+
111
+ public static enum ColumnDeclareType
112
+ {
113
+ SIMPLE,
114
+ SIZE,
115
+ SIZE_AND_SCALE,
116
+ SIZE_AND_OPTIONAL_SCALE,
117
+ };
118
+
119
+ protected String getCreateTableTypeName(JdbcColumn c)
120
+ {
121
+ String convertedTypeName = convertTypeName(c.getTypeName());
122
+ switch (getColumnDeclareType(convertedTypeName, c)) {
123
+ case SIZE:
124
+ return String.format("%s(%d)", convertedTypeName, c.getSizeTypeParameter());
125
+ case SIZE_AND_SCALE:
126
+ if (c.getScaleTypeParameter() < 0) {
127
+ return String.format("%s(%d,0)", convertedTypeName, c.getSizeTypeParameter());
128
+ } else {
129
+ return String.format("%s(%d,%d)", convertedTypeName, c.getSizeTypeParameter(), c.getScaleTypeParameter());
130
+ }
131
+ case SIZE_AND_OPTIONAL_SCALE:
132
+ if (c.getScaleTypeParameter() < 0) {
133
+ return String.format("%s(%d)", convertedTypeName, c.getSizeTypeParameter());
134
+ } else {
135
+ return String.format("%s(%d,%d)", convertedTypeName, c.getSizeTypeParameter(), c.getScaleTypeParameter());
136
+ }
137
+ default: // SIMPLE
138
+ return convertedTypeName;
139
+ }
140
+ }
141
+
142
+ // hook point for subclasses
143
+ protected String convertTypeName(String typeName)
144
+ {
145
+ return typeName;
146
+ }
147
+
148
+ // TODO
149
+ private static final String[] STANDARD_SIZE_TYPE_NAMES = new String[] {
150
+ "CHAR",
151
+ "VARCHAR", "CHAR VARYING", "CHARACTER VARYING", "LONGVARCHAR",
152
+ "NCHAR",
153
+ "NVARCHAR", "NCHAR VARYING", "NATIONAL CHAR VARYING", "NATIONAL CHARACTER VARYING",
154
+ "BINARY",
155
+ "VARBINARY", "BINARY VARYING", "LONGVARBINARY",
156
+ "BIT",
157
+ "VARBIT", "BIT VARYING",
158
+ "FLOAT", // SQL standard's FLOAT[(p)] optionally accepts precision
159
+ };
160
+
161
+ private static final String[] STANDARD_SIZE_AND_SCALE_TYPE_NAMES = new String[] {
162
+ "DECIMAL",
163
+ };
164
+
165
+ protected ColumnDeclareType getColumnDeclareType(String convertedTypeName, JdbcColumn col)
166
+ {
167
+ for (String x : STANDARD_SIZE_TYPE_NAMES) {
168
+ if (x.equals(convertedTypeName)) {
169
+ return ColumnDeclareType.SIZE;
170
+ }
171
+ }
172
+
173
+ for (String x : STANDARD_SIZE_AND_SCALE_TYPE_NAMES) {
174
+ if (x.equals(convertedTypeName)) {
175
+ return ColumnDeclareType.SIZE_AND_SCALE;
176
+ }
177
+ }
178
+
179
+ return ColumnDeclareType.SIMPLE;
180
+ }
181
+
182
+ protected String buildInsertTableSql(String fromTable, JdbcSchema fromTableSchema, String toTable)
183
+ {
184
+ StringBuilder sb = new StringBuilder();
185
+
186
+ sb.append("INSERT INTO ");
187
+ quoteIdentifierString(sb, toTable);
188
+ sb.append(" (");
189
+ boolean first = true;
190
+ for (JdbcColumn c : fromTableSchema.getColumns()) {
191
+ if (first) { first = false; }
192
+ else { sb.append(", "); }
193
+ quoteIdentifierString(sb, c.getName());
194
+ }
195
+ sb.append(") ");
196
+ sb.append("SELECT ");
197
+ for (JdbcColumn c : fromTableSchema.getColumns()) {
198
+ if (first) { first = false; }
199
+ else { sb.append(", "); }
200
+ quoteIdentifierString(sb, c.getName());
201
+ }
202
+ sb.append(" FROM ");
203
+ quoteIdentifierString(sb, fromTable);
204
+
205
+ return sb.toString();
206
+ }
207
+
208
+ protected String buildTruncateSql(String table)
209
+ {
210
+ StringBuilder sb = new StringBuilder();
211
+
212
+ sb.append("DELETE FROM ");
213
+ quoteIdentifierString(sb, table);
214
+
215
+ return sb.toString();
216
+ }
217
+
218
+ protected void insertTable(String fromTable, JdbcSchema fromTableSchema, String toTable,
219
+ boolean truncateDestinationFirst) throws SQLException
220
+ {
221
+ Statement stmt = connection.createStatement();
222
+ try {
223
+ if(truncateDestinationFirst) {
224
+ String sql = buildTruncateSql(toTable);
225
+ executeUpdate(stmt, sql);
226
+ }
227
+ String sql = buildInsertTableSql(fromTable, fromTableSchema, toTable);
228
+ executeUpdate(stmt, sql);
229
+ connection.commit();
230
+ } catch (SQLException ex) {
231
+ connection.rollback();
232
+ throw ex;
233
+ } finally {
234
+ stmt.close();
235
+ }
236
+ }
237
+
238
+ public PreparedStatement prepareInsertSql(String toTable, JdbcSchema toTableSchema) throws SQLException
239
+ {
240
+ String insertSql = buildPrepareInsertSql(toTable, toTableSchema);
241
+ logger.info("Prepared SQL: {}", insertSql);
242
+ return connection.prepareStatement(insertSql);
243
+ }
244
+
245
+ protected String buildPrepareInsertSql(String toTable, JdbcSchema toTableSchema) throws SQLException
246
+ {
247
+ StringBuilder sb = new StringBuilder();
248
+
249
+ sb.append("INSERT INTO ");
250
+ quoteIdentifierString(sb, toTable);
251
+
252
+ sb.append(" (");
253
+ for (int i=0; i < toTableSchema.getCount(); i++) {
254
+ if(i != 0) { sb.append(", "); }
255
+ quoteIdentifierString(sb, toTableSchema.getColumnName(i));
256
+ }
257
+ sb.append(") VALUES (");
258
+ for(int i=0; i < toTableSchema.getCount(); i++) {
259
+ if(i != 0) { sb.append(", "); }
260
+ sb.append("?");
261
+ }
262
+ sb.append(")");
263
+
264
+ return sb.toString();
265
+ }
266
+
267
+ // TODO
268
+ //protected void gatherInsertTables(List<String> fromTables, JdbcSchema fromTableSchema, String toTable,
269
+ // boolean truncateDestinationFirst) throws SQLException
270
+ //{
271
+ // Statement stmt = connection.createStatement();
272
+ // try {
273
+ // if(truncateDestinationFirst) {
274
+ // String sql = buildTruncateSql(toTable);
275
+ // executeUpdate(stmt, sql);
276
+ // }
277
+ // String sql = buildGatherInsertTables(fromTable, fromTableSchema, toTable);
278
+ // executeUpdate(stmt, sql);
279
+ // connection.commit();
280
+ // } catch (SQLException ex) {
281
+ // connection.rollback();
282
+ // throw ex;
283
+ // } finally {
284
+ // stmt.close();
285
+ // }
286
+ //}
287
+
288
+ public void replaceTable(String fromTable, JdbcSchema schema, String toTable) throws SQLException
289
+ {
290
+ Statement stmt = connection.createStatement();
291
+ try {
292
+ {
293
+ StringBuilder sb = new StringBuilder();
294
+ sb.append("DROP TABLE IF EXISTS ");
295
+ quoteIdentifierString(sb, toTable);
296
+ String sql = sb.toString();
297
+ executeUpdate(stmt, sql);
298
+ }
299
+
300
+ {
301
+ StringBuilder sb = new StringBuilder();
302
+ sb.append("ALTER TABLE ");
303
+ quoteIdentifierString(sb, fromTable);
304
+ sb.append(" RENAME TO ");
305
+ quoteIdentifierString(sb, toTable);
306
+ String sql = sb.toString();
307
+ executeUpdate(stmt, sql);
308
+ }
309
+
310
+ connection.commit();
311
+ } catch (SQLException ex) {
312
+ connection.rollback();
313
+ throw ex;
314
+ } finally {
315
+ stmt.close();
316
+ }
317
+ }
318
+
319
+ protected void quoteIdentifierString(StringBuilder sb, String str)
320
+ {
321
+ sb.append(quoteIdentifierString(str, identifierQuoteString));
322
+ }
323
+
324
+ protected String quoteIdentifierString(String str)
325
+ {
326
+ return quoteIdentifierString(str, identifierQuoteString);
327
+ }
328
+
329
+ protected String quoteIdentifierString(String str, String quoteString)
330
+ {
331
+ // TODO if identifierQuoteString.equals(" ") && str.contains([^a-zA-Z0-9_connection.getMetaData().getExtraNameCharacters()])
332
+ // TODO if str.contains(identifierQuoteString);
333
+ return quoteString + str + quoteString;
334
+ }
335
+
336
+ // PostgreSQL JDBC driver implements isValid() method. But the
337
+ // implementation throws following exception:
338
+ // "java.io.IOException: Method org.postgresql.jdbc4.Jdbc4Connection.isValid(int) is not yet implemented."
339
+ //
340
+ // So, checking mechanism doesn't work at all.
341
+ // Thus here just runs "SELECT 1" to check connectivity.
342
+ //
343
+ public boolean isValidConnection(int timeout) throws SQLException
344
+ {
345
+ Statement stmt = connection.createStatement();
346
+ try {
347
+ stmt.executeQuery("SELECT 1").close();
348
+ return true;
349
+ } catch (SQLException ex) {
350
+ return false;
351
+ } finally {
352
+ stmt.close();
353
+ }
354
+ }
355
+
356
+ protected String[] getDeterministicSqlStates()
357
+ {
358
+ return new String[0];
359
+ }
360
+
361
+ protected int[] getDeterministicErrorCodes()
362
+ {
363
+ return new int[0];
364
+ }
365
+
366
+ protected Class[] getDeterministicRootCauses()
367
+ {
368
+ return new Class[] {
369
+ // Don't retry on UnknownHostException.
370
+ java.net.UnknownHostException.class,
371
+
372
+ //// we should not retry on connect() error?
373
+ //java.net.ConnectException.class,
374
+ };
375
+ }
376
+
377
+ public boolean isRetryableException(SQLException exception)
378
+ {
379
+ String sqlState = exception.getSQLState();
380
+ for (String deterministic : getDeterministicSqlStates()) {
381
+ if (sqlState.equals(deterministic)) {
382
+ return false;
383
+ }
384
+ }
385
+
386
+ int errorCode = exception.getErrorCode();
387
+ for (int deterministic : getDeterministicErrorCodes()) {
388
+ if (errorCode == deterministic) {
389
+ return false;
390
+ }
391
+ }
392
+
393
+ Throwable rootCause = getRootCause(exception);
394
+ for (Class deterministic : getDeterministicRootCauses()) {
395
+ if (deterministic.equals(rootCause.getClass())) {
396
+ return false;
397
+ }
398
+ }
399
+
400
+ return true;
401
+ }
402
+
403
+ private Throwable getRootCause(Throwable e) {
404
+ while (e.getCause() != null) {
405
+ e = e.getCause();
406
+ }
407
+ return e;
408
+ }
409
+
410
+ protected int executeUpdate(Statement stmt, String sql) throws SQLException
411
+ {
412
+ logger.info("SQL: " + sql);
413
+ long startTime = System.currentTimeMillis();
414
+ int count = stmt.executeUpdate(sql);
415
+ double seconds = (System.currentTimeMillis() - startTime) / 1000.0;
416
+ if (count == 0) {
417
+ logger.info(String.format("> %.2f seconds", seconds));
418
+ } else {
419
+ logger.info(String.format("> %.2f seconds (%,d rows)", seconds, count));
420
+ }
421
+ return count;
422
+ }
423
+ }