Skip to content
This repository has been archived by the owner on Sep 2, 2022. It is now read-only.

[1.0] Migration timestamps #1613

Merged
merged 1 commit into from
Jan 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,12 @@ package cool.graph.deploy.database.persistence

import cool.graph.deploy.database.tables.{Migration, Project}
import cool.graph.shared.models
import cool.graph.shared.models.{MigrationStep, Schema, Function}
import cool.graph.shared.models.{MigrationStep, Schema}

object DbToModelMapper {
import cool.graph.shared.models.MigrationStepsJsonFormatter._
import cool.graph.shared.models.ProjectJsonFormatter._

// def convert(migration: Migration): models.Project = {
// val projectModel = migration.schema.as[models.Project]
// projectModel.copy(revision = migration.revision)
// }

// def convert(project: Project, migration: Migration): models.Project = {
// val projectModel = migration.schema.as[models.Project]
// projectModel.copy(revision = migration.revision)
// }

def convert(project: Project, migration: Migration): models.Project = {
models.Project(
id = project.id,
Expand All @@ -42,7 +32,9 @@ object DbToModelMapper {
applied = migration.applied,
rolledBack = migration.rolledBack,
steps = migration.steps.as[Vector[MigrationStep]],
errors = migration.errors.as[Vector[String]]
errors = migration.errors.as[Vector[String]],
startedAt = migration.startedAt,
finishedAt = migration.finishedAt
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cool.graph.deploy.database.persistence

import cool.graph.shared.models.{Migration, MigrationId}
import cool.graph.shared.models.MigrationStatus.MigrationStatus
import org.joda.time.DateTime

import scala.concurrent.Future

Expand All @@ -16,6 +17,8 @@ trait MigrationPersistence {
def updateMigrationErrors(id: MigrationId, errors: Vector[String]): Future[Unit]
def updateMigrationApplied(id: MigrationId, applied: Int): Future[Unit]
def updateMigrationRolledBack(id: MigrationId, rolledBack: Int): Future[Unit]
def updateStartedAt(id: MigrationId, startedAt: DateTime): Future[Unit]
def updateFinishedAt(id: MigrationId, finishedAt: DateTime): Future[Unit]

def loadDistinctUnmigratedProjectIds(): Future[Seq[String]]
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import cool.graph.deploy.database.tables.{MigrationTable, Tables}
import cool.graph.shared.models.{Migration, MigrationId}
import cool.graph.shared.models.MigrationStatus.MigrationStatus
import cool.graph.utils.future.FutureUtils.FutureOpt
import org.joda.time.DateTime
import play.api.libs.json.Json
import slick.jdbc.MySQLProfile.api._
import slick.jdbc.MySQLProfile.backend.DatabaseDef
Expand Down Expand Up @@ -62,6 +63,14 @@ case class MigrationPersistenceImpl(
internalDatabase.run(MigrationTable.updateMigrationRolledBack(id.projectId, id.revision, rolledBack)).map(_ => ())
}

override def updateStartedAt(id: MigrationId, startedAt: DateTime): Future[Unit] = {
internalDatabase.run(MigrationTable.updateStartedAt(id.projectId, id.revision, startedAt)).map(_ => ())
}

override def updateFinishedAt(id: MigrationId, finishedAt: DateTime): Future[Unit] = {
internalDatabase.run(MigrationTable.updateFinishedAt(id.projectId, id.revision, finishedAt)).map(_ => ())
}

override def getLastMigration(projectId: String): Future[Option[Migration]] = {
FutureOpt(internalDatabase.run(MigrationTable.lastSuccessfulMigration(projectId))).map(DbToModelMapper.convert).future
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ object ModelToDbMapper {
applied = migration.applied,
rolledBack = migration.rolledBack,
steps = migrationStepsJson,
errors = errorsJson
errors = errorsJson,
startedAt = migration.startedAt,
finishedAt = migration.finishedAt
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ object InternalDatabaseSchema {
`rolledBack` int NOT NULL default 0,
`steps` mediumtext COLLATE utf8_unicode_ci DEFAULT NULL,
`errors` mediumtext COLLATE utf8_unicode_ci DEFAULT NULL,
`startedAt` datetime DEFAULT NULL,
`finishedAt` datetime DEFAULT NULL,
PRIMARY KEY (`projectId`, `revision`),
CONSTRAINT `migrations_projectid_foreign` FOREIGN KEY (`projectId`) REFERENCES `Project` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;""",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package cool.graph.deploy.database.tables

import com.github.tototoshi.slick.MySQLJodaSupport
import cool.graph.shared.models.MigrationStatus
import cool.graph.shared.models.MigrationStatus.MigrationStatus
import org.joda.time.DateTime
import play.api.libs.json.JsValue
import slick.dbio.Effect.{Read, Write}
import slick.jdbc.MySQLProfile.api._
Expand All @@ -16,12 +18,15 @@ case class Migration(
applied: Int,
rolledBack: Int,
steps: JsValue,
errors: JsValue
errors: JsValue,
startedAt: Option[DateTime],
finishedAt: Option[DateTime]
)

class MigrationTable(tag: Tag) extends Table[Migration](tag, "Migration") {
implicit val statusMapper = MigrationTable.statusMapper
implicit val jsonMapper = MigrationTable.jsonMapper
implicit val jodaMapper = MySQLJodaSupport.datetimeTypeMapper

def projectId = column[String]("projectId")
def revision = column[Int]("revision")
Expand All @@ -32,13 +37,16 @@ class MigrationTable(tag: Tag) extends Table[Migration](tag, "Migration") {
def rolledBack = column[Int]("rolledBack")
def steps = column[JsValue]("steps")
def errors = column[JsValue]("errors")
def startedAt = column[Option[DateTime]]("startedAt")
def finishedAt = column[Option[DateTime]]("finishedAt")

def migration = foreignKey("migrations_projectid_foreign", projectId, Tables.Projects)(_.id)
def * = (projectId, revision, schema, functions, status, applied, rolledBack, steps, errors) <> (Migration.tupled, Migration.unapply)
def * = (projectId, revision, schema, functions, status, applied, rolledBack, steps, errors, startedAt, finishedAt) <> (Migration.tupled, Migration.unapply)
}

object MigrationTable {
implicit val jsonMapper = MappedColumns.jsonMapper
implicit val jodaMapper = MySQLJodaSupport.datetimeTypeMapper
implicit val statusMapper = MappedColumnType.base[MigrationStatus, String](
_.toString,
MigrationStatus.withName
Expand Down Expand Up @@ -100,6 +108,14 @@ object MigrationTable {
updateBaseQuery(projectId, revision).map(_.rolledBack).update(rolledBack)
}

def updateStartedAt(projectId: String, revision: Int, startedAt: DateTime) = {
updateBaseQuery(projectId, revision).map(_.startedAt).update(Some(startedAt))
}

def updateFinishedAt(projectId: String, revision: Int, finishedAt: DateTime) = {
updateBaseQuery(projectId, revision).map(_.finishedAt).update(Some(finishedAt))
}

def loadByRevision(projectId: String, revision: Int): SqlAction[Option[Migration], NoStream, Read] = {
val baseQuery = for {
migration <- Tables.Migrations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import cool.graph.deploy.migration.MigrationStepMapper
import cool.graph.deploy.migration.mutactions.ClientSqlMutaction
import cool.graph.shared.models.{Migration, MigrationStatus, MigrationStep, Schema}
import cool.graph.utils.exceptions.StackTraceUtils
import org.joda.time.DateTime
import slick.jdbc.MySQLProfile.backend.DatabaseDef

import scala.concurrent.{ExecutionContext, Future}
Expand All @@ -27,7 +28,9 @@ case class MigrationApplierImpl(
_ <- Future.unit
nextState = if (migration.status == MigrationStatus.Pending) MigrationStatus.InProgress else migration.status
_ <- migrationPersistence.updateMigrationStatus(migration.id, nextState)
_ <- migrationPersistence.updateStartedAt(migration.id, DateTime.now())
result <- startRecurse(previousSchema, migration)
_ <- migrationPersistence.updateFinishedAt(migration.id, DateTime.now())
} yield result
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package cool.graph.deploy.schema

import org.joda.time.{DateTime, DateTimeZone}
import sangria.ast
import sangria.schema.ScalarType
import sangria.validation.ValueCoercionViolation

import scala.util.{Failure, Success, Try}

object CustomScalarTypes {
case object DateCoercionViolation extends ValueCoercionViolation("Date value expected")

def parseDate(s: String) = Try(new DateTime(s, DateTimeZone.UTC)) match {
case Success(date) ⇒ Right(date)
case Failure(_) ⇒ Left(DateCoercionViolation)
}

val DateTimeType =
ScalarType[DateTime](
"DateTime",
coerceOutput = (d, caps) => {
d.toDateTime
},
coerceUserInput = {
case s: String ⇒ parseDate(s)
case _ ⇒ Left(DateCoercionViolation)
},
coerceInput = {
case ast.StringValue(s, _, _, _, _) ⇒ parseDate(s)
case _ ⇒ Left(DateCoercionViolation)
}
)

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package cool.graph.deploy.schema.types

import cool.graph.deploy.schema.SystemUserContext
import cool.graph.deploy.schema.{CustomScalarTypes, SystemUserContext}
import cool.graph.shared.models
import sangria.schema._

Expand All @@ -15,7 +15,9 @@ object MigrationType {
Field("applied", IntType, resolve = _.value.applied),
Field("rolledBack", IntType, resolve = _.value.rolledBack),
Field("steps", ListType(MigrationStepType.Type), resolve = _.value.steps),
Field("errors", ListType(StringType), resolve = _.value.errors)
Field("errors", ListType(StringType), resolve = _.value.errors),
Field("startedAt", OptionType(CustomScalarTypes.DateTimeType), resolve = _.value.startedAt),
Field("finishedAt", OptionType(CustomScalarTypes.DateTimeType), resolve = _.value.finishedAt)
)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cool.graph.deploy.database.persistence
import cool.graph.deploy.database.tables.Tables
import cool.graph.deploy.specutils.{DeploySpecBase, TestProject}
import cool.graph.shared.models._
import org.joda.time.DateTime
import org.scalatest.{FlatSpec, Matchers}
import slick.jdbc.MySQLProfile.api._

Expand Down Expand Up @@ -110,6 +111,28 @@ class MigrationPersistenceImplSpec extends FlatSpec with Matchers with DeploySpe
reloadedMigration.rolledBack shouldEqual 1
}

".updateMigrationStartedAt()" should "update the migration startedAt timestamp correctly" in {
val (project, _) = setupProject(basicTypesGql)
val createdMigration = migrationPersistence.create(Migration.empty(project.id)).await
val time = DateTime.now()

migrationPersistence.updateStartedAt(createdMigration.id, time).await

val reloadedMigration = migrationPersistence.byId(createdMigration.id).await.get
reloadedMigration.startedAt.isDefined shouldEqual true // some bug causes mysql timstamps to be off by a margin, equal is broken
}

".updateMigrationFinishedAt()" should "update the migration finishedAt timestamp correctly" in {
val (project, _) = setupProject(basicTypesGql)
val createdMigration = migrationPersistence.create(Migration.empty(project.id)).await
val time = DateTime.now()

migrationPersistence.updateFinishedAt(createdMigration.id, time).await

val reloadedMigration = migrationPersistence.byId(createdMigration.id).await.get
reloadedMigration.finishedAt.isDefined shouldEqual true // some bug causes mysql timstamps to be off by a margin, equal is broken
}

".getLastMigration()" should "get the last migration applied to a project" in {
val (project, _) = setupProject(basicTypesGql)
migrationPersistence.getLastMigration(project.id).await.get.revision shouldEqual 2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class MigrationApplierSpec extends FlatSpec with Matchers with DeploySpecBase wi
persisted.status should be(MigrationStatus.Success)
persisted.applied should be(migration.steps.size)
persisted.rolledBack should be(0)
persisted.startedAt.isDefined shouldEqual true
persisted.finishedAt.isDefined shouldEqual true
}

"the applier" should "mark a migration as ROLLBACK_SUCCESS if all steps can be rolled back successfully" in {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
package cool.graph.shared.models

import cool.graph.shared.models.MigrationStatus.MigrationStatus

//case class UnappliedMigration(
// previousProject: Project,
// nextProject: Project,
// migration: Migration
//)
import org.joda.time.DateTime

case class MigrationId(projectId: String, revision: Int)

Expand All @@ -19,7 +14,9 @@ case class Migration(
applied: Int,
rolledBack: Int,
steps: Vector[MigrationStep],
errors: Vector[String]
errors: Vector[String],
startedAt: Option[DateTime] = None,
finishedAt: Option[DateTime] = None
) {
def id: MigrationId = MigrationId(projectId, revision)
def isRollingBack: Boolean = status == MigrationStatus.RollingBack
Expand Down