Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DPP-535 Verify postgres version #10577

Merged
merged 11 commits into from
Aug 30, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import anorm.SQL
import anorm.SqlParser.get
import com.daml.ledger.offset.Offset
import com.daml.lf.data.Ref
import com.daml.logging.LoggingContext
import com.daml.logging.{ContextualizedLogger, LoggingContext}
import com.daml.platform.store.appendonlydao.events.Party
import com.daml.platform.store.backend.EventStorageBackend.FilterParams
import com.daml.platform.store.backend.common.ComposableQuery.{CompositeSql, SqlStringInterpolation}
Expand All @@ -35,6 +35,8 @@ import com.daml.platform.store.backend.{
import javax.sql.DataSource
import org.postgresql.ds.PGSimpleDataSource

import scala.util.Using

private[backend] object PostgresStorageBackend
extends StorageBackend[AppendOnlySchema.Batch]
with CommonStorageBackend[AppendOnlySchema.Batch]
Expand All @@ -43,6 +45,8 @@ private[backend] object PostgresStorageBackend
with CompletionStorageBackendTemplate
with PartyStorageBackendTemplate {

private val logger: ContextualizedLogger = ContextualizedLogger.get(this.getClass)

override def insertBatch(
connection: Connection,
postgresDbBatch: AppendOnlySchema.Batch,
Expand Down Expand Up @@ -106,6 +110,44 @@ private[backend] object PostgresStorageBackend
()
}

def getPostgresVersion(
connection: Connection
)(implicit loggingContext: LoggingContext): Option[(Int, Int)] = {
val version = SQL"SHOW server_version".as(get[String](1).single)(connection)
logger.debug(s"Found Postgres version $version")
parsePostgresVersion(version)
}

def parsePostgresVersion(version: String): Option[(Int, Int)] = {
val versionPattern = """(\d+)[.](\d+).*""".r
version match {
case versionPattern(major, minor) => Some((major.toInt, minor.toInt))
case _ => None
}
}

private def checkCompatibility(
connection: Connection
)(implicit loggingContext: LoggingContext): Unit = {
getPostgresVersion(connection) match {
case Some((major, minor)) =>
if (major < 10) {
logger.error(
"Deprecated Postgres version. " +
s"Found Postgres version $major.$minor, minimum required Postgres version is 10. " +
"This application will continue running but is at risk of data loss, as Postgres < 10 does not support crash-fault tolerant hash indices. " +
"Please upgrade your Postgres database to version 10 or later to fix this issue. " +
"In the future, this deprecation warning may be upgraded to a fatal error."
)
}
case None =>
logger.warn(
s"Could not determine the version of the Postgres database. Please verify that this application is compatible with this Postgres version."
nmarton-da marked this conversation as resolved.
Show resolved Hide resolved
)
}
()
}

object PostgresQueryStrategy extends QueryStrategy {

override def arrayIntersectionNonEmptyClause(
Expand Down Expand Up @@ -181,6 +223,9 @@ private[backend] object PostgresStorageBackend
)(implicit loggingContext: LoggingContext): DataSource = {
val pgSimpleDataSource = new PGSimpleDataSource()
pgSimpleDataSource.setUrl(jdbcUrl)

Using.resource(pgSimpleDataSource.getConnection())(checkCompatibility)

val hookFunctions = List(
dataSourceConfig.postgresConfig.synchronousCommit.toList
.map(synchCommitValue => exe(s"SET synchronous_commit TO ${synchCommitValue.pgSqlName}")),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,32 @@

package com.daml.platform.store.backend

import com.daml.platform.store.backend.postgresql.PostgresStorageBackend
import org.scalatest.flatspec.AsyncFlatSpec

final class StorageBackendPostgresSpec
extends AsyncFlatSpec
with StorageBackendProviderPostgres
with StorageBackendSuite
with StorageBackendSuite {

behavior of "StorageBackend (Postgres)"

it should "find the Postgres version" in {
for {
version <- executeSql(PostgresStorageBackend.getPostgresVersion)
} yield {
inside(version) { case Some(versionNumbers) =>
// Minimum Postgres version used in tests
versionNumbers._1 should be >= 9
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aren't there ordering for tuples? (not sure it will be nicer though)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there is. However, I would opt for creating a case class wrapping the version to have nicer usage syntax, e.g.:

  case class PostgresVersion(major: Int, minor: Int) {
    override def toString: String = s"$major.$minor"
  }

  object PostgresVersion {
    implicit val versionOrdering: Ordering[PostgresVersion] = Ordering.by[PostgresVersion, (Int, Int)](v => (v.major, v.minor))
  }

and then in tests we would have:

version should be >= PostgresVersion(major = 9, minor = 0)

versionNumbers._2 should be >= 0
}
}
}

it should "correctly parse a Postgres version" in {
PostgresStorageBackend.parsePostgresVersion("1.2") shouldBe Some((1, 2))
PostgresStorageBackend.parsePostgresVersion("1.2.3") shouldBe Some((1, 2))
PostgresStorageBackend.parsePostgresVersion("1.2.3-alpha.4.5") shouldBe Some((1, 2))
PostgresStorageBackend.parsePostgresVersion("10.11") shouldBe Some((10, 11))
}
}