embulk-output-jdbc 0.4.1 → 0.4.2
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/build.gradle +2 -2
- data/classpath/embulk-output-jdbc-0.4.2.jar +0 -0
- data/lib/embulk/output/jdbc.rb +3 -3
- data/src/main/java/org/embulk/output/JdbcOutputPlugin.java +137 -138
- data/src/main/java/org/embulk/output/jdbc/AbstractJdbcOutputPlugin.java +972 -973
- data/src/main/java/org/embulk/output/jdbc/BatchInsert.java +52 -53
- data/src/main/java/org/embulk/output/jdbc/JdbcColumn.java +116 -116
- data/src/main/java/org/embulk/output/jdbc/JdbcColumnOption.java +34 -34
- data/src/main/java/org/embulk/output/jdbc/JdbcOutputConnection.java +492 -492
- data/src/main/java/org/embulk/output/jdbc/JdbcOutputConnector.java +8 -8
- data/src/main/java/org/embulk/output/jdbc/JdbcSchema.java +60 -60
- data/src/main/java/org/embulk/output/jdbc/JdbcUtils.java +153 -155
- data/src/main/java/org/embulk/output/jdbc/StandardBatchInsert.java +200 -200
- data/src/main/java/org/embulk/output/jdbc/ToString.java +54 -54
- data/src/main/java/org/embulk/output/jdbc/ToStringMap.java +34 -35
- data/src/main/java/org/embulk/output/jdbc/setter/BigDecimalColumnSetter.java +68 -70
- data/src/main/java/org/embulk/output/jdbc/setter/BooleanColumnSetter.java +53 -54
- data/src/main/java/org/embulk/output/jdbc/setter/ByteColumnSetter.java +75 -76
- data/src/main/java/org/embulk/output/jdbc/setter/ColumnSetter.java +44 -45
- data/src/main/java/org/embulk/output/jdbc/setter/ColumnSetterFactory.java +196 -196
- data/src/main/java/org/embulk/output/jdbc/setter/ColumnSetterVisitor.java +95 -96
- data/src/main/java/org/embulk/output/jdbc/setter/DefaultValueSetter.java +48 -48
- data/src/main/java/org/embulk/output/jdbc/setter/DoubleColumnSetter.java +60 -61
- data/src/main/java/org/embulk/output/jdbc/setter/FloatColumnSetter.java +60 -61
- data/src/main/java/org/embulk/output/jdbc/setter/IntColumnSetter.java +75 -76
- data/src/main/java/org/embulk/output/jdbc/setter/LongColumnSetter.java +71 -72
- data/src/main/java/org/embulk/output/jdbc/setter/NStringColumnSetter.java +58 -59
- data/src/main/java/org/embulk/output/jdbc/setter/NullColumnSetter.java +53 -53
- data/src/main/java/org/embulk/output/jdbc/setter/NullDefaultValueSetter.java +105 -105
- data/src/main/java/org/embulk/output/jdbc/setter/PassThroughColumnSetter.java +58 -59
- data/src/main/java/org/embulk/output/jdbc/setter/ShortColumnSetter.java +75 -76
- data/src/main/java/org/embulk/output/jdbc/setter/SkipColumnSetter.java +43 -43
- data/src/main/java/org/embulk/output/jdbc/setter/SqlDateColumnSetter.java +58 -59
- data/src/main/java/org/embulk/output/jdbc/setter/SqlTimeColumnSetter.java +58 -59
- data/src/main/java/org/embulk/output/jdbc/setter/SqlTimestampColumnSetter.java +58 -58
- data/src/main/java/org/embulk/output/jdbc/setter/StringColumnSetter.java +58 -59
- data/src/test/java/org/embulk/output/TestJdbcOutputPlugin.java +5 -5
- metadata +3 -3
- data/classpath/embulk-output-jdbc-0.4.1.jar +0 -0
@@ -1,53 +1,52 @@
|
|
1
|
-
package org.embulk.output.jdbc;
|
2
|
-
|
3
|
-
import java.math.BigDecimal;
|
4
|
-
import java.util.Calendar;
|
5
|
-
import java.io.IOException;
|
6
|
-
import java.sql.SQLException;
|
7
|
-
import
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
}
|
1
|
+
package org.embulk.output.jdbc;
|
2
|
+
|
3
|
+
import java.math.BigDecimal;
|
4
|
+
import java.util.Calendar;
|
5
|
+
import java.io.IOException;
|
6
|
+
import java.sql.SQLException;
|
7
|
+
import org.embulk.spi.time.Timestamp;
|
8
|
+
|
9
|
+
public interface BatchInsert
|
10
|
+
{
|
11
|
+
public void prepare(String loadTable, JdbcSchema insertSchema) throws SQLException;
|
12
|
+
|
13
|
+
public int getBatchWeight();
|
14
|
+
|
15
|
+
public void add() throws IOException, SQLException;
|
16
|
+
|
17
|
+
public void close() throws IOException, SQLException;
|
18
|
+
|
19
|
+
public void flush() throws IOException, SQLException;
|
20
|
+
|
21
|
+
public void finish() throws IOException, SQLException;
|
22
|
+
|
23
|
+
public void setNull(int sqlType) throws IOException, SQLException;
|
24
|
+
|
25
|
+
public void setBoolean(boolean v) throws IOException, SQLException;
|
26
|
+
|
27
|
+
public void setByte(byte v) throws IOException, SQLException;
|
28
|
+
|
29
|
+
public void setShort(short v) throws IOException, SQLException;
|
30
|
+
|
31
|
+
public void setInt(int v) throws IOException, SQLException;
|
32
|
+
|
33
|
+
public void setLong(long v) throws IOException, SQLException;
|
34
|
+
|
35
|
+
public void setFloat(float v) throws IOException, SQLException;
|
36
|
+
|
37
|
+
public void setDouble(double v) throws IOException, SQLException;
|
38
|
+
|
39
|
+
public void setBigDecimal(BigDecimal v) throws IOException, SQLException;
|
40
|
+
|
41
|
+
public void setString(String v) throws IOException, SQLException;
|
42
|
+
|
43
|
+
public void setNString(String v) throws IOException, SQLException;
|
44
|
+
|
45
|
+
public void setBytes(byte[] v) throws IOException, SQLException;
|
46
|
+
|
47
|
+
public void setSqlDate(Timestamp v, Calendar cal) throws IOException, SQLException;
|
48
|
+
|
49
|
+
public void setSqlTime(Timestamp v, Calendar cal) throws IOException, SQLException;
|
50
|
+
|
51
|
+
public void setSqlTimestamp(Timestamp v, Calendar cal) throws IOException, SQLException;
|
52
|
+
}
|
@@ -1,116 +1,116 @@
|
|
1
|
-
package org.embulk.output.jdbc;
|
2
|
-
|
3
|
-
import com.google.common.base.Optional;
|
4
|
-
import com.fasterxml.jackson.annotation.JsonCreator;
|
5
|
-
import com.fasterxml.jackson.annotation.JsonProperty;
|
6
|
-
import com.fasterxml.jackson.annotation.JsonIgnore;
|
7
|
-
|
8
|
-
public class JdbcColumn
|
9
|
-
{
|
10
|
-
private final String name;
|
11
|
-
private final String simpleTypeName;
|
12
|
-
private final int sqlType;
|
13
|
-
private final int sizeTypeParameter;
|
14
|
-
private final int scaleTypeParameter;
|
15
|
-
private final Optional<String> declaredType;
|
16
|
-
private final boolean isNotNull;
|
17
|
-
private final boolean isUniqueKey;
|
18
|
-
|
19
|
-
@JsonCreator
|
20
|
-
public JdbcColumn(
|
21
|
-
@JsonProperty("name") String name,
|
22
|
-
@JsonProperty("sqlType") int sqlType,
|
23
|
-
@JsonProperty("simpleTypeName") String simpleTypeName,
|
24
|
-
@JsonProperty("sizeTypeParameter") int sizeTypeParameter,
|
25
|
-
@JsonProperty("scaleTypeParameter") int scaleTypeParameter,
|
26
|
-
@JsonProperty("declaredType") Optional<String> declaredType,
|
27
|
-
@JsonProperty("notNull") boolean isNotNull,
|
28
|
-
@JsonProperty("uniqueKey") boolean isUniqueKey)
|
29
|
-
{
|
30
|
-
this.name = name;
|
31
|
-
this.simpleTypeName = simpleTypeName;
|
32
|
-
this.sqlType = sqlType;
|
33
|
-
this.sizeTypeParameter = sizeTypeParameter;
|
34
|
-
this.scaleTypeParameter = scaleTypeParameter;
|
35
|
-
this.declaredType = declaredType;
|
36
|
-
this.isNotNull = isNotNull;
|
37
|
-
this.isUniqueKey = isUniqueKey;
|
38
|
-
}
|
39
|
-
|
40
|
-
public static JdbcColumn newGenericTypeColumn(String name, int sqlType,
|
41
|
-
String simpleTypeName, int sizeTypeParameter, int scaleTypeParameter,
|
42
|
-
boolean isNotNull, boolean isUniqueKey)
|
43
|
-
{
|
44
|
-
return new JdbcColumn(name, sqlType,
|
45
|
-
simpleTypeName, sizeTypeParameter, scaleTypeParameter, Optional.<String>absent(),
|
46
|
-
isNotNull, isUniqueKey);
|
47
|
-
}
|
48
|
-
|
49
|
-
public static JdbcColumn newTypeDeclaredColumn(String name, int sqlType,
|
50
|
-
String declaredType, boolean isNotNull, boolean isUniqueKey)
|
51
|
-
{
|
52
|
-
return new JdbcColumn(name, sqlType,
|
53
|
-
declaredType, 0, 0, Optional.of(declaredType),
|
54
|
-
isNotNull, isUniqueKey);
|
55
|
-
}
|
56
|
-
|
57
|
-
@JsonIgnore
|
58
|
-
public static JdbcColumn skipColumn()
|
59
|
-
{
|
60
|
-
return new JdbcColumn(null, 0, null, 0, 0, Optional.<String>absent(), false, false);
|
61
|
-
}
|
62
|
-
|
63
|
-
@JsonIgnore
|
64
|
-
public boolean isSkipColumn()
|
65
|
-
{
|
66
|
-
return name == null;
|
67
|
-
}
|
68
|
-
|
69
|
-
@JsonProperty("name")
|
70
|
-
public String getName()
|
71
|
-
{
|
72
|
-
return name;
|
73
|
-
}
|
74
|
-
|
75
|
-
@JsonProperty("sqlType")
|
76
|
-
public int getSqlType()
|
77
|
-
{
|
78
|
-
return sqlType;
|
79
|
-
}
|
80
|
-
|
81
|
-
@JsonProperty("simpleTypeName")
|
82
|
-
public String getSimpleTypeName()
|
83
|
-
{
|
84
|
-
return simpleTypeName;
|
85
|
-
}
|
86
|
-
|
87
|
-
@JsonProperty("sizeTypeParameter")
|
88
|
-
public int getSizeTypeParameter()
|
89
|
-
{
|
90
|
-
return sizeTypeParameter;
|
91
|
-
}
|
92
|
-
|
93
|
-
@JsonProperty("scaleTypeParameter")
|
94
|
-
public int getScaleTypeParameter()
|
95
|
-
{
|
96
|
-
return scaleTypeParameter;
|
97
|
-
}
|
98
|
-
|
99
|
-
@JsonProperty("declaredType")
|
100
|
-
public Optional<String> getDeclaredType()
|
101
|
-
{
|
102
|
-
return declaredType;
|
103
|
-
}
|
104
|
-
|
105
|
-
@JsonProperty("notNull")
|
106
|
-
public boolean isNotNull()
|
107
|
-
{
|
108
|
-
return isNotNull;
|
109
|
-
}
|
110
|
-
|
111
|
-
@JsonProperty("uniqueKey")
|
112
|
-
public boolean isUniqueKey()
|
113
|
-
{
|
114
|
-
return isUniqueKey;
|
115
|
-
}
|
116
|
-
}
|
1
|
+
package org.embulk.output.jdbc;
|
2
|
+
|
3
|
+
import com.google.common.base.Optional;
|
4
|
+
import com.fasterxml.jackson.annotation.JsonCreator;
|
5
|
+
import com.fasterxml.jackson.annotation.JsonProperty;
|
6
|
+
import com.fasterxml.jackson.annotation.JsonIgnore;
|
7
|
+
|
8
|
+
public class JdbcColumn
|
9
|
+
{
|
10
|
+
private final String name;
|
11
|
+
private final String simpleTypeName;
|
12
|
+
private final int sqlType;
|
13
|
+
private final int sizeTypeParameter;
|
14
|
+
private final int scaleTypeParameter;
|
15
|
+
private final Optional<String> declaredType;
|
16
|
+
private final boolean isNotNull;
|
17
|
+
private final boolean isUniqueKey;
|
18
|
+
|
19
|
+
@JsonCreator
|
20
|
+
public JdbcColumn(
|
21
|
+
@JsonProperty("name") String name,
|
22
|
+
@JsonProperty("sqlType") int sqlType,
|
23
|
+
@JsonProperty("simpleTypeName") String simpleTypeName,
|
24
|
+
@JsonProperty("sizeTypeParameter") int sizeTypeParameter,
|
25
|
+
@JsonProperty("scaleTypeParameter") int scaleTypeParameter,
|
26
|
+
@JsonProperty("declaredType") Optional<String> declaredType,
|
27
|
+
@JsonProperty("notNull") boolean isNotNull,
|
28
|
+
@JsonProperty("uniqueKey") boolean isUniqueKey)
|
29
|
+
{
|
30
|
+
this.name = name;
|
31
|
+
this.simpleTypeName = simpleTypeName;
|
32
|
+
this.sqlType = sqlType;
|
33
|
+
this.sizeTypeParameter = sizeTypeParameter;
|
34
|
+
this.scaleTypeParameter = scaleTypeParameter;
|
35
|
+
this.declaredType = declaredType;
|
36
|
+
this.isNotNull = isNotNull;
|
37
|
+
this.isUniqueKey = isUniqueKey;
|
38
|
+
}
|
39
|
+
|
40
|
+
public static JdbcColumn newGenericTypeColumn(String name, int sqlType,
|
41
|
+
String simpleTypeName, int sizeTypeParameter, int scaleTypeParameter,
|
42
|
+
boolean isNotNull, boolean isUniqueKey)
|
43
|
+
{
|
44
|
+
return new JdbcColumn(name, sqlType,
|
45
|
+
simpleTypeName, sizeTypeParameter, scaleTypeParameter, Optional.<String>absent(),
|
46
|
+
isNotNull, isUniqueKey);
|
47
|
+
}
|
48
|
+
|
49
|
+
public static JdbcColumn newTypeDeclaredColumn(String name, int sqlType,
|
50
|
+
String declaredType, boolean isNotNull, boolean isUniqueKey)
|
51
|
+
{
|
52
|
+
return new JdbcColumn(name, sqlType,
|
53
|
+
declaredType, 0, 0, Optional.of(declaredType),
|
54
|
+
isNotNull, isUniqueKey);
|
55
|
+
}
|
56
|
+
|
57
|
+
@JsonIgnore
|
58
|
+
public static JdbcColumn skipColumn()
|
59
|
+
{
|
60
|
+
return new JdbcColumn(null, 0, null, 0, 0, Optional.<String>absent(), false, false);
|
61
|
+
}
|
62
|
+
|
63
|
+
@JsonIgnore
|
64
|
+
public boolean isSkipColumn()
|
65
|
+
{
|
66
|
+
return name == null;
|
67
|
+
}
|
68
|
+
|
69
|
+
@JsonProperty("name")
|
70
|
+
public String getName()
|
71
|
+
{
|
72
|
+
return name;
|
73
|
+
}
|
74
|
+
|
75
|
+
@JsonProperty("sqlType")
|
76
|
+
public int getSqlType()
|
77
|
+
{
|
78
|
+
return sqlType;
|
79
|
+
}
|
80
|
+
|
81
|
+
@JsonProperty("simpleTypeName")
|
82
|
+
public String getSimpleTypeName()
|
83
|
+
{
|
84
|
+
return simpleTypeName;
|
85
|
+
}
|
86
|
+
|
87
|
+
@JsonProperty("sizeTypeParameter")
|
88
|
+
public int getSizeTypeParameter()
|
89
|
+
{
|
90
|
+
return sizeTypeParameter;
|
91
|
+
}
|
92
|
+
|
93
|
+
@JsonProperty("scaleTypeParameter")
|
94
|
+
public int getScaleTypeParameter()
|
95
|
+
{
|
96
|
+
return scaleTypeParameter;
|
97
|
+
}
|
98
|
+
|
99
|
+
@JsonProperty("declaredType")
|
100
|
+
public Optional<String> getDeclaredType()
|
101
|
+
{
|
102
|
+
return declaredType;
|
103
|
+
}
|
104
|
+
|
105
|
+
@JsonProperty("notNull")
|
106
|
+
public boolean isNotNull()
|
107
|
+
{
|
108
|
+
return isNotNull;
|
109
|
+
}
|
110
|
+
|
111
|
+
@JsonProperty("uniqueKey")
|
112
|
+
public boolean isUniqueKey()
|
113
|
+
{
|
114
|
+
return isUniqueKey;
|
115
|
+
}
|
116
|
+
}
|
@@ -1,34 +1,34 @@
|
|
1
|
-
package org.embulk.output.jdbc;
|
2
|
-
|
3
|
-
import com.google.common.base.Optional;
|
4
|
-
import org.joda.time.DateTimeZone;
|
5
|
-
import org.jruby.embed.ScriptingContainer;
|
6
|
-
import org.embulk.config.Task;
|
7
|
-
import org.embulk.config.Config;
|
8
|
-
import org.embulk.config.ConfigDefault;
|
9
|
-
import org.embulk.config.ConfigInject;
|
10
|
-
import org.embulk.spi.time.TimestampFormat;
|
11
|
-
|
12
|
-
public interface JdbcColumnOption
|
13
|
-
extends Task
|
14
|
-
{
|
15
|
-
@Config("type")
|
16
|
-
@ConfigDefault("null")
|
17
|
-
public Optional<String> getType();
|
18
|
-
|
19
|
-
@Config("value_type")
|
20
|
-
@ConfigDefault("\"coerce\"")
|
21
|
-
public String getValueType();
|
22
|
-
|
23
|
-
@Config("timestamp_format")
|
24
|
-
@ConfigDefault("\"%Y-%m-%d %H:%M:%S.%6N\"")
|
25
|
-
public TimestampFormat getTimestampFormat();
|
26
|
-
|
27
|
-
@Config("timezone")
|
28
|
-
@ConfigDefault("null")
|
29
|
-
public Optional<DateTimeZone> getTimeZone();
|
30
|
-
|
31
|
-
// required by TimestampFormatter
|
32
|
-
@ConfigInject
|
33
|
-
public ScriptingContainer getJRuby();
|
34
|
-
}
|
1
|
+
package org.embulk.output.jdbc;
|
2
|
+
|
3
|
+
import com.google.common.base.Optional;
|
4
|
+
import org.joda.time.DateTimeZone;
|
5
|
+
import org.jruby.embed.ScriptingContainer;
|
6
|
+
import org.embulk.config.Task;
|
7
|
+
import org.embulk.config.Config;
|
8
|
+
import org.embulk.config.ConfigDefault;
|
9
|
+
import org.embulk.config.ConfigInject;
|
10
|
+
import org.embulk.spi.time.TimestampFormat;
|
11
|
+
|
12
|
+
public interface JdbcColumnOption
|
13
|
+
extends Task
|
14
|
+
{
|
15
|
+
@Config("type")
|
16
|
+
@ConfigDefault("null")
|
17
|
+
public Optional<String> getType();
|
18
|
+
|
19
|
+
@Config("value_type")
|
20
|
+
@ConfigDefault("\"coerce\"")
|
21
|
+
public String getValueType();
|
22
|
+
|
23
|
+
@Config("timestamp_format")
|
24
|
+
@ConfigDefault("\"%Y-%m-%d %H:%M:%S.%6N\"")
|
25
|
+
public TimestampFormat getTimestampFormat();
|
26
|
+
|
27
|
+
@Config("timezone")
|
28
|
+
@ConfigDefault("null")
|
29
|
+
public Optional<DateTimeZone> getTimeZone();
|
30
|
+
|
31
|
+
// required by TimestampFormatter
|
32
|
+
@ConfigInject
|
33
|
+
public ScriptingContainer getJRuby();
|
34
|
+
}
|
@@ -1,492 +1,492 @@
|
|
1
|
-
package org.embulk.output.jdbc;
|
2
|
-
|
3
|
-
import java.util.List;
|
4
|
-
import java.nio.charset.Charset;
|
5
|
-
import java.nio.charset.StandardCharsets;
|
6
|
-
import java.sql.Connection;
|
7
|
-
import java.sql.DatabaseMetaData;
|
8
|
-
import java.sql.PreparedStatement;
|
9
|
-
import java.sql.ResultSet;
|
10
|
-
import java.sql.SQLException;
|
11
|
-
import java.sql.Statement;
|
12
|
-
import org.slf4j.Logger;
|
13
|
-
import com.google.common.base.Optional;
|
14
|
-
import org.embulk.spi.Exec;
|
15
|
-
|
16
|
-
public class JdbcOutputConnection
|
17
|
-
implements AutoCloseable
|
18
|
-
{
|
19
|
-
private final Logger logger = Exec.getLogger(JdbcOutputConnection.class);
|
20
|
-
protected final Connection connection;
|
21
|
-
protected final String schemaName;
|
22
|
-
protected final DatabaseMetaData databaseMetaData;
|
23
|
-
protected String identifierQuoteString;
|
24
|
-
|
25
|
-
public JdbcOutputConnection(Connection connection, String schemaName)
|
26
|
-
throws SQLException
|
27
|
-
{
|
28
|
-
this.connection = connection;
|
29
|
-
this.schemaName = schemaName;
|
30
|
-
this.databaseMetaData = connection.getMetaData();
|
31
|
-
this.identifierQuoteString = databaseMetaData.getIdentifierQuoteString();
|
32
|
-
if (schemaName != null) {
|
33
|
-
setSearchPath(schemaName);
|
34
|
-
}
|
35
|
-
}
|
36
|
-
|
37
|
-
@Override
|
38
|
-
public void close() throws SQLException
|
39
|
-
{
|
40
|
-
connection.close();
|
41
|
-
}
|
42
|
-
|
43
|
-
public String getSchemaName()
|
44
|
-
{
|
45
|
-
return schemaName;
|
46
|
-
}
|
47
|
-
|
48
|
-
public DatabaseMetaData getMetaData() throws SQLException
|
49
|
-
{
|
50
|
-
return databaseMetaData;
|
51
|
-
}
|
52
|
-
|
53
|
-
public Charset getTableNameCharset() throws SQLException
|
54
|
-
{
|
55
|
-
return StandardCharsets.UTF_8;
|
56
|
-
}
|
57
|
-
|
58
|
-
protected void setSearchPath(String schema) throws SQLException
|
59
|
-
{
|
60
|
-
Statement stmt = connection.createStatement();
|
61
|
-
try {
|
62
|
-
String sql = "SET search_path TO " + quoteIdentifierString(schema);
|
63
|
-
executeUpdate(stmt, sql);
|
64
|
-
commitIfNecessary(connection);
|
65
|
-
} finally {
|
66
|
-
stmt.close();
|
67
|
-
}
|
68
|
-
}
|
69
|
-
|
70
|
-
public boolean tableExists(String tableName) throws SQLException
|
71
|
-
{
|
72
|
-
try (ResultSet rs = connection.getMetaData().getTables(null, schemaName, tableName, null)) {
|
73
|
-
return rs.next();
|
74
|
-
}
|
75
|
-
}
|
76
|
-
|
77
|
-
public void dropTableIfExists(String tableName) throws SQLException
|
78
|
-
{
|
79
|
-
Statement stmt = connection.createStatement();
|
80
|
-
try {
|
81
|
-
dropTableIfExists(stmt, tableName);
|
82
|
-
commitIfNecessary(connection);
|
83
|
-
} catch (SQLException ex) {
|
84
|
-
throw safeRollback(connection, ex);
|
85
|
-
} finally {
|
86
|
-
stmt.close();
|
87
|
-
}
|
88
|
-
}
|
89
|
-
|
90
|
-
protected void dropTableIfExists(Statement stmt, String tableName) throws SQLException
|
91
|
-
{
|
92
|
-
String sql = String.format("DROP TABLE IF EXISTS %s", quoteIdentifierString(tableName));
|
93
|
-
executeUpdate(stmt, sql);
|
94
|
-
}
|
95
|
-
|
96
|
-
public void dropTable(String tableName) throws SQLException
|
97
|
-
{
|
98
|
-
Statement stmt = connection.createStatement();
|
99
|
-
try {
|
100
|
-
dropTable(stmt, tableName);
|
101
|
-
commitIfNecessary(connection);
|
102
|
-
} catch (SQLException ex) {
|
103
|
-
throw safeRollback(connection, ex);
|
104
|
-
} finally {
|
105
|
-
stmt.close();
|
106
|
-
}
|
107
|
-
}
|
108
|
-
|
109
|
-
protected void dropTable(Statement stmt, String tableName) throws SQLException
|
110
|
-
{
|
111
|
-
String sql = String.format("DROP TABLE %s", quoteIdentifierString(tableName));
|
112
|
-
executeUpdate(stmt, sql);
|
113
|
-
}
|
114
|
-
|
115
|
-
public void createTableIfNotExists(String tableName, JdbcSchema schema) throws SQLException
|
116
|
-
{
|
117
|
-
Statement stmt = connection.createStatement();
|
118
|
-
try {
|
119
|
-
String sql = buildCreateTableIfNotExistsSql(tableName, schema);
|
120
|
-
executeUpdate(stmt, sql);
|
121
|
-
commitIfNecessary(connection);
|
122
|
-
} catch (SQLException ex) {
|
123
|
-
throw safeRollback(connection, ex);
|
124
|
-
} finally {
|
125
|
-
stmt.close();
|
126
|
-
}
|
127
|
-
}
|
128
|
-
|
129
|
-
protected String buildCreateTableIfNotExistsSql(String name, JdbcSchema schema)
|
130
|
-
{
|
131
|
-
StringBuilder sb = new StringBuilder();
|
132
|
-
|
133
|
-
sb.append("CREATE TABLE IF NOT EXISTS ");
|
134
|
-
quoteIdentifierString(sb, name);
|
135
|
-
sb.append(buildCreateTableSchemaSql(schema));
|
136
|
-
return sb.toString();
|
137
|
-
}
|
138
|
-
|
139
|
-
protected String buildCreateTableSchemaSql(JdbcSchema schema)
|
140
|
-
{
|
141
|
-
StringBuilder sb = new StringBuilder();
|
142
|
-
|
143
|
-
sb.append(" (");
|
144
|
-
for (int i=0; i < schema.getCount(); i++) {
|
145
|
-
if (i != 0) { sb.append(", "); }
|
146
|
-
quoteIdentifierString(sb, schema.getColumnName(i));
|
147
|
-
sb.append(" ");
|
148
|
-
String typeName = getCreateTableTypeName(schema.getColumn(i));
|
149
|
-
sb.append(typeName);
|
150
|
-
}
|
151
|
-
sb.append(")");
|
152
|
-
|
153
|
-
return sb.toString();
|
154
|
-
}
|
155
|
-
|
156
|
-
public static enum ColumnDeclareType
|
157
|
-
{
|
158
|
-
SIMPLE,
|
159
|
-
SIZE,
|
160
|
-
SIZE_AND_SCALE,
|
161
|
-
SIZE_AND_OPTIONAL_SCALE,
|
162
|
-
};
|
163
|
-
|
164
|
-
protected String getCreateTableTypeName(JdbcColumn c)
|
165
|
-
{
|
166
|
-
if (c.getDeclaredType().isPresent()) {
|
167
|
-
return c.getDeclaredType().get();
|
168
|
-
} else {
|
169
|
-
return buildColumnTypeName(c);
|
170
|
-
}
|
171
|
-
}
|
172
|
-
|
173
|
-
protected String buildColumnTypeName(JdbcColumn c)
|
174
|
-
{
|
175
|
-
String simpleTypeName = c.getSimpleTypeName();
|
176
|
-
switch (getColumnDeclareType(simpleTypeName, c)) {
|
177
|
-
case SIZE:
|
178
|
-
return String.format("%s(%d)", simpleTypeName, c.getSizeTypeParameter());
|
179
|
-
case SIZE_AND_SCALE:
|
180
|
-
if (c.getScaleTypeParameter() < 0) {
|
181
|
-
return String.format("%s(%d,0)", simpleTypeName, c.getSizeTypeParameter());
|
182
|
-
} else {
|
183
|
-
return String.format("%s(%d,%d)", simpleTypeName, c.getSizeTypeParameter(), c.getScaleTypeParameter());
|
184
|
-
}
|
185
|
-
case SIZE_AND_OPTIONAL_SCALE:
|
186
|
-
if (c.getScaleTypeParameter() < 0) {
|
187
|
-
return String.format("%s(%d)", simpleTypeName, c.getSizeTypeParameter());
|
188
|
-
} else {
|
189
|
-
return String.format("%s(%d,%d)", simpleTypeName, c.getSizeTypeParameter(), c.getScaleTypeParameter());
|
190
|
-
}
|
191
|
-
default: // SIMPLE
|
192
|
-
return simpleTypeName;
|
193
|
-
}
|
194
|
-
}
|
195
|
-
|
196
|
-
// TODO
|
197
|
-
private static final String[] STANDARD_SIZE_TYPE_NAMES = new String[] {
|
198
|
-
"CHAR",
|
199
|
-
"VARCHAR", "CHAR VARYING", "CHARACTER VARYING", "LONGVARCHAR",
|
200
|
-
"NCHAR",
|
201
|
-
"NVARCHAR", "NCHAR VARYING", "NATIONAL CHAR VARYING", "NATIONAL CHARACTER VARYING",
|
202
|
-
"BINARY",
|
203
|
-
"VARBINARY", "BINARY VARYING", "LONGVARBINARY",
|
204
|
-
"BIT",
|
205
|
-
"VARBIT", "BIT VARYING",
|
206
|
-
"FLOAT", // SQL standard's FLOAT[(p)] optionally accepts precision
|
207
|
-
};
|
208
|
-
|
209
|
-
private static final String[] STANDARD_SIZE_AND_SCALE_TYPE_NAMES = new String[] {
|
210
|
-
"DECIMAL",
|
211
|
-
};
|
212
|
-
|
213
|
-
protected ColumnDeclareType getColumnDeclareType(String convertedTypeName, JdbcColumn col)
|
214
|
-
{
|
215
|
-
for (String x : STANDARD_SIZE_TYPE_NAMES) {
|
216
|
-
if (x.equals(convertedTypeName)) {
|
217
|
-
return ColumnDeclareType.SIZE;
|
218
|
-
}
|
219
|
-
}
|
220
|
-
|
221
|
-
for (String x : STANDARD_SIZE_AND_SCALE_TYPE_NAMES) {
|
222
|
-
if (x.equals(convertedTypeName)) {
|
223
|
-
return ColumnDeclareType.SIZE_AND_SCALE;
|
224
|
-
}
|
225
|
-
}
|
226
|
-
|
227
|
-
return ColumnDeclareType.SIMPLE;
|
228
|
-
}
|
229
|
-
|
230
|
-
public PreparedStatement prepareBatchInsertStatement(String toTable, JdbcSchema toTableSchema, Optional<List<String>> mergeKeys) throws SQLException
|
231
|
-
{
|
232
|
-
String sql;
|
233
|
-
if (mergeKeys.isPresent()) {
|
234
|
-
sql = buildPreparedMergeSql(toTable, toTableSchema, mergeKeys.get());
|
235
|
-
} else {
|
236
|
-
sql = buildPreparedInsertSql(toTable, toTableSchema);
|
237
|
-
}
|
238
|
-
logger.info("Prepared SQL: {}", sql);
|
239
|
-
return connection.prepareStatement(sql);
|
240
|
-
}
|
241
|
-
|
242
|
-
protected String buildPreparedInsertSql(String toTable, JdbcSchema toTableSchema) throws SQLException
|
243
|
-
{
|
244
|
-
StringBuilder sb = new StringBuilder();
|
245
|
-
|
246
|
-
sb.append("INSERT INTO ");
|
247
|
-
quoteIdentifierString(sb, toTable);
|
248
|
-
|
249
|
-
sb.append(" (");
|
250
|
-
for (int i=0; i < toTableSchema.getCount(); i++) {
|
251
|
-
if(i != 0) { sb.append(", "); }
|
252
|
-
quoteIdentifierString(sb, toTableSchema.getColumnName(i));
|
253
|
-
}
|
254
|
-
sb.append(") VALUES (");
|
255
|
-
for(int i=0; i < toTableSchema.getCount(); i++) {
|
256
|
-
if(i != 0) { sb.append(", "); }
|
257
|
-
sb.append("?");
|
258
|
-
}
|
259
|
-
sb.append(")");
|
260
|
-
|
261
|
-
return sb.toString();
|
262
|
-
}
|
263
|
-
|
264
|
-
protected String buildPreparedMergeSql(String toTable, JdbcSchema toTableSchema, List<String> mergeKeys) throws SQLException
|
265
|
-
{
|
266
|
-
throw new UnsupportedOperationException("not implemented");
|
267
|
-
}
|
268
|
-
|
269
|
-
protected void collectInsert(List<String> fromTables, JdbcSchema schema, String toTable,
|
270
|
-
boolean truncateDestinationFirst) throws SQLException
|
271
|
-
{
|
272
|
-
Statement stmt = connection.createStatement();
|
273
|
-
try {
|
274
|
-
if (truncateDestinationFirst) {
|
275
|
-
String sql = buildTruncateSql(toTable);
|
276
|
-
executeUpdate(stmt, sql);
|
277
|
-
}
|
278
|
-
String sql = buildCollectInsertSql(fromTables, schema, toTable);
|
279
|
-
executeUpdate(stmt, sql);
|
280
|
-
commitIfNecessary(connection);
|
281
|
-
} catch (SQLException ex) {
|
282
|
-
throw safeRollback(connection, ex);
|
283
|
-
} finally {
|
284
|
-
stmt.close();
|
285
|
-
}
|
286
|
-
}
|
287
|
-
|
288
|
-
protected String buildTruncateSql(String table)
|
289
|
-
{
|
290
|
-
StringBuilder sb = new StringBuilder();
|
291
|
-
|
292
|
-
sb.append("DELETE FROM ");
|
293
|
-
quoteIdentifierString(sb, table);
|
294
|
-
|
295
|
-
return sb.toString();
|
296
|
-
}
|
297
|
-
|
298
|
-
protected String buildCollectInsertSql(List<String> fromTables, JdbcSchema schema, String toTable)
|
299
|
-
{
|
300
|
-
StringBuilder sb = new StringBuilder();
|
301
|
-
|
302
|
-
sb.append("INSERT INTO ");
|
303
|
-
quoteIdentifierString(sb, toTable);
|
304
|
-
sb.append(" (");
|
305
|
-
for (int i=0; i < schema.getCount(); i++) {
|
306
|
-
if (i != 0) { sb.append(", "); }
|
307
|
-
quoteIdentifierString(sb, schema.getColumnName(i));
|
308
|
-
}
|
309
|
-
sb.append(") ");
|
310
|
-
for (int i=0; i < fromTables.size(); i++) {
|
311
|
-
if (i != 0) { sb.append(" UNION ALL "); }
|
312
|
-
sb.append("SELECT ");
|
313
|
-
for (int j=0; j < schema.getCount(); j++) {
|
314
|
-
if (j != 0) { sb.append(", "); }
|
315
|
-
quoteIdentifierString(sb, schema.getColumnName(j));
|
316
|
-
}
|
317
|
-
sb.append(" FROM ");
|
318
|
-
quoteIdentifierString(sb, fromTables.get(i));
|
319
|
-
}
|
320
|
-
|
321
|
-
return sb.toString();
|
322
|
-
}
|
323
|
-
|
324
|
-
protected void collectMerge(List<String> fromTables, JdbcSchema schema, String toTable, List<String> mergeKeys) throws SQLException
|
325
|
-
{
|
326
|
-
Statement stmt = connection.createStatement();
|
327
|
-
try {
|
328
|
-
String sql = buildCollectMergeSql(fromTables, schema, toTable, mergeKeys);
|
329
|
-
executeUpdate(stmt, sql);
|
330
|
-
commitIfNecessary(connection);
|
331
|
-
} catch (SQLException ex) {
|
332
|
-
throw safeRollback(connection, ex);
|
333
|
-
} finally {
|
334
|
-
stmt.close();
|
335
|
-
}
|
336
|
-
}
|
337
|
-
|
338
|
-
protected String buildCollectMergeSql(List<String> fromTables, JdbcSchema schema, String toTable, List<String> mergeKeys) throws SQLException
|
339
|
-
{
|
340
|
-
throw new UnsupportedOperationException("not implemented");
|
341
|
-
}
|
342
|
-
|
343
|
-
public void replaceTable(String fromTable, JdbcSchema schema, String toTable) throws SQLException
|
344
|
-
{
|
345
|
-
Statement stmt = connection.createStatement();
|
346
|
-
try {
|
347
|
-
dropTableIfExists(stmt, toTable);
|
348
|
-
|
349
|
-
StringBuilder sb = new StringBuilder();
|
350
|
-
sb.append("ALTER TABLE ");
|
351
|
-
quoteIdentifierString(sb, fromTable);
|
352
|
-
sb.append(" RENAME TO ");
|
353
|
-
quoteIdentifierString(sb, toTable);
|
354
|
-
String sql = sb.toString();
|
355
|
-
executeUpdate(stmt, sql);
|
356
|
-
|
357
|
-
commitIfNecessary(connection);
|
358
|
-
} catch (SQLException ex) {
|
359
|
-
throw safeRollback(connection, ex);
|
360
|
-
} finally {
|
361
|
-
stmt.close();
|
362
|
-
}
|
363
|
-
}
|
364
|
-
|
365
|
-
protected void quoteIdentifierString(StringBuilder sb, String str)
|
366
|
-
{
|
367
|
-
sb.append(quoteIdentifierString(str, identifierQuoteString));
|
368
|
-
}
|
369
|
-
|
370
|
-
protected String quoteIdentifierString(String str)
|
371
|
-
{
|
372
|
-
return quoteIdentifierString(str, identifierQuoteString);
|
373
|
-
}
|
374
|
-
|
375
|
-
protected String quoteIdentifierString(String str, String quoteString)
|
376
|
-
{
|
377
|
-
// TODO if identifierQuoteString.equals(" ") && str.contains([^a-zA-Z0-9_connection.getMetaData().getExtraNameCharacters()])
|
378
|
-
// TODO if str.contains(identifierQuoteString);
|
379
|
-
return quoteString + str + quoteString;
|
380
|
-
}
|
381
|
-
|
382
|
-
// PostgreSQL JDBC driver implements isValid() method. But the
|
383
|
-
// implementation throws following exception:
|
384
|
-
// "java.io.IOException: Method org.postgresql.jdbc4.Jdbc4Connection.isValid(int) is not yet implemented."
|
385
|
-
//
|
386
|
-
// So, checking mechanism doesn't work at all.
|
387
|
-
// Thus here just runs "SELECT 1" to check connectivity.
|
388
|
-
//
|
389
|
-
public boolean isValidConnection(int timeout) throws SQLException
|
390
|
-
{
|
391
|
-
Statement stmt = connection.createStatement();
|
392
|
-
try {
|
393
|
-
stmt.executeQuery("SELECT 1").close();
|
394
|
-
return true;
|
395
|
-
} catch (SQLException ex) {
|
396
|
-
return false;
|
397
|
-
} finally {
|
398
|
-
stmt.close();
|
399
|
-
}
|
400
|
-
}
|
401
|
-
|
402
|
-
protected String[] getDeterministicSqlStates()
|
403
|
-
{
|
404
|
-
return new String[0];
|
405
|
-
}
|
406
|
-
|
407
|
-
protected int[] getDeterministicErrorCodes()
|
408
|
-
{
|
409
|
-
return new int[0];
|
410
|
-
}
|
411
|
-
|
412
|
-
protected Class[] getDeterministicRootCauses()
|
413
|
-
{
|
414
|
-
return new Class[] {
|
415
|
-
// Don't retry on UnknownHostException.
|
416
|
-
java.net.UnknownHostException.class,
|
417
|
-
|
418
|
-
//// we should not retry on connect() error?
|
419
|
-
//java.net.ConnectException.class,
|
420
|
-
};
|
421
|
-
}
|
422
|
-
|
423
|
-
public boolean isRetryableException(SQLException exception)
|
424
|
-
{
|
425
|
-
String sqlState = exception.getSQLState();
|
426
|
-
for (String deterministic : getDeterministicSqlStates()) {
|
427
|
-
if (sqlState.equals(deterministic)) {
|
428
|
-
return false;
|
429
|
-
}
|
430
|
-
}
|
431
|
-
|
432
|
-
int errorCode = exception.getErrorCode();
|
433
|
-
for (int deterministic : getDeterministicErrorCodes()) {
|
434
|
-
if (errorCode == deterministic) {
|
435
|
-
return false;
|
436
|
-
}
|
437
|
-
}
|
438
|
-
|
439
|
-
Throwable rootCause = getRootCause(exception);
|
440
|
-
for (Class deterministic : getDeterministicRootCauses()) {
|
441
|
-
if (deterministic.equals(rootCause.getClass())) {
|
442
|
-
return false;
|
443
|
-
}
|
444
|
-
}
|
445
|
-
|
446
|
-
return true;
|
447
|
-
}
|
448
|
-
|
449
|
-
private Throwable getRootCause(Throwable e) {
|
450
|
-
while (e.getCause() != null) {
|
451
|
-
e = e.getCause();
|
452
|
-
}
|
453
|
-
return e;
|
454
|
-
}
|
455
|
-
|
456
|
-
protected int executeUpdate(Statement stmt, String sql) throws SQLException
|
457
|
-
{
|
458
|
-
logger.info("SQL: " + sql);
|
459
|
-
long startTime = System.currentTimeMillis();
|
460
|
-
int count = stmt.executeUpdate(sql);
|
461
|
-
double seconds = (System.currentTimeMillis() - startTime) / 1000.0;
|
462
|
-
if (count == 0) {
|
463
|
-
logger.info(String.format("> %.2f seconds", seconds));
|
464
|
-
} else {
|
465
|
-
logger.info(String.format("> %.2f seconds (%,d rows)", seconds, count));
|
466
|
-
}
|
467
|
-
return count;
|
468
|
-
}
|
469
|
-
|
470
|
-
protected void commitIfNecessary(Connection con) throws SQLException
|
471
|
-
{
|
472
|
-
if (!con.getAutoCommit()) {
|
473
|
-
con.commit();
|
474
|
-
}
|
475
|
-
}
|
476
|
-
|
477
|
-
protected SQLException safeRollback(Connection con, SQLException cause)
|
478
|
-
{
|
479
|
-
try {
|
480
|
-
if (!con.getAutoCommit()) {
|
481
|
-
con.rollback();
|
482
|
-
}
|
483
|
-
return cause;
|
484
|
-
} catch (SQLException ex) {
|
485
|
-
if (cause != null) {
|
486
|
-
cause.addSuppressed(ex);
|
487
|
-
return cause;
|
488
|
-
}
|
489
|
-
return ex;
|
490
|
-
}
|
491
|
-
}
|
492
|
-
}
|
1
|
+
package org.embulk.output.jdbc;
|
2
|
+
|
3
|
+
import java.util.List;
|
4
|
+
import java.nio.charset.Charset;
|
5
|
+
import java.nio.charset.StandardCharsets;
|
6
|
+
import java.sql.Connection;
|
7
|
+
import java.sql.DatabaseMetaData;
|
8
|
+
import java.sql.PreparedStatement;
|
9
|
+
import java.sql.ResultSet;
|
10
|
+
import java.sql.SQLException;
|
11
|
+
import java.sql.Statement;
|
12
|
+
import org.slf4j.Logger;
|
13
|
+
import com.google.common.base.Optional;
|
14
|
+
import org.embulk.spi.Exec;
|
15
|
+
|
16
|
+
public class JdbcOutputConnection
|
17
|
+
implements AutoCloseable
|
18
|
+
{
|
19
|
+
private final Logger logger = Exec.getLogger(JdbcOutputConnection.class);
|
20
|
+
protected final Connection connection;
|
21
|
+
protected final String schemaName;
|
22
|
+
protected final DatabaseMetaData databaseMetaData;
|
23
|
+
protected String identifierQuoteString;
|
24
|
+
|
25
|
+
public JdbcOutputConnection(Connection connection, String schemaName)
|
26
|
+
throws SQLException
|
27
|
+
{
|
28
|
+
this.connection = connection;
|
29
|
+
this.schemaName = schemaName;
|
30
|
+
this.databaseMetaData = connection.getMetaData();
|
31
|
+
this.identifierQuoteString = databaseMetaData.getIdentifierQuoteString();
|
32
|
+
if (schemaName != null) {
|
33
|
+
setSearchPath(schemaName);
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
@Override
|
38
|
+
public void close() throws SQLException
|
39
|
+
{
|
40
|
+
connection.close();
|
41
|
+
}
|
42
|
+
|
43
|
+
public String getSchemaName()
|
44
|
+
{
|
45
|
+
return schemaName;
|
46
|
+
}
|
47
|
+
|
48
|
+
public DatabaseMetaData getMetaData() throws SQLException
|
49
|
+
{
|
50
|
+
return databaseMetaData;
|
51
|
+
}
|
52
|
+
|
53
|
+
public Charset getTableNameCharset() throws SQLException
|
54
|
+
{
|
55
|
+
return StandardCharsets.UTF_8;
|
56
|
+
}
|
57
|
+
|
58
|
+
protected void setSearchPath(String schema) throws SQLException
|
59
|
+
{
|
60
|
+
Statement stmt = connection.createStatement();
|
61
|
+
try {
|
62
|
+
String sql = "SET search_path TO " + quoteIdentifierString(schema);
|
63
|
+
executeUpdate(stmt, sql);
|
64
|
+
commitIfNecessary(connection);
|
65
|
+
} finally {
|
66
|
+
stmt.close();
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
public boolean tableExists(String tableName) throws SQLException
|
71
|
+
{
|
72
|
+
try (ResultSet rs = connection.getMetaData().getTables(null, schemaName, tableName, null)) {
|
73
|
+
return rs.next();
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
public void dropTableIfExists(String tableName) throws SQLException
|
78
|
+
{
|
79
|
+
Statement stmt = connection.createStatement();
|
80
|
+
try {
|
81
|
+
dropTableIfExists(stmt, tableName);
|
82
|
+
commitIfNecessary(connection);
|
83
|
+
} catch (SQLException ex) {
|
84
|
+
throw safeRollback(connection, ex);
|
85
|
+
} finally {
|
86
|
+
stmt.close();
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
protected void dropTableIfExists(Statement stmt, String tableName) throws SQLException
|
91
|
+
{
|
92
|
+
String sql = String.format("DROP TABLE IF EXISTS %s", quoteIdentifierString(tableName));
|
93
|
+
executeUpdate(stmt, sql);
|
94
|
+
}
|
95
|
+
|
96
|
+
public void dropTable(String tableName) throws SQLException
|
97
|
+
{
|
98
|
+
Statement stmt = connection.createStatement();
|
99
|
+
try {
|
100
|
+
dropTable(stmt, tableName);
|
101
|
+
commitIfNecessary(connection);
|
102
|
+
} catch (SQLException ex) {
|
103
|
+
throw safeRollback(connection, ex);
|
104
|
+
} finally {
|
105
|
+
stmt.close();
|
106
|
+
}
|
107
|
+
}
|
108
|
+
|
109
|
+
protected void dropTable(Statement stmt, String tableName) throws SQLException
|
110
|
+
{
|
111
|
+
String sql = String.format("DROP TABLE %s", quoteIdentifierString(tableName));
|
112
|
+
executeUpdate(stmt, sql);
|
113
|
+
}
|
114
|
+
|
115
|
+
public void createTableIfNotExists(String tableName, JdbcSchema schema) throws SQLException
|
116
|
+
{
|
117
|
+
Statement stmt = connection.createStatement();
|
118
|
+
try {
|
119
|
+
String sql = buildCreateTableIfNotExistsSql(tableName, schema);
|
120
|
+
executeUpdate(stmt, sql);
|
121
|
+
commitIfNecessary(connection);
|
122
|
+
} catch (SQLException ex) {
|
123
|
+
throw safeRollback(connection, ex);
|
124
|
+
} finally {
|
125
|
+
stmt.close();
|
126
|
+
}
|
127
|
+
}
|
128
|
+
|
129
|
+
protected String buildCreateTableIfNotExistsSql(String name, JdbcSchema schema)
|
130
|
+
{
|
131
|
+
StringBuilder sb = new StringBuilder();
|
132
|
+
|
133
|
+
sb.append("CREATE TABLE IF NOT EXISTS ");
|
134
|
+
quoteIdentifierString(sb, name);
|
135
|
+
sb.append(buildCreateTableSchemaSql(schema));
|
136
|
+
return sb.toString();
|
137
|
+
}
|
138
|
+
|
139
|
+
protected String buildCreateTableSchemaSql(JdbcSchema schema)
|
140
|
+
{
|
141
|
+
StringBuilder sb = new StringBuilder();
|
142
|
+
|
143
|
+
sb.append(" (");
|
144
|
+
for (int i=0; i < schema.getCount(); i++) {
|
145
|
+
if (i != 0) { sb.append(", "); }
|
146
|
+
quoteIdentifierString(sb, schema.getColumnName(i));
|
147
|
+
sb.append(" ");
|
148
|
+
String typeName = getCreateTableTypeName(schema.getColumn(i));
|
149
|
+
sb.append(typeName);
|
150
|
+
}
|
151
|
+
sb.append(")");
|
152
|
+
|
153
|
+
return sb.toString();
|
154
|
+
}
|
155
|
+
|
156
|
+
public static enum ColumnDeclareType
|
157
|
+
{
|
158
|
+
SIMPLE,
|
159
|
+
SIZE,
|
160
|
+
SIZE_AND_SCALE,
|
161
|
+
SIZE_AND_OPTIONAL_SCALE,
|
162
|
+
};
|
163
|
+
|
164
|
+
protected String getCreateTableTypeName(JdbcColumn c)
|
165
|
+
{
|
166
|
+
if (c.getDeclaredType().isPresent()) {
|
167
|
+
return c.getDeclaredType().get();
|
168
|
+
} else {
|
169
|
+
return buildColumnTypeName(c);
|
170
|
+
}
|
171
|
+
}
|
172
|
+
|
173
|
+
protected String buildColumnTypeName(JdbcColumn c)
|
174
|
+
{
|
175
|
+
String simpleTypeName = c.getSimpleTypeName();
|
176
|
+
switch (getColumnDeclareType(simpleTypeName, c)) {
|
177
|
+
case SIZE:
|
178
|
+
return String.format("%s(%d)", simpleTypeName, c.getSizeTypeParameter());
|
179
|
+
case SIZE_AND_SCALE:
|
180
|
+
if (c.getScaleTypeParameter() < 0) {
|
181
|
+
return String.format("%s(%d,0)", simpleTypeName, c.getSizeTypeParameter());
|
182
|
+
} else {
|
183
|
+
return String.format("%s(%d,%d)", simpleTypeName, c.getSizeTypeParameter(), c.getScaleTypeParameter());
|
184
|
+
}
|
185
|
+
case SIZE_AND_OPTIONAL_SCALE:
|
186
|
+
if (c.getScaleTypeParameter() < 0) {
|
187
|
+
return String.format("%s(%d)", simpleTypeName, c.getSizeTypeParameter());
|
188
|
+
} else {
|
189
|
+
return String.format("%s(%d,%d)", simpleTypeName, c.getSizeTypeParameter(), c.getScaleTypeParameter());
|
190
|
+
}
|
191
|
+
default: // SIMPLE
|
192
|
+
return simpleTypeName;
|
193
|
+
}
|
194
|
+
}
|
195
|
+
|
196
|
+
// TODO
|
197
|
+
private static final String[] STANDARD_SIZE_TYPE_NAMES = new String[] {
|
198
|
+
"CHAR",
|
199
|
+
"VARCHAR", "CHAR VARYING", "CHARACTER VARYING", "LONGVARCHAR",
|
200
|
+
"NCHAR",
|
201
|
+
"NVARCHAR", "NCHAR VARYING", "NATIONAL CHAR VARYING", "NATIONAL CHARACTER VARYING",
|
202
|
+
"BINARY",
|
203
|
+
"VARBINARY", "BINARY VARYING", "LONGVARBINARY",
|
204
|
+
"BIT",
|
205
|
+
"VARBIT", "BIT VARYING",
|
206
|
+
"FLOAT", // SQL standard's FLOAT[(p)] optionally accepts precision
|
207
|
+
};
|
208
|
+
|
209
|
+
private static final String[] STANDARD_SIZE_AND_SCALE_TYPE_NAMES = new String[] {
|
210
|
+
"DECIMAL",
|
211
|
+
};
|
212
|
+
|
213
|
+
protected ColumnDeclareType getColumnDeclareType(String convertedTypeName, JdbcColumn col)
|
214
|
+
{
|
215
|
+
for (String x : STANDARD_SIZE_TYPE_NAMES) {
|
216
|
+
if (x.equals(convertedTypeName)) {
|
217
|
+
return ColumnDeclareType.SIZE;
|
218
|
+
}
|
219
|
+
}
|
220
|
+
|
221
|
+
for (String x : STANDARD_SIZE_AND_SCALE_TYPE_NAMES) {
|
222
|
+
if (x.equals(convertedTypeName)) {
|
223
|
+
return ColumnDeclareType.SIZE_AND_SCALE;
|
224
|
+
}
|
225
|
+
}
|
226
|
+
|
227
|
+
return ColumnDeclareType.SIMPLE;
|
228
|
+
}
|
229
|
+
|
230
|
+
public PreparedStatement prepareBatchInsertStatement(String toTable, JdbcSchema toTableSchema, Optional<List<String>> mergeKeys) throws SQLException
|
231
|
+
{
|
232
|
+
String sql;
|
233
|
+
if (mergeKeys.isPresent()) {
|
234
|
+
sql = buildPreparedMergeSql(toTable, toTableSchema, mergeKeys.get());
|
235
|
+
} else {
|
236
|
+
sql = buildPreparedInsertSql(toTable, toTableSchema);
|
237
|
+
}
|
238
|
+
logger.info("Prepared SQL: {}", sql);
|
239
|
+
return connection.prepareStatement(sql);
|
240
|
+
}
|
241
|
+
|
242
|
+
protected String buildPreparedInsertSql(String toTable, JdbcSchema toTableSchema) throws SQLException
|
243
|
+
{
|
244
|
+
StringBuilder sb = new StringBuilder();
|
245
|
+
|
246
|
+
sb.append("INSERT INTO ");
|
247
|
+
quoteIdentifierString(sb, toTable);
|
248
|
+
|
249
|
+
sb.append(" (");
|
250
|
+
for (int i=0; i < toTableSchema.getCount(); i++) {
|
251
|
+
if(i != 0) { sb.append(", "); }
|
252
|
+
quoteIdentifierString(sb, toTableSchema.getColumnName(i));
|
253
|
+
}
|
254
|
+
sb.append(") VALUES (");
|
255
|
+
for(int i=0; i < toTableSchema.getCount(); i++) {
|
256
|
+
if(i != 0) { sb.append(", "); }
|
257
|
+
sb.append("?");
|
258
|
+
}
|
259
|
+
sb.append(")");
|
260
|
+
|
261
|
+
return sb.toString();
|
262
|
+
}
|
263
|
+
|
264
|
+
protected String buildPreparedMergeSql(String toTable, JdbcSchema toTableSchema, List<String> mergeKeys) throws SQLException
|
265
|
+
{
|
266
|
+
throw new UnsupportedOperationException("not implemented");
|
267
|
+
}
|
268
|
+
|
269
|
+
protected void collectInsert(List<String> fromTables, JdbcSchema schema, String toTable,
|
270
|
+
boolean truncateDestinationFirst) throws SQLException
|
271
|
+
{
|
272
|
+
Statement stmt = connection.createStatement();
|
273
|
+
try {
|
274
|
+
if (truncateDestinationFirst) {
|
275
|
+
String sql = buildTruncateSql(toTable);
|
276
|
+
executeUpdate(stmt, sql);
|
277
|
+
}
|
278
|
+
String sql = buildCollectInsertSql(fromTables, schema, toTable);
|
279
|
+
executeUpdate(stmt, sql);
|
280
|
+
commitIfNecessary(connection);
|
281
|
+
} catch (SQLException ex) {
|
282
|
+
throw safeRollback(connection, ex);
|
283
|
+
} finally {
|
284
|
+
stmt.close();
|
285
|
+
}
|
286
|
+
}
|
287
|
+
|
288
|
+
protected String buildTruncateSql(String table)
|
289
|
+
{
|
290
|
+
StringBuilder sb = new StringBuilder();
|
291
|
+
|
292
|
+
sb.append("DELETE FROM ");
|
293
|
+
quoteIdentifierString(sb, table);
|
294
|
+
|
295
|
+
return sb.toString();
|
296
|
+
}
|
297
|
+
|
298
|
+
protected String buildCollectInsertSql(List<String> fromTables, JdbcSchema schema, String toTable)
|
299
|
+
{
|
300
|
+
StringBuilder sb = new StringBuilder();
|
301
|
+
|
302
|
+
sb.append("INSERT INTO ");
|
303
|
+
quoteIdentifierString(sb, toTable);
|
304
|
+
sb.append(" (");
|
305
|
+
for (int i=0; i < schema.getCount(); i++) {
|
306
|
+
if (i != 0) { sb.append(", "); }
|
307
|
+
quoteIdentifierString(sb, schema.getColumnName(i));
|
308
|
+
}
|
309
|
+
sb.append(") ");
|
310
|
+
for (int i=0; i < fromTables.size(); i++) {
|
311
|
+
if (i != 0) { sb.append(" UNION ALL "); }
|
312
|
+
sb.append("SELECT ");
|
313
|
+
for (int j=0; j < schema.getCount(); j++) {
|
314
|
+
if (j != 0) { sb.append(", "); }
|
315
|
+
quoteIdentifierString(sb, schema.getColumnName(j));
|
316
|
+
}
|
317
|
+
sb.append(" FROM ");
|
318
|
+
quoteIdentifierString(sb, fromTables.get(i));
|
319
|
+
}
|
320
|
+
|
321
|
+
return sb.toString();
|
322
|
+
}
|
323
|
+
|
324
|
+
protected void collectMerge(List<String> fromTables, JdbcSchema schema, String toTable, List<String> mergeKeys) throws SQLException
|
325
|
+
{
|
326
|
+
Statement stmt = connection.createStatement();
|
327
|
+
try {
|
328
|
+
String sql = buildCollectMergeSql(fromTables, schema, toTable, mergeKeys);
|
329
|
+
executeUpdate(stmt, sql);
|
330
|
+
commitIfNecessary(connection);
|
331
|
+
} catch (SQLException ex) {
|
332
|
+
throw safeRollback(connection, ex);
|
333
|
+
} finally {
|
334
|
+
stmt.close();
|
335
|
+
}
|
336
|
+
}
|
337
|
+
|
338
|
+
protected String buildCollectMergeSql(List<String> fromTables, JdbcSchema schema, String toTable, List<String> mergeKeys) throws SQLException
|
339
|
+
{
|
340
|
+
throw new UnsupportedOperationException("not implemented");
|
341
|
+
}
|
342
|
+
|
343
|
+
public void replaceTable(String fromTable, JdbcSchema schema, String toTable) throws SQLException
|
344
|
+
{
|
345
|
+
Statement stmt = connection.createStatement();
|
346
|
+
try {
|
347
|
+
dropTableIfExists(stmt, toTable);
|
348
|
+
|
349
|
+
StringBuilder sb = new StringBuilder();
|
350
|
+
sb.append("ALTER TABLE ");
|
351
|
+
quoteIdentifierString(sb, fromTable);
|
352
|
+
sb.append(" RENAME TO ");
|
353
|
+
quoteIdentifierString(sb, toTable);
|
354
|
+
String sql = sb.toString();
|
355
|
+
executeUpdate(stmt, sql);
|
356
|
+
|
357
|
+
commitIfNecessary(connection);
|
358
|
+
} catch (SQLException ex) {
|
359
|
+
throw safeRollback(connection, ex);
|
360
|
+
} finally {
|
361
|
+
stmt.close();
|
362
|
+
}
|
363
|
+
}
|
364
|
+
|
365
|
+
protected void quoteIdentifierString(StringBuilder sb, String str)
|
366
|
+
{
|
367
|
+
sb.append(quoteIdentifierString(str, identifierQuoteString));
|
368
|
+
}
|
369
|
+
|
370
|
+
protected String quoteIdentifierString(String str)
|
371
|
+
{
|
372
|
+
return quoteIdentifierString(str, identifierQuoteString);
|
373
|
+
}
|
374
|
+
|
375
|
+
protected String quoteIdentifierString(String str, String quoteString)
|
376
|
+
{
|
377
|
+
// TODO if identifierQuoteString.equals(" ") && str.contains([^a-zA-Z0-9_connection.getMetaData().getExtraNameCharacters()])
|
378
|
+
// TODO if str.contains(identifierQuoteString);
|
379
|
+
return quoteString + str + quoteString;
|
380
|
+
}
|
381
|
+
|
382
|
+
// PostgreSQL JDBC driver implements isValid() method. But the
|
383
|
+
// implementation throws following exception:
|
384
|
+
// "java.io.IOException: Method org.postgresql.jdbc4.Jdbc4Connection.isValid(int) is not yet implemented."
|
385
|
+
//
|
386
|
+
// So, checking mechanism doesn't work at all.
|
387
|
+
// Thus here just runs "SELECT 1" to check connectivity.
|
388
|
+
//
|
389
|
+
public boolean isValidConnection(int timeout) throws SQLException
|
390
|
+
{
|
391
|
+
Statement stmt = connection.createStatement();
|
392
|
+
try {
|
393
|
+
stmt.executeQuery("SELECT 1").close();
|
394
|
+
return true;
|
395
|
+
} catch (SQLException ex) {
|
396
|
+
return false;
|
397
|
+
} finally {
|
398
|
+
stmt.close();
|
399
|
+
}
|
400
|
+
}
|
401
|
+
|
402
|
+
protected String[] getDeterministicSqlStates()
|
403
|
+
{
|
404
|
+
return new String[0];
|
405
|
+
}
|
406
|
+
|
407
|
+
protected int[] getDeterministicErrorCodes()
|
408
|
+
{
|
409
|
+
return new int[0];
|
410
|
+
}
|
411
|
+
|
412
|
+
protected Class[] getDeterministicRootCauses()
|
413
|
+
{
|
414
|
+
return new Class[] {
|
415
|
+
// Don't retry on UnknownHostException.
|
416
|
+
java.net.UnknownHostException.class,
|
417
|
+
|
418
|
+
//// we should not retry on connect() error?
|
419
|
+
//java.net.ConnectException.class,
|
420
|
+
};
|
421
|
+
}
|
422
|
+
|
423
|
+
public boolean isRetryableException(SQLException exception)
|
424
|
+
{
|
425
|
+
String sqlState = exception.getSQLState();
|
426
|
+
for (String deterministic : getDeterministicSqlStates()) {
|
427
|
+
if (sqlState.equals(deterministic)) {
|
428
|
+
return false;
|
429
|
+
}
|
430
|
+
}
|
431
|
+
|
432
|
+
int errorCode = exception.getErrorCode();
|
433
|
+
for (int deterministic : getDeterministicErrorCodes()) {
|
434
|
+
if (errorCode == deterministic) {
|
435
|
+
return false;
|
436
|
+
}
|
437
|
+
}
|
438
|
+
|
439
|
+
Throwable rootCause = getRootCause(exception);
|
440
|
+
for (Class deterministic : getDeterministicRootCauses()) {
|
441
|
+
if (deterministic.equals(rootCause.getClass())) {
|
442
|
+
return false;
|
443
|
+
}
|
444
|
+
}
|
445
|
+
|
446
|
+
return true;
|
447
|
+
}
|
448
|
+
|
449
|
+
private Throwable getRootCause(Throwable e) {
|
450
|
+
while (e.getCause() != null) {
|
451
|
+
e = e.getCause();
|
452
|
+
}
|
453
|
+
return e;
|
454
|
+
}
|
455
|
+
|
456
|
+
protected int executeUpdate(Statement stmt, String sql) throws SQLException
|
457
|
+
{
|
458
|
+
logger.info("SQL: " + sql);
|
459
|
+
long startTime = System.currentTimeMillis();
|
460
|
+
int count = stmt.executeUpdate(sql);
|
461
|
+
double seconds = (System.currentTimeMillis() - startTime) / 1000.0;
|
462
|
+
if (count == 0) {
|
463
|
+
logger.info(String.format("> %.2f seconds", seconds));
|
464
|
+
} else {
|
465
|
+
logger.info(String.format("> %.2f seconds (%,d rows)", seconds, count));
|
466
|
+
}
|
467
|
+
return count;
|
468
|
+
}
|
469
|
+
|
470
|
+
protected void commitIfNecessary(Connection con) throws SQLException
|
471
|
+
{
|
472
|
+
if (!con.getAutoCommit()) {
|
473
|
+
con.commit();
|
474
|
+
}
|
475
|
+
}
|
476
|
+
|
477
|
+
protected SQLException safeRollback(Connection con, SQLException cause)
|
478
|
+
{
|
479
|
+
try {
|
480
|
+
if (!con.getAutoCommit()) {
|
481
|
+
con.rollback();
|
482
|
+
}
|
483
|
+
return cause;
|
484
|
+
} catch (SQLException ex) {
|
485
|
+
if (cause != null) {
|
486
|
+
cause.addSuppressed(ex);
|
487
|
+
return cause;
|
488
|
+
}
|
489
|
+
return ex;
|
490
|
+
}
|
491
|
+
}
|
492
|
+
}
|