in app/models/ProjectMetadata.scala [82:136]
def allMetadataFor(projectRef:Int)(implicit db:slick.jdbc.PostgresProfile#Backend#Database):Future[Try[Seq[ProjectMetadata]]] =
db.run(
TableQuery[ProjectMetadataRow].filter(_.projectRef===projectRef).result.asTry
)
def deleteAllMetadataFor(projectRef:Int)(implicit db:slick.jdbc.PostgresProfile#Backend#Database):Future[Try[Int]] =
db.run(
TableQuery[ProjectMetadataRow].filter(_.projectRef===projectRef).delete.asTry
)
/**
* Set (by upsert) entries in bulk
* @param projectRef project ID to set entries for
* @param data a Map[String,String] containing keys and values to set
* @param db implicitly provided database object
* @return a Future, containing an Int indicating the number of insert/updates. The future will fail if there is a database error
*/
def setBulk(projectRef:Int, data:Map[String,String])(implicit db:slick.jdbc.PostgresProfile#Backend#Database):Future[Try[Int]] = {
def tryInsertWithRecovery(mdEntry:ProjectMetadata,onRetry:Boolean=false):Future[ProjectMetadata] = mdEntry.save.flatMap({
case Failure(err)=>
val errorString = err.toString
if (errorString.contains("violates foreign key constraint") ||
errorString.contains("Referential integrity constraint violation") ||
errorString.contains("violates unique constraint")) {
ProjectMetadata.deleteFor(mdEntry.projectRef, mdEntry.key).flatMap({
case Failure(err)=>
logger.error("Could not delete old metadata value", err)
throw err
case Success(rows)=>
if (onRetry)
throw err
else
tryInsertWithRecovery(mdEntry, onRetry = true)
})
} else {
throw err
}
case Success(savedEntry)=>Future(savedEntry)
})
val createdObjects = Future.sequence(data.map(kvTuple=>ProjectMetadata.getOrCreate(projectRef,kvTuple._1,Some(kvTuple._2))))
val splitResultsFuture = createdObjects.map(_.partition(_.isSuccess))
splitResultsFuture.flatMap(resultTuple=>{
if(resultTuple._2.count(x=>true)>0){
val resultSeq = resultTuple._2.foldLeft("")((acc, failedTry)=>acc + failedTry.failed.get.toString).mkString("; ")
Future(Failure(new RuntimeException(resultSeq)) ) //fixme: define a custom exception to hold the sequence instead
} else {
Future.sequence(resultTuple._1.map(successfulTry=>tryInsertWithRecovery(successfulTry.get)))
.map(iterable=>Success(iterable.count(x=>true)))
.recover({ case ex:Throwable=>Failure(ex) })
}
})
}