1 package com.atlassian.dbexporter.exporter;
2
3 import com.atlassian.dbexporter.Context;
4 import com.atlassian.dbexporter.EntityNameProcessor;
5 import com.atlassian.dbexporter.ImportExportErrorService;
6 import com.atlassian.dbexporter.Table;
7 import com.atlassian.dbexporter.jdbc.JdbcUtils;
8 import com.atlassian.dbexporter.node.NodeCreator;
9 import com.atlassian.dbexporter.progress.ProgressMonitor;
10 import com.google.common.base.Function;
11 import com.google.common.collect.Iterables;
12
13 import java.math.BigDecimal;
14 import java.sql.Clob;
15 import java.sql.Connection;
16 import java.sql.ResultSet;
17 import java.sql.ResultSetMetaData;
18 import java.sql.SQLException;
19 import java.sql.Statement;
20 import java.sql.Timestamp;
21 import java.sql.Types;
22
23 import static com.atlassian.dbexporter.jdbc.JdbcUtils.JdbcCallable;
24 import static com.atlassian.dbexporter.jdbc.JdbcUtils.closeQuietly;
25 import static com.atlassian.dbexporter.jdbc.JdbcUtils.createStatement;
26 import static com.atlassian.dbexporter.jdbc.JdbcUtils.metadata;
27 import static com.atlassian.dbexporter.jdbc.JdbcUtils.quote;
28 import static com.atlassian.dbexporter.jdbc.JdbcUtils.withConnection;
29 import static com.atlassian.dbexporter.jdbc.JdbcUtils.withNoAutoCommit;
30 import static com.atlassian.dbexporter.node.NodeBackup.ColumnDataNode;
31 import static com.atlassian.dbexporter.node.NodeBackup.RowDataNode;
32 import static com.atlassian.dbexporter.node.NodeBackup.TableDataNode;
33 import static com.atlassian.dbexporter.progress.ProgressMonitor.Task;
34 import static com.google.common.base.Preconditions.checkNotNull;
35
36 public final class DataExporter implements Exporter {
37 private final ImportExportErrorService errorService;
38 private final String schema;
39
40 public DataExporter(ImportExportErrorService errorService, String schema) {
41 this.errorService = checkNotNull(errorService);
42 this.schema = isBlank(schema) ? null : schema;
43 }
44
45 @Override
46 public void export(final NodeCreator node, final ExportConfiguration configuration, final Context context) {
47 final ProgressMonitor monitor = configuration.getProgressMonitor();
48 monitor.begin(Task.TABLES_DATA);
49 withConnection(errorService, configuration.getConnectionProvider(), new JdbcUtils.JdbcCallable<Void>() {
50 public Void call(Connection connection) {
51 for (final String table : getTableNames(context)) {
52 withNoAutoCommit(errorService, connection, new JdbcCallable<Void>() {
53 @Override
54 public Void call(Connection connection) {
55 exportTable(table, connection, node, monitor, configuration.getEntityNameProcessor());
56 return null;
57 }
58 });
59 }
60 node.closeEntity();
61 return null;
62 }
63 });
64 monitor.end(Task.TABLES_DATA);
65 }
66
67
68
69
70
71
72 private Iterable<String> getTableNames(Context context) {
73 return Iterables.transform(context.getAll(Table.class), new Function<Table, String>() {
74 @Override
75 public String apply(Table t) {
76 return t.getName();
77 }
78 });
79 }
80
81 private NodeCreator exportTable(String table, Connection connection, NodeCreator node, ProgressMonitor monitor, EntityNameProcessor entityNameProcessor) {
82 monitor.begin(Task.TABLE_DATA, entityNameProcessor.tableName(table));
83 TableDataNode.add(node, entityNameProcessor.tableName(table));
84
85 final Statement statement = createStatement(errorService, table, connection);
86 ResultSet result = null;
87 try {
88 result = executeQueryWithFetchSize(table, statement, "SELECT * FROM " + tableName(table, connection), 100);
89 final ResultSetMetaData meta = resultSetMetaData(table, result);
90
91
92 node = writeColumnDefinitions(table, node, meta, entityNameProcessor);
93 while (next(table, result)) {
94 node = exportRow(table, node, result, monitor);
95 }
96 } finally {
97 closeQuietly(result, statement);
98 }
99
100 monitor.end(Task.TABLE_DATA, entityNameProcessor.tableName(table));
101 return node.closeEntity();
102 }
103
104 private String tableName(String table, Connection connection) {
105 final String quoted = quote(errorService, table, connection, table);
106 return schema != null ? schema + "." + quoted : quoted;
107 }
108
109 private NodeCreator exportRow(String table, NodeCreator node, ResultSet result, ProgressMonitor monitor) {
110 monitor.begin(Task.TABLE_ROW);
111 final ResultSetMetaData metaData = resultSetMetaData(table, result);
112
113 RowDataNode.add(node);
114
115 for (int col = 1; col <= columnCount(table, metaData); col++) {
116 switch (columnType(table, metaData, col)) {
117 case Types.TINYINT:
118 case Types.SMALLINT:
119 case Types.BIGINT:
120 case Types.INTEGER:
121 appendInteger(table, result, col, node);
122 break;
123 case Types.NUMERIC:
124
125 if (scale(table, metaData, col) > 0 || precision(table, metaData, col) == 126) {
126 appendDouble(table, result, col, node);
127 }
128
129 else if (precision(table, metaData, col) == 1) {
130 appendBoolean(table, result, col, node);
131 } else {
132 appendInteger(table, result, col, node);
133 }
134 break;
135 case Types.CHAR:
136 case Types.NCHAR:
137 case Types.VARCHAR:
138 case Types.LONGVARCHAR:
139 case Types.NVARCHAR:
140 case Types.LONGNVARCHAR:
141 final String s = getString(table, result, col);
142 RowDataNode.append(node, wasNull(table, result) ? null : s);
143 break;
144
145 case Types.BOOLEAN:
146 case Types.BIT:
147 appendBoolean(table, result, col, node);
148 break;
149
150 case Types.DOUBLE:
151 case Types.DECIMAL:
152 appendDouble(table, result, col, node);
153 break;
154
155 case Types.TIMESTAMP:
156 final Timestamp t = getTimestamp(table, result, col);
157 RowDataNode.append(node, wasNull(table, result) ? null : t);
158 break;
159
160 case Types.CLOB:
161 case Types.NCLOB:
162 final String c = getClobAsString(table, result, col);
163 RowDataNode.append(node, wasNull(table, result) ? null : c);
164 break;
165
166 case Types.BLOB:
167 case Types.BINARY:
168 case Types.VARBINARY:
169 case Types.LONGVARBINARY:
170 final byte[] b = getBinary(table, result, col);
171 RowDataNode.append(node, wasNull(table, result) ? null : b);
172 break;
173
174 default:
175 throw errorService.newImportExportException(table, String.format(
176 "Cannot encode value for unsupported column type: \"%s\" (%d) of column %s.%s",
177 columnTypeName(table, metaData, col),
178 columnType(table, metaData, col),
179 table,
180 columnName(table, metaData, col)));
181 }
182 }
183
184 monitor.end(Task.TABLE_ROW);
185 return node.closeEntity();
186 }
187
188 private void appendBoolean(String table, ResultSet result, int col, NodeCreator node) {
189 final boolean b = getBoolean(table, result, col);
190 RowDataNode.append(node, wasNull(table, result) ? null : b);
191 }
192
193 private void appendInteger(String table, ResultSet result, int col, NodeCreator node) {
194 final BigDecimal bd = getBigDecimal(table, result, col);
195 RowDataNode.append(node, wasNull(table, result) ? null : bd.toBigInteger());
196 }
197
198 private void appendDouble(String table, ResultSet result, int col, NodeCreator node) {
199 final double d = getDouble(table, result, col);
200 RowDataNode.append(node, wasNull(table, result) ? null : BigDecimal.valueOf(d));
201 }
202
203 private NodeCreator writeColumnDefinitions(String table, NodeCreator node, ResultSetMetaData metaData, EntityNameProcessor entityNameProcessor) {
204 for (int i = 1; i <= columnCount(table, metaData); i++) {
205 final String columnName = entityNameProcessor.columnName(columnName(table, metaData, i));
206 ColumnDataNode.add(node, columnName).closeEntity();
207 }
208 return node;
209 }
210
211 private ResultSetMetaData resultSetMetaData(String table, ResultSet result) {
212 try {
213 return result.getMetaData();
214 } catch (SQLException e) {
215 throw errorService.newImportExportSqlException(table, "Could not get result set metadata", e);
216 }
217 }
218
219 private int scale(String table, ResultSetMetaData metaData, int col) {
220 try {
221 return metaData.getScale(col);
222 } catch (SQLException e) {
223 throw errorService.newImportExportSqlException(table, "Could not get scale for col #" + col + " from result set meta data", e);
224 }
225 }
226
227 private int precision(String table, ResultSetMetaData metaData, int col) {
228 try {
229 return metaData.getPrecision(col);
230 } catch (SQLException e) {
231 throw errorService.newImportExportSqlException(table, "Could not get scale for col #" + col + " from result set meta data", e);
232 }
233 }
234
235 private int columnCount(String table, ResultSetMetaData metaData) {
236 try {
237 return metaData.getColumnCount();
238 } catch (SQLException e) {
239 throw errorService.newImportExportSqlException(table, "Could not get column count from result set metadata", e);
240 }
241 }
242
243 private int columnType(String table, ResultSetMetaData metaData, int col) {
244 try {
245 return metaData.getColumnType(col);
246 } catch (SQLException e) {
247 throw errorService.newImportExportSqlException(table, "Could not get column type for col #" + col + " from result set meta data", e);
248 }
249 }
250
251 private String columnTypeName(String table, ResultSetMetaData metaData, int col) {
252 try {
253 return metaData.getColumnTypeName(col);
254 } catch (SQLException e) {
255 throw errorService.newImportExportSqlException(table, "Could not get column type name for col #" + col + " from result set meta data", e);
256 }
257 }
258
259 private String columnName(String table, ResultSetMetaData metaData, int i) {
260 try {
261 return metaData.getColumnName(i);
262 } catch (SQLException e) {
263 throw errorService.newImportExportSqlException(table, "Could not get column #" + i + " name from result set meta data", e);
264 }
265 }
266
267 private String getString(String table, ResultSet result, int col) {
268 try {
269 return result.getString(col);
270 } catch (SQLException e) {
271 throw errorService.newImportExportSqlException(table, "Could not get string value for col #" + col, e);
272 }
273 }
274
275 private boolean getBoolean(String table, ResultSet result, int col) {
276 try {
277 return result.getBoolean(col);
278 } catch (SQLException e) {
279 throw errorService.newImportExportSqlException(table, "Could not get boolean value for col #" + col, e);
280 }
281 }
282
283 private BigDecimal getBigDecimal(String table, ResultSet result, int col) {
284 try {
285 return result.getBigDecimal(col);
286 } catch (SQLException e) {
287 throw errorService.newImportExportSqlException(table, "Could not get big decimal value for col #" + col, e);
288 }
289 }
290
291 private double getDouble(String table, ResultSet result, int col) {
292 try {
293 return result.getDouble(col);
294 } catch (SQLException e) {
295 throw errorService.newImportExportSqlException(table, "Could not get double value for col #" + col, e);
296 }
297 }
298
299 private Timestamp getTimestamp(String table, ResultSet result, int col) {
300 try {
301 return result.getTimestamp(col);
302 } catch (SQLException e) {
303 throw errorService.newImportExportSqlException(table, "Could not get timestamp value for col #" + col, e);
304 }
305 }
306
307 private String getClobAsString(String table, ResultSet result, int col) {
308 try {
309 final Clob clob = result.getClob(col);
310 return clob == null ? null : clob.getSubString(1L, (int) clob.length());
311 } catch (SQLException e) {
312 throw errorService.newImportExportSqlException(table, "Could not get clob value for col #" + col, e);
313 }
314 }
315
316 private byte[] getBinary(String table, ResultSet result, int col) {
317 try {
318 return result.getBytes(col);
319 } catch (SQLException e) {
320 throw errorService.newImportExportSqlException(table, "Could not get binary value for col #" + col, e);
321 }
322 }
323
324 private boolean wasNull(String table, ResultSet result) {
325 try {
326 return result.wasNull();
327 } catch (SQLException e) {
328 throw errorService.newImportExportSqlException(table, "Could not figure out whether value was NULL", e);
329 }
330 }
331
332 private boolean next(String table, ResultSet result) {
333 try {
334 return result.next();
335 } catch (SQLException e) {
336 throw errorService.newImportExportSqlException(table, "Could not get next for result set", e);
337 }
338 }
339
340 private ResultSet getTablesResultSet(Connection connection) {
341 try {
342 return metadata(errorService, connection).getTables(null, schema, "%", new String[]{"TABLE"});
343 } catch (SQLException e) {
344 throw errorService.newImportExportSqlException(null, "Could not read tables in data exporter", e);
345 }
346 }
347
348 private ResultSet executeQueryWithFetchSize(String table, Statement statement, String sql, int fetchSize) {
349 try {
350 statement.setFetchSize(fetchSize);
351 return statement.executeQuery(sql);
352 } catch (SQLException e) {
353 throw errorService.newImportExportSqlException(table, "Could not execute query '" + sql + "' with fetch size " + fetchSize, e);
354 }
355 }
356
357 private static boolean isBlank(String str) {
358 int strLen;
359 if (str == null || (strLen = str.length()) == 0) {
360 return true;
361 }
362 for (int i = 0; i < strLen; i++) {
363 if (!Character.isWhitespace(str.charAt(i))) {
364 return false;
365 }
366 }
367 return true;
368 }
369 }