     }
     
     /// Returns the set of applied migration identifiers.
+    ///
+    /// - parameter reader: A DatabaseReader (DatabaseQueue or DatabasePool).
+    /// - parameter targetIdentifier: The identifier of a registered migration.
+    /// - throws: An eventual database error.
     public func appliedMigrations(in reader: DatabaseReader) throws -> Set<String> {
         return try reader.read { db in
             return try appliedIdentifiers(db)
         }
     }
     
+    /// Returns true if there exist unapplied migrations.
+    ///
+    /// - parameter reader: A DatabaseReader (DatabaseQueue or DatabasePool).
+    /// - throws: An eventual database error.
+    public func hasUnappliedMigrations(in reader: DatabaseReader) throws -> Bool {
+        guard let lastMigration = migrations.last else {
+            return false
+        }
+        return try hasUnappliedMigrations(in: reader, upTo: lastMigration.identifier)
+    }
+    
+    /// Returns true if there exist unapplied migrations, up to the
+    /// provided target.
+    ///
+    /// - parameter reader: A DatabaseReader (DatabaseQueue or DatabasePool).
+    /// - parameter targetIdentifier: The identifier of a registered migration.
+    /// - throws: An eventual database error.
+    public func hasUnappliedMigrations(in reader: DatabaseReader, upTo targetIdentifier: String) throws -> Bool {
+        return try reader.read { db in
+            let appliedIdentifiers = try self.appliedIdentifiers(db)
+            let unappliedMigrations = self.unappliedMigrations(
+                upTo: targetIdentifier,
+                appliedIdentifiers: appliedIdentifiers)
+            return unappliedMigrations.isEmpty == false
+        }
+    }
     
     // MARK: - Non public
     

        
        // targetIdentifier must refer to a registered migration
        GRDBPrecondition(
            prefixMigrations.last?.identifier == targetIdentifier,
            "undefined migration: \(String(reflecting: targetIdentifier))")
        
        // Subsequent migration must not be applied
        let appliedIdentifiers = try self.appliedIdentifiers(db)
        if prefixMigrations.count < migrations.count {
            let nextIdentifier = migrations[prefixMigrations.count].identifier
            GRDBPrecondition(
                !appliedIdentifiers.contains(nextIdentifier),
                "database is already migrated beyond migration \(String(reflecting: targetIdentifier))")
        }
        
        for migration in prefixMigrations where !appliedIdentifiers.contains(migration.identifier) {
            try migration.run(db)
        }
    }

    /// provided target. If a migration has not yet been applied, its block is
    /// executed in a transaction.
    ///
    /// - parameter writer: A DatabaseWriter (DatabaseQueue or DatabasePool)
    ///   where migrations should apply.
    /// - parameter targetIdentifier: The identifier of a registered migration.
    /// - throws: An eventual error thrown by the registered migration blocks.
    public func migrate(_ writer: DatabaseWriter, upTo targetIdentifier: String) throws {
        if eraseDatabaseOnSchemaChange {
