1
16 package org.flywaydb.core;
17
18 import org.flywaydb.core.api.FlywayException;
19 import org.flywaydb.core.api.MigrationInfoService;
20 import org.flywaydb.core.api.callback.Callback;
21 import org.flywaydb.core.api.configuration.ClassicConfiguration;
22 import org.flywaydb.core.api.configuration.Configuration;
23 import org.flywaydb.core.api.configuration.FluentConfiguration;
24 import org.flywaydb.core.api.logging.Log;
25 import org.flywaydb.core.api.logging.LogFactory;
26 import org.flywaydb.core.api.migration.JavaMigration;
27 import org.flywaydb.core.api.resolver.MigrationResolver;
28 import org.flywaydb.core.internal.callback.*;
29 import org.flywaydb.core.internal.clazz.ClassProvider;
30 import org.flywaydb.core.internal.clazz.NoopClassProvider;
31 import org.flywaydb.core.internal.command.*;
32 import org.flywaydb.core.internal.configuration.ConfigurationValidator;
33 import org.flywaydb.core.internal.database.DatabaseFactory;
34 import org.flywaydb.core.internal.database.base.Database;
35 import org.flywaydb.core.internal.database.base.Schema;
36 import org.flywaydb.core.internal.jdbc.JdbcConnectionFactory;
37 import org.flywaydb.core.internal.license.VersionPrinter;
38 import org.flywaydb.core.internal.parser.ParsingContext;
39 import org.flywaydb.core.internal.resolver.CompositeMigrationResolver;
40 import org.flywaydb.core.internal.resource.NoopResourceProvider;
41 import org.flywaydb.core.internal.resource.ResourceProvider;
42 import org.flywaydb.core.internal.resource.StringResource;
43 import org.flywaydb.core.internal.resource.ResourceNameValidator;
44 import org.flywaydb.core.internal.scanner.LocationScannerCache;
45 import org.flywaydb.core.internal.scanner.ResourceNameCache;
46 import org.flywaydb.core.internal.scanner.Scanner;
47 import org.flywaydb.core.internal.scanner.classpath.ClassPathLocationScanner;
48 import org.flywaydb.core.internal.schemahistory.SchemaHistory;
49 import org.flywaydb.core.internal.schemahistory.SchemaHistoryFactory;
50 import org.flywaydb.core.internal.sqlscript.SqlScript;
51 import org.flywaydb.core.internal.sqlscript.SqlScriptExecutorFactory;
52 import org.flywaydb.core.internal.sqlscript.SqlScriptFactory;
53 import org.flywaydb.core.internal.util.IOUtils;
54 import org.flywaydb.core.internal.util.Pair;
55 import org.flywaydb.core.internal.util.StringUtils;
56
57 import java.sql.Connection;
58 import java.util.*;
59
60
74 public class Flyway {
75 private static final Log LOG = LogFactory.getLog(Flyway.class);
76
77 private final ClassicConfiguration configuration;
78
79
82 private boolean dbConnectionInfoPrinted;
83
84
87 private ConfigurationValidator configurationValidator = new ConfigurationValidator();
88
89
92 private ResourceNameValidator resourceNameValidator = new ResourceNameValidator();
93
94
104 public static FluentConfiguration configure() {
105 return new FluentConfiguration();
106 }
107
108
119 public static FluentConfiguration configure(ClassLoader classLoader) {
120 return new FluentConfiguration(classLoader);
121 }
122
123
129 public Flyway(Configuration configuration) {
130 this.configuration = new ClassicConfiguration(configuration);
131 }
132
133
136 public Configuration getConfiguration() {
137 return new ClassicConfiguration(configuration);
138 }
139
140
143 private ResourceNameCache resourceNameCache = new ResourceNameCache();
144
145
148 private final LocationScannerCache locationScannerCache = new LocationScannerCache();
149
150
158 public int migrate() throws FlywayException {
159 return execute(new Command<Integer>() {
160 public Integer execute(MigrationResolver migrationResolver,
161 SchemaHistory schemaHistory, Database database, Schema[] schemas, CallbackExecutor callbackExecutor
162
163
164
165 ) {
166 if (configuration.isValidateOnMigrate()) {
167 doValidate(database, migrationResolver, schemaHistory, schemas, callbackExecutor,
168 true
169 );
170 }
171
172 if (!schemaHistory.exists()) {
173 List<Schema> nonEmptySchemas = new ArrayList<>();
174 for (Schema schema : schemas) {
175 if (schema.exists() && !schema.empty()) {
176 nonEmptySchemas.add(schema);
177 }
178 }
179
180 if (!nonEmptySchemas.isEmpty()) {
181 if (configuration.isBaselineOnMigrate()) {
182 doBaseline(schemaHistory, callbackExecutor);
183 } else {
184
185 if (!schemaHistory.exists()) {
186 throw new FlywayException("Found non-empty schema(s) "
187 + StringUtils.collectionToCommaDelimitedString(nonEmptySchemas)
188 + " but no schema history table. Use baseline()"
189 + " or set baselineOnMigrate to true to initialize the schema history table.");
190 }
191 }
192 } else {
193 new DbSchemas(database, schemas, schemaHistory).create(false);
194 schemaHistory.create(false);
195 }
196 }
197
198 return new DbMigrate(database, schemaHistory, schemas[0], migrationResolver, configuration,
199 callbackExecutor).migrate();
200 }
201 }, true);
202 }
203
204 private void doBaseline(SchemaHistory schemaHistory, CallbackExecutor callbackExecutor) {
205 new DbBaseline(schemaHistory, configuration.getBaselineVersion(), configuration.getBaselineDescription(),
206 callbackExecutor).baseline();
207 }
208
209
219 public int undo() throws FlywayException {
220
221 throw new org.flywaydb.core.internal.license.FlywayProUpgradeRequiredException("undo");
222
223
224
225
226
227
228
229
230
231
232
233
234 }
235
236
250 public void validate() throws FlywayException {
251 execute(new Command<Void>() {
252 public Void execute(MigrationResolver migrationResolver, SchemaHistory schemaHistory, Database database,
253 Schema[] schemas, CallbackExecutor callbackExecutor
254
255
256
257 ) {
258 doValidate(database, migrationResolver, schemaHistory, schemas, callbackExecutor,
259 configuration.isIgnorePendingMigrations());
260 return null;
261 }
262 }, true);
263 }
264
265
275 private void doValidate(Database database, MigrationResolver migrationResolver, SchemaHistory schemaHistory,
276 Schema[] schemas, CallbackExecutor callbackExecutor, boolean ignorePending) {
277 String validationError =
278 new DbValidate(database, schemaHistory, schemas[0], migrationResolver,
279 configuration, ignorePending, callbackExecutor).validate();
280
281 if (validationError != null) {
282 if (configuration.isCleanOnValidationError()) {
283 doClean(database, schemaHistory, schemas, callbackExecutor);
284 } else {
285 throw new FlywayException("Validate failed: " + validationError);
286 }
287 }
288 }
289
290 private void doClean(Database database, SchemaHistory schemaHistory, Schema[] schemas, CallbackExecutor callbackExecutor) {
291 new DbClean(database, schemaHistory, schemas, callbackExecutor, configuration.isCleanDisabled()).clean();
292 }
293
294
301 public void clean() {
302 execute(new Command<Void>() {
303 public Void execute(MigrationResolver migrationResolver, SchemaHistory schemaHistory, Database database,
304 Schema[] schemas, CallbackExecutor callbackExecutor
305
306
307
308 ) {
309 doClean(database, schemaHistory, schemas, callbackExecutor);
310 return null;
311 }
312 }, false);
313 }
314
315
323 public MigrationInfoService info() {
324 return execute(new Command<MigrationInfoService>() {
325 public MigrationInfoService execute(MigrationResolver migrationResolver, SchemaHistory schemaHistory,
326 final Database database, final Schema[] schemas, CallbackExecutor callbackExecutor
327
328
329
330 ) {
331 return new DbInfo(migrationResolver, schemaHistory, configuration, callbackExecutor).info();
332 }
333 }, true);
334 }
335
336
343 public void baseline() throws FlywayException {
344 execute(new Command<Void>() {
345 public Void execute(MigrationResolver migrationResolver,
346 SchemaHistory schemaHistory, Database database, Schema[] schemas, CallbackExecutor callbackExecutor
347
348
349
350 ) {
351 new DbSchemas(database, schemas, schemaHistory).create(true);
352 doBaseline(schemaHistory, callbackExecutor);
353 return null;
354 }
355 }, false);
356 }
357
358
368 public void repair() throws FlywayException {
369 execute(new Command<Void>() {
370 public Void execute(MigrationResolver migrationResolver,
371 SchemaHistory schemaHistory, Database database, Schema[] schemas, CallbackExecutor callbackExecutor
372
373
374
375 ) {
376 new DbRepair(database, migrationResolver, schemaHistory, callbackExecutor, configuration).repair();
377 return null;
378 }
379 }, true);
380 }
381
382
391 private MigrationResolver createMigrationResolver(ResourceProvider resourceProvider,
392 ClassProvider<JavaMigration> classProvider,
393 SqlScriptExecutorFactory sqlScriptExecutorFactory,
394 SqlScriptFactory sqlScriptFactory,
395 ParsingContext parsingContext) {
396 return new CompositeMigrationResolver(resourceProvider, classProvider, configuration,
397 sqlScriptExecutorFactory, sqlScriptFactory, parsingContext, configuration.getResolvers());
398 }
399
400
407 <T> T execute(Command<T> command, boolean scannerRequired) {
408 T result;
409
410 VersionPrinter.printVersion(
411
412
413
414 );
415
416 configurationValidator.validate(configuration);
417
418
419
420
421
422
423
424
425
426
427
428
429 final ResourceProvider resourceProvider;
430 ClassProvider<JavaMigration> classProvider;
431 if (!scannerRequired && configuration.isSkipDefaultResolvers() && configuration.isSkipDefaultCallbacks()) {
432 resourceProvider = NoopResourceProvider.INSTANCE;
433
434 classProvider = NoopClassProvider.INSTANCE;
435 } else {
436 Scanner<JavaMigration> scanner = new Scanner<>(
437 JavaMigration.class,
438 Arrays.asList(configuration.getLocations()),
439 configuration.getClassLoader(),
440 configuration.getEncoding()
441
442
443
444 , resourceNameCache
445 , locationScannerCache
446 );
447 resourceProvider = scanner;
448 classProvider = scanner;
449 }
450
451 if (configuration.isValidateMigrationNaming()) {
452 resourceNameValidator.validateSQLMigrationNaming(resourceProvider, configuration);
453 }
454
455 JdbcConnectionFactory jdbcConnectionFactory = new JdbcConnectionFactory(configuration.getDataSource(),
456 configuration.getConnectRetries()
457
458
459
460
461 );
462
463 final ParsingContext parsingContext = new ParsingContext();
464 final SqlScriptFactory sqlScriptFactory =
465 DatabaseFactory.createSqlScriptFactory(jdbcConnectionFactory, configuration, parsingContext);
466
467 final SqlScriptExecutorFactory noCallbackSqlScriptExecutorFactory = DatabaseFactory.createSqlScriptExecutorFactory(
468 jdbcConnectionFactory
469
470
471
472
473 );
474
475 jdbcConnectionFactory.setConnectionInitializer(new JdbcConnectionFactory.ConnectionInitializer() {
476 @Override
477 public void initialize(JdbcConnectionFactory jdbcConnectionFactory, Connection connection) {
478 if (configuration.getInitSql() == null) {
479 return;
480 }
481 StringResource resource = new StringResource(configuration.getInitSql());
482
483 SqlScript sqlScript = sqlScriptFactory.createSqlScript(resource, true, resourceProvider);
484 noCallbackSqlScriptExecutorFactory.createSqlScriptExecutor(connection
485
486
487
488 ).execute(sqlScript);
489 }
490 });
491
492 Database database = null;
493 try {
494 database = DatabaseFactory.createDatabase(configuration, !dbConnectionInfoPrinted, jdbcConnectionFactory
495
496
497
498 );
499
500 dbConnectionInfoPrinted = true;
501 LOG.debug("DDL Transactions Supported: " + database.supportsDdlTransactions());
502
503 Pair<Schema, List<Schema>> schemas = prepareSchemas(database);
504 Schema defaultSchema = schemas.getLeft();
505
506
507
508
509
510
511
512 parsingContext.populate(database, configuration);
513
514 database.ensureSupported();
515
516 DefaultCallbackExecutor callbackExecutor = new DefaultCallbackExecutor(configuration, database, defaultSchema,
517 prepareCallbacks(database, resourceProvider, jdbcConnectionFactory, sqlScriptFactory
518
519
520
521 ));
522
523 SqlScriptExecutorFactory sqlScriptExecutorFactory = DatabaseFactory.createSqlScriptExecutorFactory(jdbcConnectionFactory
524
525
526
527
528 );
529
530 result = command.execute(
531 createMigrationResolver(resourceProvider, classProvider, sqlScriptExecutorFactory, sqlScriptFactory, parsingContext),
532 SchemaHistoryFactory.getSchemaHistory(configuration, noCallbackSqlScriptExecutorFactory, sqlScriptFactory,
533 database, defaultSchema
534
535
536
537 ),
538 database,
539 schemas.getRight().toArray(new Schema[0]),
540 callbackExecutor
541
542
543
544 );
545 } finally {
546 IOUtils.close(database);
547
548
549
550 showMemoryUsage();
551 }
552 return result;
553 }
554
555 private void showMemoryUsage() {
556 Runtime runtime = Runtime.getRuntime();
557 long free = runtime.freeMemory();
558 long total = runtime.totalMemory();
559 long used = total - free;
560
561 long totalMB = total / (1024 * 1024);
562 long usedMB = used / (1024 * 1024);
563 LOG.debug("Memory usage: " + usedMB + " of " + totalMB + "M");
564 }
565
566 private Pair<Schema, List<Schema>> prepareSchemas(Database database) {
567 String defaultSchemaName = configuration.getDefaultSchema();
568 String[] schemaNames = configuration.getSchemas();
569
570 if (!isDefaultSchemaValid(defaultSchemaName, schemaNames)) {
571 throw new FlywayException("The defaultSchema property is specified but is not a member of the schemas property");
572 }
573
574 LOG.debug("Schemas: " + StringUtils.arrayToCommaDelimitedString(schemaNames));
575 LOG.debug("Default schema: " + defaultSchemaName);
576
577 List<Schema> schemas = new ArrayList<>();
578
579 if (schemaNames.length == 0) {
580 Schema currentSchema = database.getMainConnection().getCurrentSchema();
581 if (currentSchema == null) {
582 throw new FlywayException("Unable to determine schema for the schema history table." +
583 " Set a default schema for the connection or specify one using the defaultSchema property!");
584 }
585 schemas.add(currentSchema);
586 } else {
587 for (String schemaName : schemaNames) {
588 schemas.add(database.getMainConnection().getSchema(schemaName));
589 }
590 }
591
592 if (defaultSchemaName == null && schemaNames.length > 0) {
593 defaultSchemaName = schemaNames[0];
594 }
595
596 Schema defaultSchema = (defaultSchemaName != null)
597 ? database.getMainConnection().getSchema(defaultSchemaName)
598 : database.getMainConnection().getCurrentSchema();
599
600 return Pair.of(defaultSchema, schemas);
601 }
602
603 private boolean isDefaultSchemaValid(String defaultSchema, String[] schemas) {
604
605 if (defaultSchema == null) {
606 return true;
607 }
608
609 for (String schema : schemas) {
610 if (defaultSchema.equals(schema)) {
611 return true;
612 }
613 }
614 return false;
615 }
616
617 private List<Callback> prepareCallbacks(Database database, ResourceProvider resourceProvider,
618 JdbcConnectionFactory jdbcConnectionFactory,
619 SqlScriptFactory sqlScriptFactory
620
621
622
623
624 ) {
625 List<Callback> effectiveCallbacks = new ArrayList<>();
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644 effectiveCallbacks.addAll(Arrays.asList(configuration.getCallbacks()));
645
646 if (!configuration.isSkipDefaultCallbacks()) {
647 SqlScriptExecutorFactory sqlScriptExecutorFactory =
648 DatabaseFactory.createSqlScriptExecutorFactory(jdbcConnectionFactory
649
650
651
652
653 );
654
655 effectiveCallbacks.addAll(
656 new SqlScriptCallbackFactory(
657 resourceProvider,
658 sqlScriptExecutorFactory,
659 sqlScriptFactory,
660 configuration
661 ).getCallbacks());
662 }
663
664
665
666
667
668 return effectiveCallbacks;
669 }
670
671
676 interface Command<T> {
677
687 T execute(MigrationResolver migrationResolver, SchemaHistory schemaHistory,
688 Database database, Schema[] schemas, CallbackExecutor callbackExecutor
689
690
691
692 );
693 }
694 }