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,8 @@
1
+ package org.embulk.output.jdbc;
2
+
3
+ import java.sql.SQLException;
4
+
5
+ public interface JdbcOutputConnector
6
+ {
7
+ public JdbcOutputConnection connect(boolean autoCommit) throws SQLException;
8
+ }
@@ -0,0 +1,37 @@
1
+ package org.embulk.output.jdbc;
2
+
3
+ import java.util.List;
4
+ import com.fasterxml.jackson.annotation.JsonCreator;
5
+ import com.fasterxml.jackson.annotation.JsonValue;
6
+
7
+ public class JdbcSchema
8
+ {
9
+ private List<JdbcColumn> columns;
10
+
11
+ @JsonCreator
12
+ public JdbcSchema(List<JdbcColumn> columns)
13
+ {
14
+ this.columns = columns;
15
+ }
16
+
17
+ @JsonValue
18
+ public List<JdbcColumn> getColumns()
19
+ {
20
+ return columns;
21
+ }
22
+
23
+ public int getCount()
24
+ {
25
+ return columns.size();
26
+ }
27
+
28
+ public JdbcColumn getColumn(int i)
29
+ {
30
+ return columns.get(i);
31
+ }
32
+
33
+ public String getColumnName(int i)
34
+ {
35
+ return columns.get(i).getName();
36
+ }
37
+ }
@@ -0,0 +1,155 @@
1
+ package org.embulk.output.jdbc;
2
+
3
+ import java.sql.Connection;
4
+ import java.sql.PreparedStatement;
5
+ import java.sql.SQLException;
6
+ import java.sql.Statement;
7
+ import java.util.regex.Matcher;
8
+ import java.util.regex.Pattern;
9
+ import org.slf4j.Logger;
10
+ import org.embulk.spi.Exec;
11
+
12
+ public class JdbcUtils
13
+ {
14
+ public final Logger logger = Exec.getLogger(JdbcUtils.class.getName());
15
+
16
+ private static String[] SEARCH_STRING_SPECIAL_CHARS = new String[] { "_", "%" };
17
+
18
+ public static String escapeSearchString(String searchString, String escapeString)
19
+ {
20
+ if (searchString != null && escapeString != null) {
21
+ // First of all, escape escapeString '\' itself: '\' -> '\\'
22
+ searchString = searchString.replaceAll(Pattern.quote(escapeString),
23
+ Matcher.quoteReplacement(escapeString + escapeString));
24
+ for (String specialChar : SEARCH_STRING_SPECIAL_CHARS) {
25
+ if (specialChar.equals(escapeString)) {
26
+ throw new IllegalArgumentException("Special char " + specialChar + " cannot be an escape char");
27
+ }
28
+ searchString = searchString.replaceAll(Pattern.quote(specialChar),
29
+ Matcher.quoteReplacement(escapeString + specialChar));
30
+ }
31
+ }
32
+ return searchString;
33
+ }
34
+
35
+ private Class<?> connectionClass;
36
+
37
+ // Connection.isValid() is available from Java 1.6 + JDBC4.
38
+ //private boolean hasIsValid;
39
+
40
+ // Connection.setNetworkTimeout() is available from Java 1.7 + JDBC4.
41
+ //private boolean hasSetNetworkTimeout;
42
+ //private Method setNetworkTimeoutMethod;
43
+
44
+ public JdbcUtils(Class<?> connectionClass)
45
+ {
46
+ this.connectionClass = connectionClass;
47
+
48
+ //configureSetNetworkTimeout();
49
+ }
50
+
51
+ public int executeUpdateWithSqlLogging(Statement stmt, String sql) throws SQLException
52
+ {
53
+ logger.info("SQL: " + sql);
54
+ long startTime = System.currentTimeMillis();
55
+ int count = stmt.executeUpdate(sql);
56
+ double seconds = (System.currentTimeMillis() - startTime) / 1000.0;
57
+ if (count == 0) {
58
+ logger.info(String.format("> %.2f seconds", seconds));
59
+ } else {
60
+ logger.info(String.format("> %.2f seconds (%,d rows)", seconds, count));
61
+ }
62
+ return count;
63
+ }
64
+
65
+ //private void configureSetNetworkTimeout() {
66
+ // try {
67
+ // Method m = connectionClass.getMethod("setNetworkTimeout", Executor.class, int.class);
68
+ // if (isCallableMethod(m)) {
69
+ // setNetworkTimeoutMethod = m;
70
+ // hasSetNetworkTimeout = true;
71
+ // }
72
+ // } catch (SecurityException ex) {
73
+ // } catch (NoSuchMethodException ex) {
74
+ // }
75
+ //}
76
+
77
+ //private boolean isCallableMethod(Method m) {
78
+ // int modifiers = m.getModifiers();
79
+ // if (Modifier.isAbstract(modifiers)) {
80
+ // // Method.invoke throws java.lang.AbstractMethodError if it's an
81
+ // // abstract method. Applications can't catch AbstractMethodError.
82
+ // return false;
83
+ // }
84
+ // if (!Modifier.isPublic(modifiers)) {
85
+ // // we can only call public methods
86
+ // return false;
87
+ // }
88
+ // return true;
89
+ //}
90
+
91
+ // PostgreSQL JDBC driver implements isValid() method. But the
92
+ // implementation throws following exception:
93
+ // "java.io.IOException: Method org.postgresql.jdbc4.Jdbc4Connection.isValid(int) is not yet implemented."
94
+ //
95
+ // So, checking mechanism doesn't work at all.
96
+ // Thus here just runs "SELECT 1" to check connectivity.
97
+ //
98
+ //public boolean isValidConnection(Connection connection, int timeout) throws SQLException
99
+ //{
100
+ // Statement stmt = connection.createStatement();
101
+ // try {
102
+ // stmt.executeQuery("SELECT 1").close();
103
+ // return true;
104
+ // } catch (SQLException ex) {
105
+ // return false;
106
+ // } finally {
107
+ // stmt.close();
108
+ // }
109
+ //}
110
+
111
+ //public void setNetworkTimeout(Connection connection,
112
+ // Executor executor, int milliseconds) throws SQLException {
113
+ // Throwable exception = null;
114
+ // if (hasSetNetworkTimeout) {
115
+ // try {
116
+ // setNetworkTimeoutMethod.invoke(connection, executor, milliseconds);
117
+ // return;
118
+ //
119
+ // } catch (IllegalArgumentException ex) {
120
+ // // ignore error
121
+ // LOG.warn("Connection.setNetworkTimeout failed due to IllegalArgumentException.");
122
+ // exception = ex;
123
+ //
124
+ // } catch (IllegalAccessException ex) {
125
+ // // ignore error
126
+ // LOG.warn("Connection.setNetworkTimeout failed due to IllegalAccessException.");
127
+ // exception = ex;
128
+ //
129
+ // } catch (InvocationTargetException ex) {
130
+ // //Throwable cause = ex.getTargetException();
131
+ // //if (cause instanceof SQLException) {
132
+ // // throw (SQLException) cause;
133
+ // //} else if (cause instanceof RuntimeException) {
134
+ // // throw (RuntimeException) cause;
135
+ // //} else if (cause instanceof Error) {
136
+ // // throw (Error) cause;
137
+ // //} else {
138
+ // // throw new SQLException(cause);
139
+ // //}
140
+ // exception = ex.getTargetException();
141
+ // // It's safe to ignore exceptions.
142
+ // }
143
+ //
144
+ // hasSetNetworkTimeout = false;
145
+ // }
146
+ //
147
+ // if (exception != null) {
148
+ // LOG.warn("Connection.setNetworkTimeout is not available: "+exception);
149
+ // } else {
150
+ // LOG.warn("Connection.setNetworkTimeout is not available.");
151
+ // }
152
+ // // TODO any substitute implementations?
153
+ //}
154
+ }
155
+
@@ -0,0 +1,105 @@
1
+ package org.embulk.output.jdbc;
2
+
3
+ import java.util.concurrent.Callable;
4
+ import java.util.concurrent.ExecutionException;
5
+
6
+ public class RetryExecutor
7
+ {
8
+ public static RetryExecutor retryExecutor()
9
+ {
10
+ return new RetryExecutor();
11
+ }
12
+
13
+ public static abstract class IdempotentOperation<T> implements Callable<T>
14
+ {
15
+ public abstract T call() throws Exception;
16
+
17
+ public void onRetry(Throwable exception, int retryCount, int retryLimit, int retryWait)
18
+ { }
19
+
20
+ public void onGiveup(Throwable firstException, Throwable lastException)
21
+ { }
22
+
23
+ public abstract boolean isRetryableException(Throwable exception);
24
+ }
25
+
26
+ private int retryLimit = 3;
27
+ private int initialRetryWait = 500;
28
+ private int maxRetryWait = 30*60*1000;
29
+
30
+ private RetryExecutor()
31
+ { }
32
+
33
+ public RetryExecutor setRetryLimit(int count)
34
+ {
35
+ this.retryLimit = count;
36
+ return this;
37
+ }
38
+
39
+ public RetryExecutor setInitialRetryWait(int msec)
40
+ {
41
+ this.initialRetryWait = msec;
42
+ return this;
43
+ }
44
+
45
+ public RetryExecutor setMaxRetryWait(int msec)
46
+ {
47
+ this.maxRetryWait = msec;
48
+ return this;
49
+ }
50
+
51
+ public <T> T runInterruptible(IdempotentOperation<T> op) throws InterruptedException, ExecutionException
52
+ {
53
+ return run(op, true);
54
+ }
55
+
56
+ public <T> T run(IdempotentOperation<T> op) throws ExecutionException
57
+ {
58
+ try {
59
+ return run(op, false);
60
+ } catch (InterruptedException ex) {
61
+ throw new ExecutionException("Unexpected interruption", ex);
62
+ }
63
+ }
64
+
65
+ private <T> T run(IdempotentOperation<T> op, boolean interruptible)
66
+ throws InterruptedException, ExecutionException
67
+ {
68
+ int retryWait = initialRetryWait;
69
+ int retryCount = 0;
70
+
71
+ Throwable firstException = null;
72
+
73
+ while(true) {
74
+ try {
75
+ return op.call();
76
+ } catch (Throwable exception) {
77
+ if (firstException == null) {
78
+ firstException = exception;
79
+ }
80
+ if (!op.isRetryableException(exception) || retryCount >= retryLimit) {
81
+ op.onGiveup(firstException, exception);
82
+ throw new ExecutionException(firstException);
83
+ }
84
+
85
+ retryCount++;
86
+ op.onRetry(exception, retryCount, retryLimit, retryWait);
87
+
88
+ try {
89
+ Thread.sleep(retryWait);
90
+ } catch (InterruptedException ex) {
91
+ if (interruptible) {
92
+ throw ex;
93
+ }
94
+ }
95
+
96
+ retryWait *= 2; // exponential back-off
97
+
98
+ if (retryWait > maxRetryWait) {
99
+ retryWait = maxRetryWait;
100
+ }
101
+ }
102
+ }
103
+ }
104
+ }
105
+
@@ -0,0 +1,180 @@
1
+ package org.embulk.output.jdbc;
2
+
3
+ import java.io.IOException;
4
+ import java.math.BigDecimal;
5
+ import java.sql.PreparedStatement;
6
+ import java.sql.SQLException;
7
+ import java.sql.Date;
8
+ import java.sql.Time;
9
+ import java.sql.Timestamp;
10
+ import org.slf4j.Logger;
11
+ import org.embulk.spi.Exec;
12
+
13
+ public class StandardBatchInsert
14
+ implements BatchInsert
15
+ {
16
+ private final Logger logger = Exec.getLogger(StandardBatchInsert.class);
17
+
18
+ private final JdbcOutputConnector connector;
19
+
20
+ private JdbcOutputConnection connection;
21
+ private PreparedStatement batch;
22
+ private int index;
23
+ private int batchWeight;
24
+ private int batchRows;
25
+ private long totalRows;
26
+
27
+ public StandardBatchInsert(JdbcOutputConnector connector) throws IOException, SQLException
28
+ {
29
+ this.connector = connector;
30
+ }
31
+
32
+ public void prepare(String loadTable, JdbcSchema insertSchema) throws SQLException
33
+ {
34
+ this.connection = connector.connect(true);
35
+ this.batch = connection.prepareInsertSql(loadTable, insertSchema);
36
+ this.index = 1; // PreparedStatement index begings from 1
37
+ this.batchRows = 0;
38
+ this.totalRows = 0;
39
+ batch.clearBatch();
40
+ }
41
+
42
+ public int getBatchWeight()
43
+ {
44
+ return batchWeight;
45
+ }
46
+
47
+ public void add() throws IOException, SQLException
48
+ {
49
+ batch.addBatch();
50
+ index = 1; // PreparedStatement index begins from 1
51
+ batchRows++;
52
+ batchWeight += 32; // add weight as overhead of each rows
53
+ }
54
+
55
+ public void close() throws IOException, SQLException
56
+ {
57
+ // caller should close the connection
58
+ }
59
+
60
+ public void flush() throws IOException, SQLException
61
+ {
62
+ logger.info(String.format("Loading %,d rows", batchRows));
63
+ long startTime = System.currentTimeMillis();
64
+ batch.executeBatch(); // here can't use returned value because MySQL Connector/J returns SUCCESS_NO_INFO as a batch result
65
+ double seconds = (System.currentTimeMillis() - startTime) / 1000.0;
66
+
67
+ totalRows += batchRows;
68
+ logger.info(String.format("> %.2f seconds (loaded %,d rows in total)", seconds, totalRows));
69
+ batch.clearBatch();
70
+ batchRows = 0;
71
+ batchWeight = 0;
72
+ }
73
+
74
+ public void finish() throws IOException, SQLException
75
+ {
76
+ if (getBatchWeight() != 0) {
77
+ flush();
78
+ }
79
+ }
80
+
81
+ public void setNull(int sqlType) throws IOException, SQLException
82
+ {
83
+ batch.setNull(index, sqlType);
84
+ nextColumn(0);
85
+ }
86
+
87
+ public void setBoolean(boolean v) throws IOException, SQLException
88
+ {
89
+ batch.setBoolean(index, v);
90
+ nextColumn(1);
91
+ }
92
+
93
+ public void setByte(byte v) throws IOException, SQLException
94
+ {
95
+ batch.setByte(index, v);
96
+ nextColumn(1);
97
+ }
98
+
99
+ public void setShort(short v) throws IOException, SQLException
100
+ {
101
+ batch.setShort(index, v);
102
+ nextColumn(2);
103
+ }
104
+
105
+ public void setInt(int v) throws IOException, SQLException
106
+ {
107
+ batch.setInt(index, v);
108
+ nextColumn(4);
109
+ }
110
+
111
+ public void setLong(long v) throws IOException, SQLException
112
+ {
113
+ batch.setLong(index, v);
114
+ nextColumn(8);
115
+ }
116
+
117
+ public void setFloat(float v) throws IOException, SQLException
118
+ {
119
+ batch.setFloat(index, v);
120
+ nextColumn(4);
121
+ }
122
+
123
+ public void setDouble(double v) throws IOException, SQLException
124
+ {
125
+ batch.setDouble(index, v);
126
+ nextColumn(8);
127
+ }
128
+
129
+ public void setBigDecimal(BigDecimal v) throws IOException, SQLException
130
+ {
131
+ // use estimated number of necessary bytes + 8 byte for the weight
132
+ // assuming one place needs 4 bits. ceil(v.precision() / 2.0) + 8
133
+ batch.setBigDecimal(index, v);
134
+ nextColumn((v.precision() & ~2) / 2 + 8);
135
+ }
136
+
137
+ public void setString(String v) throws IOException, SQLException
138
+ {
139
+ batch.setString(index, v);
140
+ // estimate all chracters use 2 bytes; almost enough for the worst case
141
+ nextColumn(v.length() * 2 + 4);
142
+ }
143
+
144
+ public void setNString(String v) throws IOException, SQLException
145
+ {
146
+ batch.setNString(index, v);
147
+ // estimate all chracters use 2 bytes; almost enough for the worst case
148
+ nextColumn(v.length() * 2 + 4);
149
+ }
150
+
151
+ public void setBytes(byte[] v) throws IOException, SQLException
152
+ {
153
+ batch.setBytes(index, v);
154
+ nextColumn(v.length + 4);
155
+ }
156
+
157
+ public void setSqlDate(Date v, int sqlType) throws IOException, SQLException
158
+ {
159
+ batch.setObject(index, v, sqlType);
160
+ nextColumn(32);
161
+ }
162
+
163
+ public void setSqlTime(Time v, int sqlType) throws IOException, SQLException
164
+ {
165
+ batch.setObject(index, v, sqlType);
166
+ nextColumn(32);
167
+ }
168
+
169
+ public void setSqlTimestamp(Timestamp v, int sqlType) throws IOException, SQLException
170
+ {
171
+ batch.setObject(index, v, sqlType);
172
+ nextColumn(32);
173
+ }
174
+
175
+ private void nextColumn(int weight)
176
+ {
177
+ index++;
178
+ batchWeight += weight + 4; // add weight as overhead of each columns
179
+ }
180
+ }