Skip to content

Commit

Permalink
Merge pull request #22 from penland365/error-to-message
Browse files Browse the repository at this point in the history
Fleshed out PostgresqlMessage, introduced PostgresqlServerFailure.
  • Loading branch information
penland365 committed Mar 22, 2016
2 parents 9fe1724 + 7719d28 commit b9329a4
Show file tree
Hide file tree
Showing 12 changed files with 253 additions and 188 deletions.
26 changes: 13 additions & 13 deletions core/src/main/scala/roc/postgresql/ClientDispatcher.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
})
}

Expand Down Expand Up @@ -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)
)
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/scala/roc/postgresql/Messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 {
Expand Down
9 changes: 9 additions & 0 deletions core/src/main/scala/roc/postgresql/failures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
7 changes: 2 additions & 5 deletions core/src/main/scala/roc/postgresql/results.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand Down
14 changes: 14 additions & 0 deletions core/src/main/scala/roc/postgresql/server/ErrorNoticeMinutia.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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."
}
Original file line number Diff line number Diff line change
Expand Up @@ -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] {
Expand Down Expand Up @@ -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))}
))
Expand Down
103 changes: 75 additions & 28 deletions core/src/test/scala/roc/postgresql/server/ErrorNoticeGenerator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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)
}
Loading

0 comments on commit b9329a4

Please sign in to comment.