Mercurial Hosting > nabble
comparison src/jdbcpgbackup/ZipBackup.java @ 0:7ecd1a4ef557
add content
| author | Franklin Schmidt <fschmidt@gmail.com> |
|---|---|
| date | Thu, 21 Mar 2019 19:15:52 -0600 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:7ecd1a4ef557 |
|---|---|
| 1 /* Copyright (c) 2012 Tomislav Gountchev <tomi@gountchev.net> */ | |
| 2 | |
| 3 package jdbcpgbackup; | |
| 4 | |
| 5 import java.io.BufferedOutputStream; | |
| 6 import java.io.BufferedReader; | |
| 7 import java.io.File; | |
| 8 import java.io.FileOutputStream; | |
| 9 import java.io.FileReader; | |
| 10 import java.io.IOException; | |
| 11 import java.io.InputStreamReader; | |
| 12 import java.io.PrintStream; | |
| 13 import java.sql.Connection; | |
| 14 import java.sql.DriverManager; | |
| 15 import java.sql.PreparedStatement; | |
| 16 import java.sql.SQLException; | |
| 17 import java.util.ArrayList; | |
| 18 import java.util.Collection; | |
| 19 import java.util.Collections; | |
| 20 import java.util.Date; | |
| 21 import java.util.Enumeration; | |
| 22 import java.util.HashMap; | |
| 23 import java.util.HashSet; | |
| 24 import java.util.List; | |
| 25 import java.util.Map; | |
| 26 import java.util.Set; | |
| 27 import java.util.zip.ZipEntry; | |
| 28 import java.util.zip.ZipFile; | |
| 29 import java.util.zip.ZipOutputStream; | |
| 30 | |
| 31 public final class ZipBackup { | |
| 32 | |
| 33 private static final String zipRoot = "pg_backup/"; | |
| 34 public static final int DEFAULT_BATCH_SIZE = 10000; | |
| 35 | |
| 36 private final String jdbcUrl; | |
| 37 private final File file; | |
| 38 | |
| 39 private DBOFactory<Schema> schemaFactory = new Schema.SchemaFactory(); | |
| 40 private DBOFactory<View> viewFactory = new View.ViewFactory(); | |
| 41 private DBOFactory<Table> tableFactory = new Table.TableFactory(); | |
| 42 private DBOFactory<Sequence> sequenceFactory = new Sequence.SequenceFactory(); | |
| 43 private DBOFactory<Index> indexFactory = new Index.IndexFactory(); | |
| 44 private DBOFactory<Constraint> constraintFactory = new Constraint.ConstraintFactory(); | |
| 45 | |
| 46 public ZipBackup(File file, String jdbcUrl) { | |
| 47 this.file = file; | |
| 48 this.jdbcUrl = jdbcUrl; | |
| 49 } | |
| 50 | |
| 51 public ZipBackup(Map<String,String> params) { | |
| 52 this(params.get("filename") == null ? null : new File(params.get("filename")), | |
| 53 buildJdbcUrl(params)); | |
| 54 } | |
| 55 | |
| 56 public void dumpAll(DataFilter dataFilter) { | |
| 57 dumpAll(dataFilter, DEFAULT_BATCH_SIZE); | |
| 58 } | |
| 59 | |
| 60 public void dumpAll(DataFilter dataFilter, int batchSize) { | |
| 61 debug("starting full dump at " + new Date()); | |
| 62 Schema.CachingSchemaFactory cachingSchemaFactory = new Schema.CachingSchemaFactory(); | |
| 63 schemaFactory = cachingSchemaFactory; | |
| 64 Connection con = null; | |
| 65 ZipOutputStream zos = null; | |
| 66 try { | |
| 67 zos = getZipOutputStream(); | |
| 68 con = DriverManager.getConnection(jdbcUrl); | |
| 69 con.setReadOnly(true); | |
| 70 con.setAutoCommit(true); | |
| 71 timerStart("schemas"); | |
| 72 Collection<Schema> schemas = cachingSchemaFactory.getDbBackupObjects(con, null); | |
| 73 setTotalCount(schemas.size()); | |
| 74 dumpSchemasSql(schemas, dataFilter, con, zos); | |
| 75 debug(schemas.size() + " schemas to be dumped"); | |
| 76 timerEnd("schemas"); | |
| 77 debug("begin dumping schemas"); | |
| 78 Collection<Schema> batch; | |
| 79 while (! (batch = cachingSchemaFactory.nextBatch(con, batchSize)).isEmpty()) { | |
| 80 viewFactory = new View.CachingViewFactory(cachingSchemaFactory); | |
| 81 tableFactory = new Table.CachingTableFactory(cachingSchemaFactory); | |
| 82 sequenceFactory = new Sequence.CachingSequenceFactory(cachingSchemaFactory); | |
| 83 indexFactory = new Index.CachingIndexFactory(cachingSchemaFactory, (Table.CachingTableFactory)tableFactory); | |
| 84 constraintFactory = new Constraint.CachingConstraintFactory(cachingSchemaFactory, (Table.CachingTableFactory)tableFactory); | |
| 85 for (Schema schema : batch) { | |
| 86 dump(schema, dataFilter, con, zos); | |
| 87 } | |
| 88 con.close(); | |
| 89 con = DriverManager.getConnection(jdbcUrl); | |
| 90 con.setReadOnly(true); | |
| 91 con.setAutoCommit(true); | |
| 92 } | |
| 93 printTimings(); | |
| 94 } catch (SQLException e) { | |
| 95 throw new RuntimeException(e.getMessage(), e); | |
| 96 } catch (IOException e) { | |
| 97 throw new RuntimeException(e.getMessage(), e); | |
| 98 } finally { | |
| 99 try { | |
| 100 if (con != null) con.close(); | |
| 101 } catch (SQLException ignore) {} | |
| 102 try { | |
| 103 if (zos != null) zos.close(); | |
| 104 } catch (IOException e) { | |
| 105 throw new RuntimeException(e.getMessage(), e); | |
| 106 } | |
| 107 } | |
| 108 debug("finished full dump at " + new Date()); | |
| 109 } | |
| 110 | |
| 111 public void dump(Iterable<String> schemaNames, DataFilter dataFilter) { | |
| 112 Connection con = null; | |
| 113 try { | |
| 114 con = DriverManager.getConnection(jdbcUrl); | |
| 115 con.setReadOnly(true); | |
| 116 con.setAutoCommit(false); | |
| 117 con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); | |
| 118 dump(schemaNames, dataFilter, con); | |
| 119 } catch (SQLException e) { | |
| 120 throw new RuntimeException(e.getMessage(), e); | |
| 121 } finally { | |
| 122 try { | |
| 123 if (con != null) con.close(); | |
| 124 } catch (SQLException ignore) {} | |
| 125 } | |
| 126 } | |
| 127 | |
| 128 public void dump(Iterable<String> schemaNames, DataFilter dataFilter, Connection con) { | |
| 129 ZipOutputStream zos = null; | |
| 130 try { | |
| 131 zos = getZipOutputStream(); | |
| 132 timerStart("schemas"); | |
| 133 List<Schema> schemas = new ArrayList<Schema>(); | |
| 134 for (String schemaName : schemaNames) { | |
| 135 Schema schema = schemaFactory.getDbBackupObject(con, schemaName, null); | |
| 136 if (schema == null) | |
| 137 throw new RuntimeException("schema " + schemaName + " not found in database"); | |
| 138 schemas.add(schema); | |
| 139 } | |
| 140 setTotalCount(schemas.size()); | |
| 141 dumpSchemasSql(schemas, dataFilter, con, zos); | |
| 142 timerEnd("schemas"); | |
| 143 for (Schema schema : schemas) { | |
| 144 dump(schema, dataFilter, con, zos); | |
| 145 } | |
| 146 printTimings(); | |
| 147 } catch (SQLException e) { | |
| 148 throw new RuntimeException(e.getMessage(), e); | |
| 149 } catch (IOException e) { | |
| 150 throw new RuntimeException(e.getMessage(), e); | |
| 151 } finally { | |
| 152 try { | |
| 153 if (zos != null) zos.close(); | |
| 154 } catch (IOException e) { | |
| 155 throw new RuntimeException(e.getMessage(), e); | |
| 156 } | |
| 157 } | |
| 158 } | |
| 159 | |
| 160 private ZipOutputStream getZipOutputStream() throws IOException { | |
| 161 if (file != null) { | |
| 162 if (file.length() > 0) { | |
| 163 throw new RuntimeException("destination file is not empty"); | |
| 164 } | |
| 165 return new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(file))); | |
| 166 } else { | |
| 167 return new ZipOutputStream(System.out); | |
| 168 } | |
| 169 } | |
| 170 | |
| 171 private void dumpSchemasSql(Iterable<Schema> schemas, DataFilter dataFilter, Connection con, ZipOutputStream zos) { | |
| 172 try { | |
| 173 zos.putNextEntry(new ZipEntry(zipRoot)); | |
| 174 putSqlZipEntry(zos, zipRoot+"schemas.sql", schemas, dataFilter); | |
| 175 zos.putNextEntry(new ZipEntry(zipRoot + "schemas/")); | |
| 176 } catch (IOException e) { | |
| 177 throw new RuntimeException(e.getMessage(), e); | |
| 178 } | |
| 179 } | |
| 180 | |
| 181 private void dump(Schema schema, DataFilter dataFilter, Connection con, ZipOutputStream zos) { | |
| 182 try { | |
| 183 String schemaRoot = zipRoot + "schemas/" + schema.getName() + "/"; | |
| 184 zos.putNextEntry(new ZipEntry(schemaRoot)); | |
| 185 | |
| 186 timerStart("sequences"); | |
| 187 Iterable<Sequence> sequences = sequenceFactory.getDbBackupObjects(con, schema); | |
| 188 putSqlZipEntry(zos, schemaRoot + "sequences.sql", sequences, dataFilter); | |
| 189 timerEnd("sequences"); | |
| 190 | |
| 191 Iterable<Table> tables = tableFactory.getDbBackupObjects(con, schema); | |
| 192 putSqlZipEntry(zos, schemaRoot + "tables.sql", tables, dataFilter); | |
| 193 | |
| 194 timerStart("table data"); | |
| 195 zos.putNextEntry(new ZipEntry(schemaRoot + "tables/")); | |
| 196 for (Table table : tables) { | |
| 197 if (dataFilter.dumpData(schema.getName(), table.getName())) { | |
| 198 zos.putNextEntry(new ZipEntry(schemaRoot + "tables/" + table.getName())); | |
| 199 table.dump(con, zos); | |
| 200 } | |
| 201 } | |
| 202 timerEnd("table data"); | |
| 203 | |
| 204 timerStart("views"); | |
| 205 Iterable<View> views = viewFactory.getDbBackupObjects(con, schema); | |
| 206 putSqlZipEntry(zos, schemaRoot + "views.sql", views, dataFilter); | |
| 207 timerEnd("views"); | |
| 208 | |
| 209 timerStart("indexes"); | |
| 210 Iterable<Index> indexes = indexFactory.getDbBackupObjects(con, schema); | |
| 211 putSqlZipEntry(zos, schemaRoot + "indexes.sql", indexes, dataFilter); | |
| 212 timerEnd("indexes"); | |
| 213 | |
| 214 timerStart("constraints"); | |
| 215 Iterable<Constraint> constraints = constraintFactory.getDbBackupObjects(con, schema); | |
| 216 putSqlZipEntry(zos, schemaRoot + "constraints.sql", constraints, dataFilter); | |
| 217 timerEnd("constraints"); | |
| 218 | |
| 219 processedSchema(); | |
| 220 | |
| 221 } catch (SQLException e) { | |
| 222 throw new RuntimeException("error dumping schema " + schema.getName(), e); | |
| 223 } catch (IOException e) { | |
| 224 throw new RuntimeException("error dumping schema " + schema.getName(), e); | |
| 225 } | |
| 226 } | |
| 227 | |
| 228 private void putSqlZipEntry(ZipOutputStream zos, String name, | |
| 229 Iterable<? extends DbBackupObject> dbBackupObjects, DataFilter dataFilter) throws IOException { | |
| 230 zos.putNextEntry(new ZipEntry(name)); | |
| 231 for (DbBackupObject o : dbBackupObjects) { | |
| 232 zos.write(o.getSql(dataFilter).getBytes()); | |
| 233 } | |
| 234 } | |
| 235 | |
| 236 | |
| 237 public List<String> schemasInBackup() { | |
| 238 ZipFile zipFile = null; | |
| 239 try { | |
| 240 zipFile = new ZipFile(file); | |
| 241 return new ArrayList<String>(getSchemaTables(zipFile).keySet()); | |
| 242 } catch (IOException e) { | |
| 243 throw new RuntimeException(e.getMessage(), e); | |
| 244 } finally { | |
| 245 try { | |
| 246 if (zipFile != null) zipFile.close(); | |
| 247 } catch (IOException ignore) {} | |
| 248 } | |
| 249 } | |
| 250 | |
| 251 public void restoreSchema(String schema) { | |
| 252 restoreSchemaTo(schema, schema); | |
| 253 } | |
| 254 | |
| 255 public void restoreSchemaTo(String schema, String toSchema) { | |
| 256 Connection con = null; | |
| 257 try { | |
| 258 con = DriverManager.getConnection(jdbcUrl); | |
| 259 con.setAutoCommit(false); | |
| 260 restoreSchemaTo(schema, toSchema, con); | |
| 261 con.commit(); | |
| 262 } catch (Exception e) { | |
| 263 try { | |
| 264 if (con != null) con.rollback(); | |
| 265 } catch (SQLException ignore) {} | |
| 266 throw new RuntimeException(e.getMessage(), e); | |
| 267 } finally { | |
| 268 try { | |
| 269 if (con != null) con.close(); | |
| 270 } catch (SQLException ignore) {} | |
| 271 } | |
| 272 } | |
| 273 | |
| 274 public void restoreSchemaTo(String schema, String toSchema, Connection con) { | |
| 275 ZipFile zipFile = null; | |
| 276 try { | |
| 277 zipFile = new ZipFile(file); | |
| 278 restoreSchema(schema, toSchema, toSchema, zipFile, con); | |
| 279 printTimings(); | |
| 280 } catch (IOException e) { | |
| 281 throw new RuntimeException(e.getMessage(), e); | |
| 282 } finally { | |
| 283 try { | |
| 284 if (zipFile != null) zipFile.close(); | |
| 285 } catch (IOException ignore) {} | |
| 286 } | |
| 287 } | |
| 288 | |
| 289 public void restoreAll() { | |
| 290 debug("starting full restore at " + new Date()); | |
| 291 ZipFile zipFile = null; | |
| 292 Connection con = null; | |
| 293 try { | |
| 294 con = DriverManager.getConnection(jdbcUrl); | |
| 295 con.setAutoCommit(false); | |
| 296 zipFile = new ZipFile(file); | |
| 297 | |
| 298 timerStart("schemas"); | |
| 299 restoreSchemasSql(zipFile, con); | |
| 300 List<String> schemas = schemasInBackup(); | |
| 301 setTotalCount(schemas.size()); | |
| 302 timerEnd("schemas"); | |
| 303 | |
| 304 int count = 0; | |
| 305 for (String schemaName : schemas) { | |
| 306 restoreSchema(schemaName, schemaName, schemaName, zipFile, con); | |
| 307 if (++count%100 == 1) con.commit(); // commit every 100 schemas | |
| 308 } | |
| 309 | |
| 310 con.commit(); | |
| 311 printTimings(); | |
| 312 } catch (Exception e) { | |
| 313 try { | |
| 314 if (con != null) con.rollback(); | |
| 315 } catch (SQLException ignore) {} | |
| 316 throw new RuntimeException(e.getMessage(), e); | |
| 317 } finally { | |
| 318 try { | |
| 319 if (con != null) con.close(); | |
| 320 } catch (SQLException ignore) {} | |
| 321 try { | |
| 322 if (zipFile != null) zipFile.close(); | |
| 323 } catch (IOException ignore) {} | |
| 324 } | |
| 325 debug("finished full restore at " + new Date()); | |
| 326 } | |
| 327 | |
| 328 private void restoreSchema(String fromSchemaName, String toSchemaName, String toOwner, ZipFile zipFile, Connection con) { | |
| 329 try { | |
| 330 timerStart("schemas"); | |
| 331 boolean isNewSchema = !toSchemaName.equals(fromSchemaName); | |
| 332 Schema toSchema = schemaFactory.getDbBackupObject(con, toSchemaName, null); | |
| 333 if (toSchema == null) | |
| 334 toSchema = Schema.createSchema(con, toSchemaName, toOwner, schemaFactory); | |
| 335 else | |
| 336 toOwner = toSchema.getOwner(); // preserve existing owner | |
| 337 setRole(con, toOwner); | |
| 338 setSearchPath(con, toSchema); | |
| 339 timerEnd("schemas"); | |
| 340 | |
| 341 String schemaRoot = zipRoot + "schemas/" + fromSchemaName + "/"; | |
| 342 | |
| 343 timerStart("sequences"); | |
| 344 ZipEntry sequencesSql = zipFile.getEntry(schemaRoot + "sequences.sql"); | |
| 345 execSqlZipEntry(zipFile, con, sequencesSql, isNewSchema); | |
| 346 timerEnd("sequences"); | |
| 347 | |
| 348 timerStart("tables"); | |
| 349 ZipEntry tablesSql = zipFile.getEntry(schemaRoot + "tables.sql"); | |
| 350 execSqlZipEntry(zipFile, con, tablesSql, isNewSchema); | |
| 351 timerEnd("tables"); | |
| 352 | |
| 353 timerStart("table data"); | |
| 354 Set<ZipEntry> tableEntries = getSchemaTables(zipFile).get(fromSchemaName); | |
| 355 for (ZipEntry tableEntry : tableEntries) { | |
| 356 String tableName = parseTable(tableEntry.getName()); | |
| 357 Table table = tableFactory.getDbBackupObject(con, tableName, toSchema); | |
| 358 if (!table.getOwner().equals(toOwner) && !isNewSchema) { | |
| 359 setRole(con, table.getOwner()); | |
| 360 } | |
| 361 table.restore(zipFile.getInputStream(tableEntry), con); | |
| 362 if (!table.getOwner().equals(toOwner) && !isNewSchema) { | |
| 363 setRole(con, toOwner); | |
| 364 } | |
| 365 } | |
| 366 timerEnd("table data"); | |
| 367 | |
| 368 timerStart("views"); | |
| 369 ZipEntry viewsSql = zipFile.getEntry(schemaRoot + "views.sql"); | |
| 370 execSqlZipEntry(zipFile, con, viewsSql, isNewSchema); | |
| 371 timerEnd("views"); | |
| 372 | |
| 373 timerStart("indexes"); | |
| 374 ZipEntry indexesSql = zipFile.getEntry(schemaRoot + "indexes.sql"); | |
| 375 execSqlZipEntry(zipFile, con, indexesSql, isNewSchema); | |
| 376 timerEnd("indexes"); | |
| 377 | |
| 378 timerStart("constraints"); | |
| 379 ZipEntry constraintsSql = zipFile.getEntry(schemaRoot + "constraints.sql"); | |
| 380 execSqlZipEntry(zipFile, con, constraintsSql, isNewSchema); | |
| 381 timerEnd("constraints"); | |
| 382 | |
| 383 resetSearchPath(con); | |
| 384 resetRole(con); | |
| 385 processedSchema(); | |
| 386 } catch (Exception e) { | |
| 387 throw new RuntimeException( | |
| 388 "error restoring " + fromSchemaName + | |
| 389 " to " + toSchemaName, e | |
| 390 ); | |
| 391 } | |
| 392 } | |
| 393 | |
| 394 private void restoreSchemasSql(ZipFile zipFile, Connection con) { | |
| 395 try { | |
| 396 ZipEntry schemasSql = zipFile.getEntry(zipRoot + "schemas.sql"); | |
| 397 if (schemasSql!=null) execSqlZipEntry(zipFile, con, schemasSql, false); | |
| 398 } catch (SQLException e) { | |
| 399 throw new RuntimeException(e.getMessage(), e); | |
| 400 } catch (IOException e) { | |
| 401 throw new RuntimeException(e.getMessage(), e); | |
| 402 } | |
| 403 } | |
| 404 | |
| 405 private void execSqlZipEntry(ZipFile zipFile, Connection con, ZipEntry zipEntry, boolean isNewSchema) throws IOException, SQLException { | |
| 406 BufferedReader reader = null; | |
| 407 try { | |
| 408 reader = new BufferedReader(new InputStreamReader(zipFile.getInputStream(zipEntry))); | |
| 409 for (String sql = reader.readLine(); sql != null; sql = reader.readLine()) { | |
| 410 if (isNewSchema) { // skip any role and ownership changes if restoring to new schema | |
| 411 if (sql.startsWith("SET ROLE ") || (sql.startsWith("ALTER ") && sql.contains(" OWNER TO "))) { | |
| 412 continue; | |
| 413 } | |
| 414 } | |
| 415 PreparedStatement stmt = null; | |
| 416 try { | |
| 417 stmt = con.prepareStatement(sql); | |
| 418 if (sql.startsWith("SELECT ")) { | |
| 419 stmt.executeQuery(); | |
| 420 } else { | |
| 421 stmt.executeUpdate(); | |
| 422 } | |
| 423 } catch (SQLException e) { | |
| 424 throw new RuntimeException("error executing sql: " + sql, e); | |
| 425 } finally { | |
| 426 if (stmt != null) stmt.close(); | |
| 427 } | |
| 428 } | |
| 429 } finally { | |
| 430 if (reader != null) reader.close(); | |
| 431 } | |
| 432 } | |
| 433 | |
| 434 | |
| 435 private Map<String,Set<ZipEntry>> schemaTables = null; | |
| 436 | |
| 437 private Map<String,Set<ZipEntry>> getSchemaTables(ZipFile zipFile) { | |
| 438 if (schemaTables == null) { | |
| 439 schemaTables = new HashMap<String,Set<ZipEntry>>(); | |
| 440 Enumeration<? extends ZipEntry> entries = zipFile.entries(); | |
| 441 while (entries.hasMoreElements()) { | |
| 442 ZipEntry entry = entries.nextElement(); | |
| 443 String schema = parseSchema(entry.getName()); | |
| 444 if (schema == null) continue; | |
| 445 Set<ZipEntry> tables = schemaTables.get(schema); | |
| 446 if (tables == null) { | |
| 447 tables = new HashSet<ZipEntry>(); | |
| 448 schemaTables.put(schema, tables); | |
| 449 } | |
| 450 if (isTable(entry.getName())) { | |
| 451 tables.add(entry); | |
| 452 } | |
| 453 } | |
| 454 } | |
| 455 return schemaTables; | |
| 456 } | |
| 457 | |
| 458 private void setRole(Connection con, String role) throws SQLException { | |
| 459 PreparedStatement stmt = null; | |
| 460 try { | |
| 461 stmt = con.prepareStatement("SET ROLE " + role); | |
| 462 //stmt.setString(1, role); | |
| 463 stmt.executeUpdate(); | |
| 464 } finally { | |
| 465 if (stmt != null) stmt.close(); | |
| 466 } | |
| 467 } | |
| 468 | |
| 469 private void setSearchPath(Connection con, Schema schema) throws SQLException { | |
| 470 PreparedStatement stmt = null; | |
| 471 try { | |
| 472 stmt = con.prepareStatement("SET SEARCH_PATH = " + schema.getName()); | |
| 473 //stmt.setString(1, schema.getName()); | |
| 474 stmt.executeUpdate(); | |
| 475 } finally { | |
| 476 if (stmt != null) stmt.close(); | |
| 477 } | |
| 478 } | |
| 479 | |
| 480 private void resetRole(Connection con) throws SQLException { | |
| 481 PreparedStatement stmt = null; | |
| 482 try { | |
| 483 stmt = con.prepareStatement("RESET ROLE"); | |
| 484 stmt.executeUpdate(); | |
| 485 } finally { | |
| 486 if (stmt != null) stmt.close(); | |
| 487 } | |
| 488 } | |
| 489 | |
| 490 private void resetSearchPath(Connection con) throws SQLException { | |
| 491 PreparedStatement stmt = null; | |
| 492 try { | |
| 493 stmt = con.prepareStatement("RESET SEARCH_PATH"); | |
| 494 stmt.executeUpdate(); | |
| 495 } finally { | |
| 496 if (stmt != null) stmt.close(); | |
| 497 } | |
| 498 } | |
| 499 | |
| 500 private static boolean isTable(String name) { | |
| 501 int i = name.indexOf("/tables/"); | |
| 502 return i > -1 && i < name.length() - "/tables/".length(); | |
| 503 } | |
| 504 | |
| 505 private static String parseTable(String name) { | |
| 506 int from = name.indexOf("/tables/") + "/tables/".length(); | |
| 507 return name.substring(from); | |
| 508 } | |
| 509 | |
| 510 private static String parseSchema(String name) { | |
| 511 int i = name.indexOf("/schemas/"); | |
| 512 if (i < 0) return null; | |
| 513 int from = i + "/schemas/".length(); | |
| 514 int to = name.indexOf('/', from); | |
| 515 if (to < 0) return null; | |
| 516 return name.substring(from, to); | |
| 517 } | |
| 518 | |
| 519 | |
| 520 | |
| 521 private static class Timing { | |
| 522 private final Map<String,Long> timerMap = new HashMap<String,Long>(); | |
| 523 private final long startTime = System.currentTimeMillis(); | |
| 524 private final PrintStream ps; | |
| 525 private int totalCount = 1; | |
| 526 private int processedCount; | |
| 527 private long time = -1; | |
| 528 | |
| 529 private Timing(PrintStream ps) { | |
| 530 this.ps = ps; | |
| 531 } | |
| 532 | |
| 533 void start(String step) { | |
| 534 if (time != -1) throw new RuntimeException(); | |
| 535 time = System.currentTimeMillis(); | |
| 536 } | |
| 537 | |
| 538 void end(String step) { | |
| 539 if (time == -1) throw new RuntimeException(); | |
| 540 long thisTime = System.currentTimeMillis() - time; | |
| 541 Long oldTotal = timerMap.get(step); | |
| 542 if (oldTotal != null) thisTime += oldTotal.longValue(); | |
| 543 timerMap.put(step, new Long(thisTime)); | |
| 544 time = -1; | |
| 545 } | |
| 546 | |
| 547 void processedSchema() { | |
| 548 if (++processedCount % 100 == 1) print(); | |
| 549 } | |
| 550 | |
| 551 void print() { | |
| 552 long totalTime = System.currentTimeMillis() - startTime; | |
| 553 long remaining = totalTime; | |
| 554 for (Map.Entry<String,Long> entry : timerMap.entrySet()) { | |
| 555 long t = entry.getValue(); | |
| 556 remaining = remaining - t; | |
| 557 ps.print(entry.getKey() + ": \t"); | |
| 558 ps.println(t/1000 + " s \t" + t*100/totalTime + " %"); | |
| 559 } | |
| 560 ps.println("others: \t" + remaining/1000 + " s \t" + remaining*100/totalTime + " %"); | |
| 561 ps.println("total time: \t" + totalTime/1000 + " s"); | |
| 562 ps.println("processed: \t" + processedCount + " out of " + totalCount + | |
| 563 " schemas \t" + processedCount*100/totalCount + "%"); | |
| 564 ps.println(); | |
| 565 } | |
| 566 | |
| 567 void print(String msg) { | |
| 568 ps.println(msg); | |
| 569 } | |
| 570 | |
| 571 } | |
| 572 | |
| 573 static final ThreadLocal<Timing> timing = new ThreadLocal<Timing>() { | |
| 574 @Override | |
| 575 protected Timing initialValue() { | |
| 576 return new Timing(null) { | |
| 577 void start(String step) {} | |
| 578 void end(String step) {} | |
| 579 void processedSchema() {} | |
| 580 void print() {} | |
| 581 void print(String msg) {} | |
| 582 }; | |
| 583 } | |
| 584 }; | |
| 585 | |
| 586 public static void setTimingOutput(PrintStream ps) { | |
| 587 timing.set(new Timing(ps)); | |
| 588 } | |
| 589 | |
| 590 static void timerStart(String step) { | |
| 591 timing.get().start(step); | |
| 592 } | |
| 593 | |
| 594 static void timerEnd(String step) { | |
| 595 timing.get().end(step); | |
| 596 } | |
| 597 | |
| 598 static void setTotalCount(int n) { | |
| 599 timing.get().totalCount = n; | |
| 600 } | |
| 601 | |
| 602 static void processedSchema() { | |
| 603 timing.get().processedSchema(); | |
| 604 } | |
| 605 | |
| 606 static void printTimings() { | |
| 607 timing.get().print(); | |
| 608 } | |
| 609 | |
| 610 static void debug(String msg) { | |
| 611 timing.get().print("at " + (System.currentTimeMillis() - timing.get().startTime)/1000 + " s:"); | |
| 612 timing.get().print(msg); | |
| 613 } | |
| 614 | |
| 615 static String buildJdbcUrl(Map<String,String> params) { | |
| 616 StringBuilder buf = new StringBuilder(); | |
| 617 buf.append("jdbc:postgresql://"); | |
| 618 String hostname = params.get("hostname"); | |
| 619 if (hostname == null) hostname = "localhost"; | |
| 620 buf.append(hostname); | |
| 621 String port = params.get("port"); | |
| 622 if (port == null) port = "5432"; | |
| 623 buf.append(":").append(port); | |
| 624 buf.append("/"); | |
| 625 String username = params.get("user"); | |
| 626 if (username == null) username = "postgres"; | |
| 627 String database = params.get("database"); | |
| 628 if (database == null) database = username; | |
| 629 buf.append(database); | |
| 630 buf.append("?user="); | |
| 631 buf.append(username); | |
| 632 String password = params.get("password"); | |
| 633 if (password != null) buf.append("&password=").append(password); | |
| 634 else { | |
| 635 File pgpass = new File(System.getProperty("user.home"), ".pgpass"); | |
| 636 if (pgpass.exists()) { | |
| 637 BufferedReader r = null; | |
| 638 try { | |
| 639 r = new BufferedReader(new FileReader(pgpass)); | |
| 640 String line; | |
| 641 while ((line = r.readLine()) != null) { | |
| 642 if (line.startsWith("#")) continue; | |
| 643 String[] entries = line.split(":"); | |
| 644 if (entries.length != 5) throw new RuntimeException( | |
| 645 "unsupported pgpass file format, better specify password on command line"); | |
| 646 if (! (hostname.equals(entries[0]) || "*".equals(entries[0])) ) continue; | |
| 647 if (! (port.equals(entries[1]) || "*".equals(entries[1])) ) continue; | |
| 648 if (! (database.equals(entries[2]) || "*".equals(entries[2])) ) continue; | |
| 649 if (! (username.equals(entries[3]) || "*".equals(entries[3])) ) continue; | |
| 650 buf.append("&password=").append(entries[4]); | |
| 651 break; | |
| 652 } | |
| 653 } catch (IOException e) { | |
| 654 throw new RuntimeException("failed to read the pgpass file"); | |
| 655 } finally { | |
| 656 try { | |
| 657 if (r != null) r.close(); | |
| 658 } catch (IOException ignore) {} | |
| 659 } | |
| 660 } | |
| 661 } | |
| 662 return buf.toString(); | |
| 663 } | |
| 664 | |
| 665 | |
| 666 | |
| 667 // new - fschmidt | |
| 668 | |
| 669 private Set<String> getEntry(ZipFile zipFile, String entryName) | |
| 670 throws IOException, SQLException | |
| 671 { | |
| 672 Set<String> set = new HashSet<String>(); | |
| 673 ZipEntry zipEntry = zipFile.getEntry(entryName); | |
| 674 BufferedReader reader = new BufferedReader(new InputStreamReader(zipFile.getInputStream(zipEntry))); | |
| 675 for (String sql = reader.readLine(); sql != null; sql = reader.readLine()) { | |
| 676 // skip any role and ownership changes if restoring to new schema | |
| 677 if (sql.startsWith("SET ROLE ") || (sql.startsWith("ALTER ") && sql.contains(" OWNER TO "))) { | |
| 678 continue; | |
| 679 } | |
| 680 set.add(sql); | |
| 681 } | |
| 682 reader.close(); | |
| 683 return set; | |
| 684 } | |
| 685 | |
| 686 private boolean matches(ZipFile zipFile1,String schemaRoot1,ZipFile zipFile2,String schemaRoot2,String what) | |
| 687 throws IOException, SQLException | |
| 688 { | |
| 689 Set<String> entry1 = getEntry( zipFile1, schemaRoot1 + what ); | |
| 690 Set<String> entry2 = getEntry( zipFile2, schemaRoot2 + what ); | |
| 691 /* | |
| 692 if( !entry1.equals(entry2) ) { | |
| 693 System.out.println("qqqqqqqqqqqqqqqqqqqqqq "+what); | |
| 694 System.out.println(entry1); | |
| 695 System.out.println(entry2); | |
| 696 } | |
| 697 */ | |
| 698 return entry1.equals(entry2); | |
| 699 } | |
| 700 | |
| 701 public boolean compareTo(String schema) { | |
| 702 try { | |
| 703 List<String> schemasInBackup = schemasInBackup(); | |
| 704 if( schemasInBackup.size() != 1 ) | |
| 705 throw new RuntimeException("must have 1 schema"); | |
| 706 String backupSchema = schemasInBackup.get(0); | |
| 707 | |
| 708 File tmpFile = File.createTempFile("jdbcpgbackup",null); | |
| 709 tmpFile.deleteOnExit(); | |
| 710 ZipBackup backup = new ZipBackup( tmpFile, jdbcUrl ); | |
| 711 backup.dump( Collections.singleton(schema), DataFilter.NO_DATA ); | |
| 712 | |
| 713 ZipFile zipFile1 = new ZipFile(file); | |
| 714 ZipFile zipFile2 = new ZipFile(tmpFile); | |
| 715 String schemaRoot1 = zipRoot + "schemas/" + backupSchema + "/"; | |
| 716 String schemaRoot2 = zipRoot + "schemas/" + schema + "/"; | |
| 717 | |
| 718 try { | |
| 719 if( !matches(zipFile1,schemaRoot1,zipFile2,schemaRoot2,"sequences.sql") ) | |
| 720 return false; | |
| 721 if( !matches(zipFile1,schemaRoot1,zipFile2,schemaRoot2,"tables.sql") ) | |
| 722 return false; | |
| 723 if( !matches(zipFile1,schemaRoot1,zipFile2,schemaRoot2,"views.sql") ) | |
| 724 return false; | |
| 725 if( !matches(zipFile1,schemaRoot1,zipFile2,schemaRoot2,"indexes.sql") ) | |
| 726 return false; | |
| 727 if( !matches(zipFile1,schemaRoot1,zipFile2,schemaRoot2,"constraints.sql") ) | |
| 728 return false; | |
| 729 return true; | |
| 730 } finally { | |
| 731 tmpFile.delete(); | |
| 732 } | |
| 733 } catch (IOException e) { | |
| 734 throw new RuntimeException(e); | |
| 735 } catch (SQLException e) { | |
| 736 throw new RuntimeException(e); | |
| 737 } | |
| 738 } | |
| 739 } |
