embulk-output-jdbc 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }