From 7719d28f051430a25ce8623d611933e75e18044c Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Davis" Date: Mon, 21 Mar 2016 15:15:40 -0500 Subject: [PATCH] Fleshed out PostgresqlMessage, introduced PostgresqlServerFailure. Following an initial discussion https://github.com/penland365/roc/issues/21 on preferred Error Handling, the early implementation of PostgresqlError was replaced by PostgresqlMessage. Following the example set in Postgresql source code, all PostgresqlMessage were furthur classified into one of Success / Warning / Error / Unknown. A new failure type was introduced, PostgresqlServerFailure, to contain any ErrorMessage sent by a Postgresql Server. This implementation allows us to much more easily handle NoticeResponses, and allows us to have a more nuanced process ( yet to be implemented ) around connection handling. --- .../roc/postgresql/ClientDispatcher.scala | 26 ++-- .../main/scala/roc/postgresql/Messages.scala | 4 +- .../main/scala/roc/postgresql/failures.scala | 9 ++ .../main/scala/roc/postgresql/results.scala | 7 +- .../server/ErrorNoticeMinutia.scala | 14 ++ ...lErrors.scala => PostgresqlMessages.scala} | 49 +++--- .../postgresql/transport/PacketDecoders.scala | 4 +- .../server/ErrorNoticeGenerator.scala | 103 +++++++++---- .../server/ErrorNoticeMinutiaSpecs.scala | 22 +++ ...Spec.scala => PostgresqlMessageSpec.scala} | 141 +++++++++++------- .../errors/SuccessfulCompletionSpec.scala | 31 ---- .../server/errors/WarningSpec.scala | 31 ---- 12 files changed, 253 insertions(+), 188 deletions(-) rename core/src/main/scala/roc/postgresql/server/{PostgresqlErrors.scala => PostgresqlMessages.scala} (88%) rename core/src/test/scala/roc/postgresql/server/{PostgresqlErrorSpec.scala => PostgresqlMessageSpec.scala} (69%) delete mode 100644 core/src/test/scala/roc/postgresql/server/errors/SuccessfulCompletionSpec.scala delete mode 100644 core/src/test/scala/roc/postgresql/server/errors/WarningSpec.scala diff --git a/core/src/main/scala/roc/postgresql/ClientDispatcher.scala b/core/src/main/scala/roc/postgresql/ClientDispatcher.scala index 7630b50..160fc12 100644 --- a/core/src/main/scala/roc/postgresql/ClientDispatcher.scala +++ b/core/src/main/scala/roc/postgresql/ClientDispatcher.scala @@ -9,7 +9,7 @@ import com.twitter.finagle.transport.Transport import com.twitter.finagle.{Service, WriteException} import com.twitter.util.{Future, Promise, Time} import roc.postgresql.transport.{Packet, PacketEncoder} -import roc.postgresql.server.PostgresqlError +import roc.postgresql.server.{ErrorMessage, PostgresqlMessage, WarningMessage} private[roc] final class ClientDispatcher(trans: Transport[Packet, Packet], startup: Startup) @@ -77,27 +77,26 @@ private[roc] final class ClientDispatcher(trans: Transport[Packet, Packet], type Descriptions = List[RowDescription] type Rows = List[DataRow] - type Errors = List[PostgresqlError] type CommandCompleteString = String - type Collection = (Descriptions, Rows, CommandCompleteString, Errors) - def go(xs: Descriptions, ys: Rows, ccStr: CommandCompleteString, errors: Errors): + type Collection = (Descriptions, Rows, CommandCompleteString) + def go(xs: Descriptions, ys: Rows, ccStr: CommandCompleteString): Future[Collection] = trans.read().flatMap(packet => Message.decode(packet) match { - case Xor.Right(RowDescription(a,b)) => go(RowDescription(a,b) :: xs, ys, ccStr, errors) - case Xor.Right(DataRow(a,b)) => go(xs, DataRow(a,b) :: ys, ccStr, errors) - case Xor.Right(EmptyQueryResponse) => go(xs, ys, "EmptyQueryResponse", errors) - case Xor.Right(CommandComplete(x)) => go(xs, ys, x, errors) - case Xor.Right(ErrorResponse(e)) => go(xs, ys, ccStr, e :: errors) - case Xor.Right(Idle) => Future.value((xs, ys, ccStr, errors)) - case Xor.Right(u) => + case Xor.Right(RowDescription(a,b)) => go(RowDescription(a,b) :: xs, ys, ccStr) + case Xor.Right(DataRow(a,b)) => go(xs, DataRow(a,b) :: ys, ccStr) + case Xor.Right(EmptyQueryResponse) => go(xs, ys, "EmptyQueryResponse") + case Xor.Right(CommandComplete(x)) => go(xs, ys, x) + case Xor.Right(ErrorResponse(e)) => go(xs, ys, ccStr) + case Xor.Right(Idle) => Future.value((xs, ys, ccStr)) + case Xor.Right(u) => Future.exception(new PostgresqlStateMachineFailure("Query", u.toString)) case Xor.Left(l) => Future.exception(l) } ) - go(List.empty[RowDescription], List.empty[DataRow], "", List.empty[PostgresqlError]) + go(List.empty[RowDescription], List.empty[DataRow], "") .map(tuple => { val f = signal.setDone() - new Result(tuple._1, tuple._2, tuple._3, tuple._4.headOption) + new Result(tuple._1, tuple._2, tuple._3) }) } @@ -179,6 +178,7 @@ private[roc] final class ClientDispatcher(trans: Transport[Packet, Packet], Future.exception(new UnsupportedAuthenticationFailure("AuthenticationSSPI")) case AuthenticationGSS => Future.exception(new UnsupportedAuthenticationFailure("AuthenticationGSS")) + case ErrorResponse(m) => Future.exception(new PostgresqlServerFailure(m)) case u => println(u); Future.exception( new PostgresqlStateMachineFailure("StartupMessage", u.toString) ) diff --git a/core/src/main/scala/roc/postgresql/Messages.scala b/core/src/main/scala/roc/postgresql/Messages.scala index e3d24a6..618db14 100644 --- a/core/src/main/scala/roc/postgresql/Messages.scala +++ b/core/src/main/scala/roc/postgresql/Messages.scala @@ -8,7 +8,7 @@ import cats.syntax.eq._ import com.twitter.util.Future import java.nio.charset.StandardCharsets import java.security.MessageDigest -import roc.postgresql.server.PostgresqlError +import roc.postgresql.server.PostgresqlMessage import roc.postgresql.transport.{Buffer, BufferReader, BufferWriter, Packet} import scala.collection.mutable.ListBuffer @@ -74,7 +74,7 @@ final class Terminate extends FrontendMessage sealed trait BackendMessage extends Message -case class ErrorResponse(error: PostgresqlError) extends BackendMessage +case class ErrorResponse(error: PostgresqlMessage) extends BackendMessage sealed trait AuthenticationMessage extends BackendMessage object AuthenticationMessage { diff --git a/core/src/main/scala/roc/postgresql/failures.scala b/core/src/main/scala/roc/postgresql/failures.scala index 724b6d1..10a4f4d 100644 --- a/core/src/main/scala/roc/postgresql/failures.scala +++ b/core/src/main/scala/roc/postgresql/failures.scala @@ -3,9 +3,18 @@ package postgresql import cats.data.NonEmptyList import cats.implicits._ +import roc.postgresql.server.PostgresqlMessage sealed abstract class Failure extends Exception +/** An Error occurring on the Postgresql Server. + * + * @see [[roc.postgresql.server.ErrorMessage]] + */ +final class PostgresqlServerFailure(message: PostgresqlMessage) extends Failure { + final override def getMessage: String = message.toString +} + final class UnknownPostgresTypeFailure(objectId: Int) extends Failure { final override def getMessage: String = s"Postgres Object ID $objectId is unknown" diff --git a/core/src/main/scala/roc/postgresql/results.scala b/core/src/main/scala/roc/postgresql/results.scala index e25c051..4babfa9 100644 --- a/core/src/main/scala/roc/postgresql/results.scala +++ b/core/src/main/scala/roc/postgresql/results.scala @@ -5,10 +5,9 @@ import cats.data.Xor import cats.Show import com.twitter.util.Future import roc.postgresql.transport.BufferReader -import roc.postgresql.server.PostgresqlError +import roc.postgresql.server.PostgresqlMessage -final class Result(rowDescription: List[RowDescription], data: List[DataRow], cc: String = "", - postgresqlError: Option[PostgresqlError]) { +final class Result(rowDescription: List[RowDescription], data: List[DataRow], cc: String = "") { val columns = rowDescription match { case h :: t => h.fields @@ -29,8 +28,6 @@ final class Result(rowDescription: List[RowDescription], data: List[DataRow], cc val wasEmptyQuery = columns.isEmpty val completedString = cc - - val error = postgresqlError } final case class Column(name: Symbol, columnType: PostgresType, formatCode: FormatCode) { diff --git a/core/src/main/scala/roc/postgresql/server/ErrorNoticeMinutia.scala b/core/src/main/scala/roc/postgresql/server/ErrorNoticeMinutia.scala index b5219ea..5dab492 100644 --- a/core/src/main/scala/roc/postgresql/server/ErrorNoticeMinutia.scala +++ b/core/src/main/scala/roc/postgresql/server/ErrorNoticeMinutia.scala @@ -77,4 +77,18 @@ private[server] object ErrorClassCodes { val ForeignDataWrapperError = "HV" val PLpgSQLError = "P0" val InternalError = "XX" + + val SuccessCodes = List(SuccessfulCompletion) + val WarningCodes = List(Warning, NoData) + val ErrorCodes = List(SQLStatementNotYetComplete, ConnectionException, TriggeredActionException, + FeatureNotSupported, InvalidTransactionInitiation, LocatorException, InvalidGrantor, + InvalidRoleSpecification, DiagnosisException, CaseNotFound, CardinalityViolation, + DataException, IntegrityConstraintViolation, InvalidCursorState, InvalidTransactionState, + InvalidSQLStatementName, TriggeredDataChangeViolation, InvalidAuthorizationSpecification, + DependentPrivilegeDescriptorsStillExist, InvalidTransactionTermination, SQLRoutineException, + InvalidCursorName, ExternalRoutineException, ExternalRoutineInvocationException, + SavepointException, InvalidCatalogName, InvalidSchemaName, TransactionRollback, + SyntaxErrorOrAccessRuleViolation, WithCheckOptionViolation, InsufficientResources, + ProgramLimitExceeded, ObjectNotInPrerequisiteState, OperatorIntervention, SystemError, + ConfigurationFileError, ForeignDataWrapperError, PLpgSQLError, InternalError) } diff --git a/core/src/main/scala/roc/postgresql/server/PostgresqlErrors.scala b/core/src/main/scala/roc/postgresql/server/PostgresqlMessages.scala similarity index 88% rename from core/src/main/scala/roc/postgresql/server/PostgresqlErrors.scala rename to core/src/main/scala/roc/postgresql/server/PostgresqlMessages.scala index 7a06a7a..2f1e996 100644 --- a/core/src/main/scala/roc/postgresql/server/PostgresqlErrors.scala +++ b/core/src/main/scala/roc/postgresql/server/PostgresqlMessages.scala @@ -15,7 +15,7 @@ import cats.syntax.eq._ * @see [[http://www.postgresql.org/docs/current/static/protocol-error-fields.html]] * @see [[http://www.postgresql.org/docs/current/static/errcodes-appendix.html]] */ -sealed abstract class PostgresqlError private[server](params: ErrorParams) { +sealed abstract class PostgresqlMessage private[server](params: ErrorParams) { /** The severity of the Error or Notice * @@ -156,14 +156,15 @@ private[server] case class ErrorParams(severity: String, code: String, message: private[server] case class RequiredParams(severity: String, code: String, message: String) -object PostgresqlError { +private[postgresql] object PostgresqlMessage { import ErrorNoticeMessageFields._ - def apply(xs: Fields): Xor[Failure, PostgresqlError] = + def apply(xs: Fields): Xor[Failure, PostgresqlMessage] = buildParamsFromTuples(xs).flatMap(x => x.code.take(2) match { - case ErrorClassCodes.SuccessfulCompletion => Xor.Right(new SuccessfulCompletion(x)) - case ErrorClassCodes.Warning => Xor.Right(new Warning(x)) - case code => Xor.Right(new UnknownError(x)) + case ErrorClassCodes.SuccessfulCompletion => Xor.Right(new SuccessMessage(x)) + case code if ErrorClassCodes.WarningCodes.contains(code) => Xor.Right(new WarningMessage(x)) + case code if ErrorClassCodes.ErrorCodes.contains(code) => Xor.Right(new ErrorMessage(x)) + case code => Xor.Right(new UnknownMessage(x)) }) // private to server for testing @@ -229,29 +230,27 @@ object PostgresqlError { } } -/** Represents an undefined error. +/** Represents an unknown or undefined message. * * From Postgresql Documentation: "Since more field types might be added in future, * frontends should silently ignore fields of unrecognized type." Therefore, if we decode * an Error we do not recognize, we do not create a Failed Decoding Result. */ -final case class UnknownError private[server](params: ErrorParams) - extends PostgresqlError(params) +final case class UnknownMessage private[server](params: ErrorParams) + extends PostgresqlMessage(params) -/** Represents a set of Successful Completion Errors - * - * Yes, it is oddly name. No, I don't know why Postgresql has an Error named Succesful - * Completion. Aren't you glad you read this? +/** Represents a set of Successful Message + * * @see [[http://www.postgresql.org/docs/current/static/errcodes-appendix.html]] */ -final case class SuccessfulCompletion private[server](params: ErrorParams) - extends PostgresqlError(params) +final case class SuccessMessage private[server](params: ErrorParams) + extends PostgresqlMessage(params) -/** Represents a set of Warning Errors +/** Represents a set of Warning Messages * - * ==Warning Errors== + * ==Warning Messages== * * 1. warning * 1. dynamic_result_sets_returned @@ -261,7 +260,21 @@ final case class SuccessfulCompletion private[server](params: ErrorParams) * 1. privilege_not_revoked * 1. string_data_right_truncation * 1. deprecated_feature + * 1. no_data + * 1. no_additional_dynamic_result_sets_returned + * + * @see [[http://www.postgresql.org/docs/current/static/errcodes-appendix.html]] + * @see [[https://github.com/postgres/postgres/blob/master/src/backend/utils/errcodes.txt]] + */ +final case class WarningMessage private[server](private val params: ErrorParams) + extends PostgresqlMessage(params) + +/** Represents a set of Error Messages * * @see [[http://www.postgresql.org/docs/current/static/errcodes-appendix.html]] + * @see [[https://github.com/postgres/postgres/blob/master/src/backend/utils/errcodes.txt]] */ -final case class Warning private[server](params: ErrorParams) extends PostgresqlError(params) +final case class ErrorMessage private[server](private val params: ErrorParams) + extends PostgresqlMessage(params) { + override def toString: String = s"$severity - $message. SQLSTATE: $code." +} diff --git a/core/src/main/scala/roc/postgresql/transport/PacketDecoders.scala b/core/src/main/scala/roc/postgresql/transport/PacketDecoders.scala index 716b8df..affe2d9 100644 --- a/core/src/main/scala/roc/postgresql/transport/PacketDecoders.scala +++ b/core/src/main/scala/roc/postgresql/transport/PacketDecoders.scala @@ -3,7 +3,7 @@ package postgresql package transport import cats.data.Xor -import roc.postgresql.server.PostgresqlError +import roc.postgresql.server.PostgresqlMessage import scala.collection.mutable.ListBuffer private[roc] trait PacketDecoder[A <: BackendMessage] { @@ -34,7 +34,7 @@ private[postgresql] trait PacketDecoderImplicits { loop(List.empty[Field]) }) .leftMap(t => new PacketDecodingFailure(t.getMessage)) - .flatMap(xs => PostgresqlError(xs).fold( + .flatMap(xs => PostgresqlMessage(xs).fold( {l => Xor.Left(l)}, {r => Xor.Right(new ErrorResponse(r))} )) diff --git a/core/src/test/scala/roc/postgresql/server/ErrorNoticeGenerator.scala b/core/src/test/scala/roc/postgresql/server/ErrorNoticeGenerator.scala index 7fc76f1..4a2dc1f 100644 --- a/core/src/test/scala/roc/postgresql/server/ErrorNoticeGenerator.scala +++ b/core/src/test/scala/roc/postgresql/server/ErrorNoticeGenerator.scala @@ -142,20 +142,20 @@ trait ErrorNoticeGen extends ScalaCheck { optional <- genOptionalFields } yield { val xs = optional.filterNot(x => {x._2 == None || x._2 == Some("")}) - val detail = PostgresqlError.extractValueByCode(Detail, xs) - val hint = PostgresqlError.extractValueByCode(Hint, xs) - val position = PostgresqlError.extractValueByCode(Position, xs) - val internalPosition = PostgresqlError.extractValueByCode(InternalPosition, xs) - val internalQuery = PostgresqlError.extractValueByCode(InternalQuery, xs) - val where = PostgresqlError.extractValueByCode(Where, xs) - val schemaName = PostgresqlError.extractValueByCode(SchemaName, xs) - val tableName = PostgresqlError.extractValueByCode(TableName, xs) - val columnName = PostgresqlError.extractValueByCode(ColumnName, xs) - val dataTypeName = PostgresqlError.extractValueByCode(DataTypeName, xs) - val constraintName = PostgresqlError.extractValueByCode(ConstraintName, xs) - val file = PostgresqlError.extractValueByCode(File, xs) - val line = PostgresqlError.extractValueByCode(Line, xs) - val routine = PostgresqlError.extractValueByCode(Routine, xs) + val detail = PostgresqlMessage.extractValueByCode(Detail, xs) + val hint = PostgresqlMessage.extractValueByCode(Hint, xs) + val position = PostgresqlMessage.extractValueByCode(Position, xs) + val internalPosition = PostgresqlMessage.extractValueByCode(InternalPosition, xs) + val internalQuery = PostgresqlMessage.extractValueByCode(InternalQuery, xs) + val where = PostgresqlMessage.extractValueByCode(Where, xs) + val schemaName = PostgresqlMessage.extractValueByCode(SchemaName, xs) + val tableName = PostgresqlMessage.extractValueByCode(TableName, xs) + val columnName = PostgresqlMessage.extractValueByCode(ColumnName, xs) + val dataTypeName = PostgresqlMessage.extractValueByCode(DataTypeName, xs) + val constraintName = PostgresqlMessage.extractValueByCode(ConstraintName, xs) + val file = PostgresqlMessage.extractValueByCode(File, xs) + val line = PostgresqlMessage.extractValueByCode(Line, xs) + val routine = PostgresqlMessage.extractValueByCode(Routine, xs) new ErrorParams(severity = severity, code = sqlStateCode, message = message, detail = detail, hint = hint, position = position, internalPosition = internalPosition, internalQuery = internalQuery, where = where, schemaName = schemaName, @@ -179,20 +179,20 @@ trait ErrorNoticeGen extends ScalaCheck { .getOrElse(throw new Exception("SQLSTATE Code must be generated")) val message = xs.find(_._1 === Message).map(_._2) .getOrElse(throw new Exception("Message must be generated")) - val detail = PostgresqlError.extractValueByCode(Detail, xs) - val hint = PostgresqlError.extractValueByCode(Hint, xs) - val position = PostgresqlError.extractValueByCode(Position, xs) - val internalPosition = PostgresqlError.extractValueByCode(InternalPosition, xs) - val internalQuery = PostgresqlError.extractValueByCode(InternalQuery, xs) - val where = PostgresqlError.extractValueByCode(Where, xs) - val schemaName = PostgresqlError.extractValueByCode(SchemaName, xs) - val tableName = PostgresqlError.extractValueByCode(TableName, xs) - val columnName = PostgresqlError.extractValueByCode(ColumnName, xs) - val dataTypeName = PostgresqlError.extractValueByCode(DataTypeName, xs) - val constraintName = PostgresqlError.extractValueByCode(ConstraintName, xs) - val file = PostgresqlError.extractValueByCode(File, xs) - val line = PostgresqlError.extractValueByCode(Line, xs) - val routine = PostgresqlError.extractValueByCode(Routine, xs) + val detail = PostgresqlMessage.extractValueByCode(Detail, xs) + val hint = PostgresqlMessage.extractValueByCode(Hint, xs) + val position = PostgresqlMessage.extractValueByCode(Position, xs) + val internalPosition = PostgresqlMessage.extractValueByCode(InternalPosition, xs) + val internalQuery = PostgresqlMessage.extractValueByCode(InternalQuery, xs) + val where = PostgresqlMessage.extractValueByCode(Where, xs) + val schemaName = PostgresqlMessage.extractValueByCode(SchemaName, xs) + val tableName = PostgresqlMessage.extractValueByCode(TableName, xs) + val columnName = PostgresqlMessage.extractValueByCode(ColumnName, xs) + val dataTypeName = PostgresqlMessage.extractValueByCode(DataTypeName, xs) + val constraintName = PostgresqlMessage.extractValueByCode(ConstraintName, xs) + val file = PostgresqlMessage.extractValueByCode(File, xs) + val line = PostgresqlMessage.extractValueByCode(Line, xs) + val routine = PostgresqlMessage.extractValueByCode(Routine, xs) new ErrorParams(severity = severity, code = code, message = message, detail = detail, hint = hint, position = position, internalPosition = internalPosition, internalQuery = internalQuery, where = where, schemaName = schemaName, @@ -228,7 +228,54 @@ trait ErrorNoticeGen extends ScalaCheck { new FieldsAndErrorParams(fields, e) } + lazy val successfulMessageGen: Gen[FieldsAndErrorParams] = for { + severity <- genValidSeverityField + message <- arbitrary[String] + optional <- genOptionalFields + } yield { + val fields = buildFields(severity, ErrorClassCodes.SuccessfulCompletion, message, optional) + val e = buildErrorParamsFromFields(fields) + new FieldsAndErrorParams(fields, e) + } + + lazy val genValidWarningCode: Gen[String] = Gen.oneOf(ErrorClassCodes.WarningCodes) + lazy val warningMessageGen: Gen[FieldsAndErrorParams] = for { + severity <- genValidSeverityField + message <- arbitrary[String] + optional <- genOptionalFields + warningCode <- genValidWarningCode + } yield { + val fields = buildFields(severity, warningCode, message, optional) + val e = buildErrorParamsFromFields(fields) + new FieldsAndErrorParams(fields, e) + } + + lazy val genValidErrorCode: Gen[String] = Gen.oneOf(ErrorClassCodes.ErrorCodes) + lazy val errorMessageGen: Gen[FieldsAndErrorParams] = for { + severity <- genValidSeverityField + message <- arbitrary[String] + optional <- genOptionalFields + errorCode <- genValidErrorCode + } yield { + val fields = buildFields(severity, errorCode, message, optional) + val e = buildErrorParamsFromFields(fields) + new FieldsAndErrorParams(fields, e) + } + + lazy val errMsgAndRequiredFieldsGen: Gen[ErrorMessageAndRequiredFields] = for { + severity <- genValidSeverityField + message <- arbitrary[String] + optional <- genOptionalFields + errorCode <- genValidErrorCode + } yield { + val fields = buildFields(severity, errorCode, message, optional) + val error = PostgresqlMessage(fields).getOrElse(throw new Exception("Should never get here.")) + new ErrorMessageAndRequiredFields(severity, message, errorCode, error) + } case class ExtractValueByCodeContainer(code: Char, xs: Fields) case class FieldsAndErrorParams(fields: Fields, errorParams: ErrorParams) + + case class ErrorMessageAndRequiredFields(severity: String, message: String, code: String, + error: PostgresqlMessage) } diff --git a/core/src/test/scala/roc/postgresql/server/ErrorNoticeMinutiaSpecs.scala b/core/src/test/scala/roc/postgresql/server/ErrorNoticeMinutiaSpecs.scala index 654fd3e..4b45a48 100644 --- a/core/src/test/scala/roc/postgresql/server/ErrorNoticeMinutiaSpecs.scala +++ b/core/src/test/scala/roc/postgresql/server/ErrorNoticeMinutiaSpecs.scala @@ -26,6 +26,10 @@ final class ErrorNoticeMinutiaSpec extends Specification with ScalaCheck { def i Routine must equal 'R' ${ENMF().testRoutine} Error Class Codes + SuccessCodes must contain all Success values ${ECC().testSuccessCodes} + WarningCodes must contain all Warning values ${ECC().testWarningCodes} + ErrorCodes must contain all Error values ${ECC().testErrorCodes} + Successful Completion must be '00' ${ECC().testSuccessfulCompletion} Warning must be '01' ${ECC().testWarning} NoData must be '02' ${ECC().testNoData} @@ -137,6 +141,24 @@ final class ErrorNoticeMinutiaSpec extends Specification with ScalaCheck { def i def testForeignDataWrapperError = ForeignDataWrapperError must_== "HV" def testPLpgSQLError = PLpgSQLError must_== "P0" def testInternalError = InternalError must_== "XX" + + def testSuccessCodes = SuccessCodes must_== List(SuccessfulCompletion) + def testWarningCodes = WarningCodes must_== List(Warning, NoData) + def testErrorCodes = { + val expectedCodes = List(SQLStatementNotYetComplete, ConnectionException, + TriggeredActionException, FeatureNotSupported, InvalidTransactionInitiation, + LocatorException, InvalidGrantor, InvalidRoleSpecification, DiagnosisException, + CaseNotFound, CardinalityViolation, DataException, IntegrityConstraintViolation, + InvalidCursorState, InvalidTransactionState, InvalidSQLStatementName, + TriggeredDataChangeViolation, InvalidAuthorizationSpecification, + DependentPrivilegeDescriptorsStillExist, InvalidTransactionTermination, SQLRoutineException, + InvalidCursorName, ExternalRoutineException, ExternalRoutineInvocationException, + SavepointException, InvalidCatalogName, InvalidSchemaName, TransactionRollback, + SyntaxErrorOrAccessRuleViolation, WithCheckOptionViolation, InsufficientResources, + ProgramLimitExceeded, ObjectNotInPrerequisiteState, OperatorIntervention, SystemError, + ConfigurationFileError, ForeignDataWrapperError, PLpgSQLError, InternalError) + ErrorCodes must_== expectedCodes + } } } diff --git a/core/src/test/scala/roc/postgresql/server/PostgresqlErrorSpec.scala b/core/src/test/scala/roc/postgresql/server/PostgresqlMessageSpec.scala similarity index 69% rename from core/src/test/scala/roc/postgresql/server/PostgresqlErrorSpec.scala rename to core/src/test/scala/roc/postgresql/server/PostgresqlMessageSpec.scala index c308e79..aa9b86d 100644 --- a/core/src/test/scala/roc/postgresql/server/PostgresqlErrorSpec.scala +++ b/core/src/test/scala/roc/postgresql/server/PostgresqlMessageSpec.scala @@ -16,11 +16,14 @@ import org.specs2.specification.create.FragmentsFactory import roc.postgresql.ErrorResponseDecodingFailure import roc.postgresql.server.ErrorNoticeMessageFields._ -final class PostgresqlErrorSpec extends Specification with ScalaCheck { def is = s2""" +final class PostgresqlMessageSpec extends Specification with ScalaCheck { def is = s2""" - PostgresqlError - must extract the value of a tuple by the Code ${PE().testExtractValueByCode} - must return Xor.Right(UnknownError(ErrorParams)) when given unknown SQLSTATE Code ${PE().testUnknownError} + PostgresqlMessage + must extract the value of a tuple by the Code ${PE().testExtractValueByCode} + must return Xor.Right(UnknownMessage(ErrorParams)) when given unknown SQLSTATE Code ${PE().testUnknownMessage} + must return Xor.Right(SuccesfulMessage) when given a valid Succes Code ${PE().testSuccessfulMessage} + must return Xor.Right(WarningMessage(ErrorParams)) when given a Warning Code ${PE().testWarningMessages} + must return Xor.Right(ErrorMessage(ErrorParams)) when given an Error Code ${PE().testErrorMessages} ValidatePacket must return RequiredParams when fields are valid ${VP().testAllValid} @@ -44,23 +47,26 @@ final class PostgresqlErrorSpec extends Specification with ScalaCheck { def is = must have correct Error Message when no required fields are present ${BPFT().testNoRequiredFieldsFoundMessage} ErrorParams - PostgresqlError must have correct Severity ${EP().testSeverity} - PostgresqlError must have correct Code ${EP().testCode} - PostgresqlError must have correct Message ${EP().testMessage} - PostgresqlError must have correct Detail ${EP().testDetail} - PostgresqlError must have correct Hint ${EP().testHint} - PostgresqlError must have correct Position ${EP().testPosition} - PostgresqlError must have correct InternalPosition ${EP().testInternalPosition} - PostgresqlError must have correct InternalQuery ${EP().testInternalQuery} - PostgresqlError must have correct Where ${EP().testWhere} - PostgresqlError must have correct SchemaName ${EP().testSchemaName} - PostgresqlError must have correct TableName ${EP().testTableName} - PostgresqlError must have correct ColumnName ${EP().testColumnName} - PostgresqlError must have correct DataTypeName ${EP().testDataTypeName} - PostgresqlError must have correct ConstraintName ${EP().testConstraintName} - PostgresqlError must have correct File ${EP().testFile} - PostgresqlError must have correct Line ${EP().testLine} - PostgresqlError must have correct Routine ${EP().testRoutine} + PostgresqlMessage must have correct Severity ${EP().testSeverity} + PostgresqlMessage must have correct Code ${EP().testCode} + PostgresqlMessage must have correct Message ${EP().testMessage} + PostgresqlMessage must have correct Detail ${EP().testDetail} + PostgresqlMessage must have correct Hint ${EP().testHint} + PostgresqlMessage must have correct Position ${EP().testPosition} + PostgresqlMessage must have correct InternalPosition ${EP().testInternalPosition} + PostgresqlMessage must have correct InternalQuery ${EP().testInternalQuery} + PostgresqlMessage must have correct Where ${EP().testWhere} + PostgresqlMessage must have correct SchemaName ${EP().testSchemaName} + PostgresqlMessage must have correct TableName ${EP().testTableName} + PostgresqlMessage must have correct ColumnName ${EP().testColumnName} + PostgresqlMessage must have correct DataTypeName ${EP().testDataTypeName} + PostgresqlMessage must have correct ConstraintName ${EP().testConstraintName} + PostgresqlMessage must have correct File ${EP().testFile} + PostgresqlMessage must have correct Line ${EP().testLine} + PostgresqlMessage must have correct Routine ${EP().testRoutine} + + ErrorMessage + ErrorMessage must have correct message ${EM().testMessage} """ case class PE() extends ErrorNoticeGen { @@ -68,11 +74,23 @@ final class PostgresqlErrorSpec extends Specification with ScalaCheck { def is = val code = container.code val xs = container.xs val expected = xs.find(_._1 === code).map(_._2) - PostgresqlError.extractValueByCode(code, xs) must_== expected + PostgresqlMessage.extractValueByCode(code, xs) must_== expected } - val testUnknownError = forAll(unknownErrorGen) { x: FieldsAndErrorParams => - PostgresqlError(x.fields) must_== Xor.Right(UnknownError(x.errorParams)) + val testUnknownMessage = forAll(unknownErrorGen) { x: FieldsAndErrorParams => + PostgresqlMessage(x.fields) must_== Xor.Right(UnknownMessage(x.errorParams)) + } + + val testSuccessfulMessage = forAll(successfulMessageGen) { x: FieldsAndErrorParams => + PostgresqlMessage(x.fields) must_== Xor.Right(SuccessMessage(x.errorParams)) + } + + val testWarningMessages = forAll(warningMessageGen) { x: FieldsAndErrorParams => + PostgresqlMessage(x.fields) must_== Xor.Right(WarningMessage(x.errorParams)) + } + + val testErrorMessages = forAll(errorMessageGen) { x: FieldsAndErrorParams => + PostgresqlMessage(x.fields) must_== Xor.Right(ErrorMessage(x.errorParams)) } } @@ -83,7 +101,7 @@ final class PostgresqlErrorSpec extends Specification with ScalaCheck { def is = val code = extractCode(xs) val message = extractMessage(xs) - val actual = PostgresqlError.validatePacket(severity.toValidatedNel, code.toValidatedNel, + val actual = PostgresqlMessage.validatePacket(severity.toValidatedNel, code.toValidatedNel, message.toValidatedNel)(RequiredParams.apply) val expected = validatePacket(severity.toValidatedNel, code.toValidatedNel, message.toValidatedNel)(RequiredParams.apply) @@ -96,7 +114,7 @@ final class PostgresqlErrorSpec extends Specification with ScalaCheck { def is = val code = extractCode(xs) val message = extractMessage(xs) - val actual = PostgresqlError.validatePacket(severity.toValidatedNel, code.toValidatedNel, + val actual = PostgresqlMessage.validatePacket(severity.toValidatedNel, code.toValidatedNel, message.toValidatedNel)(RequiredParams.apply) val expected = validatePacket(severity.toValidatedNel, code.toValidatedNel, message.toValidatedNel)(RequiredParams.apply) @@ -109,7 +127,7 @@ final class PostgresqlErrorSpec extends Specification with ScalaCheck { def is = val code = extractCode(xs) val message = extractMessage(xs) - val actual = PostgresqlError.validatePacket(severity.toValidatedNel, code.toValidatedNel, + val actual = PostgresqlMessage.validatePacket(severity.toValidatedNel, code.toValidatedNel, message.toValidatedNel)(RequiredParams.apply) val expected = validatePacket(severity.toValidatedNel, code.toValidatedNel, message.toValidatedNel)(RequiredParams.apply) @@ -122,7 +140,7 @@ final class PostgresqlErrorSpec extends Specification with ScalaCheck { def is = val code = extractCode(xs) val message = extractMessage(xs) - val actual = PostgresqlError.validatePacket(severity.toValidatedNel, code.toValidatedNel, + val actual = PostgresqlMessage.validatePacket(severity.toValidatedNel, code.toValidatedNel, message.toValidatedNel)(RequiredParams.apply) val expected = validatePacket(severity.toValidatedNel, code.toValidatedNel, message.toValidatedNel)(RequiredParams.apply) @@ -135,7 +153,7 @@ final class PostgresqlErrorSpec extends Specification with ScalaCheck { def is = val code = extractCode(xs) val message = extractMessage(xs) - val actual = PostgresqlError.validatePacket(severity.toValidatedNel, code.toValidatedNel, + val actual = PostgresqlMessage.validatePacket(severity.toValidatedNel, code.toValidatedNel, message.toValidatedNel)(RequiredParams.apply) val expected = validatePacket(severity.toValidatedNel, code.toValidatedNel, message.toValidatedNel)(RequiredParams.apply) @@ -148,7 +166,7 @@ final class PostgresqlErrorSpec extends Specification with ScalaCheck { def is = val code = extractCode(xs) val message = extractMessage(xs) - val actual = PostgresqlError.validatePacket(severity.toValidatedNel, code.toValidatedNel, + val actual = PostgresqlMessage.validatePacket(severity.toValidatedNel, code.toValidatedNel, message.toValidatedNel)(RequiredParams.apply) val expected = validatePacket(severity.toValidatedNel, code.toValidatedNel, message.toValidatedNel)(RequiredParams.apply) @@ -161,7 +179,7 @@ final class PostgresqlErrorSpec extends Specification with ScalaCheck { def is = val code = extractCode(xs) val message = extractMessage(xs) - val actual = PostgresqlError.validatePacket(severity.toValidatedNel, code.toValidatedNel, + val actual = PostgresqlMessage.validatePacket(severity.toValidatedNel, code.toValidatedNel, message.toValidatedNel)(RequiredParams.apply) val expected = validatePacket(severity.toValidatedNel, code.toValidatedNel, message.toValidatedNel)(RequiredParams.apply) @@ -174,7 +192,7 @@ final class PostgresqlErrorSpec extends Specification with ScalaCheck { def is = val code = extractCode(xs) val message = extractMessage(xs) - val actual = PostgresqlError.validatePacket(severity.toValidatedNel, code.toValidatedNel, + val actual = PostgresqlMessage.validatePacket(severity.toValidatedNel, code.toValidatedNel, message.toValidatedNel)(RequiredParams.apply) val expected = validatePacket(severity.toValidatedNel, code.toValidatedNel, message.toValidatedNel)(RequiredParams.apply) @@ -214,37 +232,37 @@ final class PostgresqlErrorSpec extends Specification with ScalaCheck { def is = case class BPFT() extends ErrorNoticeGen { val testValidFields = forAll(validFieldsGen) { xs: Fields => - PostgresqlError.buildParamsFromTuples(xs).isRight must_== true + PostgresqlMessage.buildParamsFromTuples(xs).isRight must_== true } val testInvalidFields = forAll(invalidFieldsGen) { xs: Fields => - PostgresqlError.buildParamsFromTuples(xs).isLeft must_== true + PostgresqlMessage.buildParamsFromTuples(xs).isLeft must_== true } val testSeverityMessage = { val xs = List((ErrorNoticeMessageFields.Code, "Foo"), (Message, "Bar")) - val actual = PostgresqlError.buildParamsFromTuples(xs) + val actual = PostgresqlMessage.buildParamsFromTuples(xs) val nel = NonEmptyList("Required Severity Level was not present.") actual must_== Xor.Left(new ErrorResponseDecodingFailure(nel)) } val testSqlStateCodeMessage = { val xs = List((Severity, "Foo"), (Message, "Bar")) - val actual = PostgresqlError.buildParamsFromTuples(xs) + val actual = PostgresqlMessage.buildParamsFromTuples(xs) val nel = NonEmptyList("Required SQLSTATE Code was not present.") actual must_== Xor.Left(new ErrorResponseDecodingFailure(nel)) } val testMessageMessage = { val xs = List((Severity, "Foo"), (ErrorNoticeMessageFields.Code, "Bar")) - val actual = PostgresqlError.buildParamsFromTuples(xs) + val actual = PostgresqlMessage.buildParamsFromTuples(xs) val nel = NonEmptyList("Required Message was not present.") actual must_== Xor.Left(new ErrorResponseDecodingFailure(nel)) } val testSeveritySqlStateCodeMessage = { val xs = List((Message, "Foo")) - val actual = PostgresqlError.buildParamsFromTuples(xs) + val actual = PostgresqlMessage.buildParamsFromTuples(xs) val nel = NonEmptyList("Required Severity Level was not present.", "Required SQLSTATE Code was not present.") actual must_== Xor.Left(new ErrorResponseDecodingFailure(nel)) @@ -252,7 +270,7 @@ final class PostgresqlErrorSpec extends Specification with ScalaCheck { def is = val testSeverityMessageMessage = { val xs = List((ErrorNoticeMessageFields.Code, "Foo")) - val actual = PostgresqlError.buildParamsFromTuples(xs) + val actual = PostgresqlMessage.buildParamsFromTuples(xs) val nel = NonEmptyList("Required Severity Level was not present.", "Required Message was not present.") actual must_== Xor.Left(new ErrorResponseDecodingFailure(nel)) @@ -260,7 +278,7 @@ final class PostgresqlErrorSpec extends Specification with ScalaCheck { def is = val testSqlStateCodeMessageMessage = { val xs = List((Severity, "Foo")) - val actual = PostgresqlError.buildParamsFromTuples(xs) + val actual = PostgresqlMessage.buildParamsFromTuples(xs) val nel = NonEmptyList("Required SQLSTATE Code was not present.", "Required Message was not present.") actual must_== Xor.Left(new ErrorResponseDecodingFailure(nel)) @@ -268,7 +286,7 @@ final class PostgresqlErrorSpec extends Specification with ScalaCheck { def is = val testNoRequiredFieldsFoundMessage = { val xs = List.empty[Field] - val actual = PostgresqlError.buildParamsFromTuples(xs) + val actual = PostgresqlMessage.buildParamsFromTuples(xs) val nel = NonEmptyList("Required Severity Level was not present.", "Required SQLSTATE Code was not present.", "Required Message was not present.") @@ -278,89 +296,96 @@ final class PostgresqlErrorSpec extends Specification with ScalaCheck { def is = case class EP() extends ErrorNoticeGen { val testSeverity = forAll { ep: ErrorParams => - val error = new UnknownError(ep) + val error = new UnknownMessage(ep) error.severity must_== ep.severity } val testCode = forAll { ep: ErrorParams => - val error = new UnknownError(ep) + val error = new UnknownMessage(ep) error.code must_== ep.code } val testMessage = forAll { ep: ErrorParams => - val error = new UnknownError(ep) + val error = new UnknownMessage(ep) error.message must_== ep.message } val testDetail = forAll { ep: ErrorParams => - val error = new UnknownError(ep) + val error = new UnknownMessage(ep) error.detail must_== ep.detail } val testHint = forAll { ep: ErrorParams => - val error = new UnknownError(ep) + val error = new UnknownMessage(ep) error.hint must_== ep.hint } val testPosition = forAll { ep: ErrorParams => - val error = new UnknownError(ep) + val error = new UnknownMessage(ep) error.position must_== ep.position } val testInternalPosition = forAll { ep: ErrorParams => - val error = new UnknownError(ep) + val error = new UnknownMessage(ep) error.internalPosition must_== ep.internalPosition } val testInternalQuery = forAll { ep: ErrorParams => - val error = new UnknownError(ep) + val error = new UnknownMessage(ep) error.internalQuery must_== ep.internalQuery } val testWhere = forAll { ep: ErrorParams => - val error = new UnknownError(ep) + val error = new UnknownMessage(ep) error.where must_== ep.where } val testSchemaName = forAll { ep: ErrorParams => - val error = new UnknownError(ep) + val error = new UnknownMessage(ep) error.schemaName must_== ep.schemaName } val testTableName = forAll { ep: ErrorParams => - val error = new UnknownError(ep) + val error = new UnknownMessage(ep) error.tableName must_== ep.tableName } val testColumnName = forAll { ep: ErrorParams => - val error = new UnknownError(ep) + val error = new UnknownMessage(ep) error.columnName must_== ep.columnName } val testDataTypeName = forAll { ep: ErrorParams => - val error = new UnknownError(ep) + val error = new UnknownMessage(ep) error.dataTypeName must_== ep.dataTypeName } val testConstraintName = forAll { ep: ErrorParams => - val error = new UnknownError(ep) + val error = new UnknownMessage(ep) error.constraintName must_== ep.constraintName } val testFile = forAll { ep: ErrorParams => - val error = new UnknownError(ep) + val error = new UnknownMessage(ep) error.file must_== ep.file } val testLine = forAll { ep: ErrorParams => - val error = new UnknownError(ep) + val error = new UnknownMessage(ep) error.line must_== ep.line } val testRoutine = forAll { ep: ErrorParams => - val error = new UnknownError(ep) + val error = new UnknownMessage(ep) error.routine must_== ep.routine } } + + case class EM() extends ErrorNoticeGen { + val testMessage = forAll(errMsgAndRequiredFieldsGen) { xs: ErrorMessageAndRequiredFields => + val expectedMessage = s"${xs.severity} - ${xs.message}. SQLSTATE: ${xs.code}." + xs.error.toString must_== expectedMessage + } + } } diff --git a/core/src/test/scala/roc/postgresql/server/errors/SuccessfulCompletionSpec.scala b/core/src/test/scala/roc/postgresql/server/errors/SuccessfulCompletionSpec.scala deleted file mode 100644 index d907c09..0000000 --- a/core/src/test/scala/roc/postgresql/server/errors/SuccessfulCompletionSpec.scala +++ /dev/null @@ -1,31 +0,0 @@ -package roc -package postgresql -package server - -import cats.data.Xor -import org.scalacheck.Arbitrary.arbitrary -import org.scalacheck.Gen -import org.scalacheck.Prop.forAll -import org.specs2._ - -final class SuccessfulCompletionSpec extends Specification with ErrorNoticeGen { def is = s2""" - - SuccessfulCompletion - must return Xor.Right(SuccessfulCompletion(ErrorParams)) when SQLSTATE Code is '00' $testSuccessfulCompletion - """ - - val testSuccessfulCompletion = forAll(successfulCompletionGen) { x: FieldsAndErrorParams => - PostgresqlError(x.fields) must_== Xor.Right(SuccessfulCompletion(x.errorParams)) - } - - private lazy val successfulCompletionGen: Gen[FieldsAndErrorParams] = for { - severity <- genValidSeverityField - message <- arbitrary[String] - optional <- genOptionalFields - } yield { - val fields = buildFields(severity, ErrorClassCodes.SuccessfulCompletion, message, optional) - val e = buildErrorParamsFromFields(fields) - new FieldsAndErrorParams(fields, e) - } - -} diff --git a/core/src/test/scala/roc/postgresql/server/errors/WarningSpec.scala b/core/src/test/scala/roc/postgresql/server/errors/WarningSpec.scala deleted file mode 100644 index bb83886..0000000 --- a/core/src/test/scala/roc/postgresql/server/errors/WarningSpec.scala +++ /dev/null @@ -1,31 +0,0 @@ -package roc -package postgresql -package server - -import cats.data.Xor -import org.scalacheck.Arbitrary.arbitrary -import org.scalacheck.Gen -import org.scalacheck.Prop.forAll -import org.specs2._ - -final class WarningSpec extends Specification with ErrorNoticeGen { def is = s2""" - - Warning - must return Xor.Right(Warning(ErrorParams)) when SQLSTATE Code is '01' $testWarning - """ - - val testWarning = forAll(warningGen) { x: FieldsAndErrorParams => - PostgresqlError(x.fields) must_== Xor.Right(Warning(x.errorParams)) - } - - private lazy val warningGen: Gen[FieldsAndErrorParams] = for { - severity <- genValidSeverityField - message <- arbitrary[String] - optional <- genOptionalFields - } yield { - val fields = buildFields(severity, ErrorClassCodes.Warning, message, optional) - val e = buildErrorParamsFromFields(fields) - new FieldsAndErrorParams(fields, e) - } - -}