Skip to content

Commit

Permalink
Merge pull request #19 from penland365/server_errors
Browse files Browse the repository at this point in the history
Server errors - Began fleshing out ErrorResponse and NoticeResponses.
  • Loading branch information
penland365 committed Mar 11, 2016
2 parents e33d89e + 046643e commit 9fe1724
Show file tree
Hide file tree
Showing 18 changed files with 1,257 additions and 34 deletions.
8 changes: 4 additions & 4 deletions core/src/main/scala/roc/postgresql/ByteDecoders.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ package postgresql
import cats.data.Xor

trait ByteDecoder[A] {
def fromText(bytes: Option[Array[Byte]]): ByteDecodingFailure Xor Option[A]
def fromBinary(bytes: Option[Array[Byte]]): Error Xor Option[A]
def fromText(bytes: Option[Array[Byte]]): Xor[ByteDecodingFailure, Option[A]]
def fromBinary(bytes: Option[Array[Byte]]): Xor[Failure, Option[A]]
}

trait ByteDecoderImplicits {
Expand All @@ -20,7 +20,7 @@ trait ByteDecoderImplicits {
case None => Xor.Right(None)
}

def fromBinary(bytes: Option[Array[Byte]]): Error Xor Option[Int] =
def fromBinary(bytes: Option[Array[Byte]]): Xor[Failure, Option[Int]] =
Xor.Left(new UnsupportedDecodingFailure("Decoding int from Binary is not Supported"))
}

Expand All @@ -34,7 +34,7 @@ trait ByteDecoderImplicits {
case None => Xor.Right(None)
}

def fromBinary(bytes: Option[Array[Byte]]): Error Xor Option[String] =
def fromBinary(bytes: Option[Array[Byte]]): Xor[Failure, Option[String]] =
Xor.Left(new UnsupportedDecodingFailure("Decoding string from Binary is not Supported"))
}
}
29 changes: 17 additions & 12 deletions core/src/main/scala/roc/postgresql/ClientDispatcher.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +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

private[roc] final class ClientDispatcher(trans: Transport[Packet, Packet],
startup: Startup)
Expand Down Expand Up @@ -74,25 +75,29 @@ private[roc] final class ClientDispatcher(trans: Transport[Packet, Packet],

private[this] def readQueryTx(signal: Promise[Unit]): Future[Result] = {

type Descriptions = List[RowDescription]
type Rows = List[DataRow]
def go(xs: Descriptions, ys: Rows, str: String): Future[(Descriptions, Rows, String)] =
trans.read().flatMap(packet => Message.decode(packet) match {
case Xor.Right(RowDescription(a,b)) => go(RowDescription(a,b) :: xs, ys, str)
case Xor.Right(DataRow(a,b)) => go(xs, DataRow(a,b) :: ys, str)
case Xor.Right(EmptyQueryResponse) => go(xs, ys, "EmptyQueryResponse")
case Xor.Right(CommandComplete(x)) => go(xs, ys, x)
case Xor.Right(Idle) => Future.value((xs, ys, str))
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):
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) =>
Future.exception(new PostgresqlStateMachineFailure("Query", u.toString))
case Xor.Left(l) => Future.exception(l)
}
)

go(List.empty[RowDescription], List.empty[DataRow], "")
go(List.empty[RowDescription], List.empty[DataRow], "", List.empty[PostgresqlError])
.map(tuple => {
val f = signal.setDone()
new Result(tuple._1, tuple._2, tuple._3)
new Result(tuple._1, tuple._2, tuple._3, tuple._4.headOption)
})
}

Expand Down Expand Up @@ -211,7 +216,7 @@ private[roc] final class ClientDispatcher(trans: Transport[Packet, Packet],
val pm = new PasswordMessage(startup.password)
exchange(pm).flatMap(response => response match {
case AuthenticationOk => Future.Done
case ErrorResponse(_, _) => Future.exception(new Exception())
case ErrorResponse(_) => Future.exception(new Exception())
case u => Future.exception(
new PostgresqlStateMachineFailure("PasswordMessage", u.toString)
)
Expand Down
7 changes: 4 additions & 3 deletions core/src/main/scala/roc/postgresql/Messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +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.transport.{Buffer, BufferReader, BufferWriter, Packet}
import scala.collection.mutable.ListBuffer

Expand All @@ -27,7 +28,7 @@ object Message {
val TerminateByte: Char = 'X'
val NoticeResponseByte: Char = 'N'

private[roc] def decode(packet: Packet): Xor[Error, Message] = packet.messageType match {
private[roc] def decode(packet: Packet): Xor[Failure, Message] = packet.messageType match {
case Some(mt) if mt === AuthenticationMessageByte => decodePacket[AuthenticationMessage](packet)
case Some(mt) if mt === ErrorByte => decodePacket[ErrorResponse](packet)
case Some(mt) if mt === ParameterStatusByte => decodePacket[ParameterStatus](packet)
Expand Down Expand Up @@ -73,11 +74,11 @@ final class Terminate extends FrontendMessage

sealed trait BackendMessage extends Message

case class ErrorResponse(byte: Char, reason: String) extends BackendMessage
case class ErrorResponse(error: PostgresqlError) extends BackendMessage

sealed trait AuthenticationMessage extends BackendMessage
object AuthenticationMessage {
def apply(tuple: (Int, Option[Array[Byte]])): Error Xor AuthenticationMessage = tuple match {
def apply(tuple: (Int, Option[Array[Byte]])): Failure Xor AuthenticationMessage = tuple match {
case (0, None) => Xor.Right(AuthenticationOk)
case (2, None) => Xor.Right(AuthenticationKerberosV5)
case (3, None) => Xor.Right(AuthenticationClearTxtPasswd)
Expand Down
4 changes: 0 additions & 4 deletions core/src/main/scala/roc/postgresql/errors.scala

This file was deleted.

23 changes: 22 additions & 1 deletion core/src/main/scala/roc/postgresql/failures.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package roc
package postgresql

sealed trait Failure extends Error
import cats.data.NonEmptyList
import cats.implicits._

sealed abstract class Failure extends Exception

final class UnknownPostgresTypeFailure(objectId: Int) extends Failure {
final override def getMessage: String = s"Postgres Object ID $objectId is unknown"
Expand Down Expand Up @@ -90,6 +93,24 @@ final class ReadyForQueryDecodingFailure(unknownChar: Char) extends Failure {
}
}

/** Denotes a failure to decode an ErrorResponse from the Postgresql Server
*
* @constructor creates an error response decoding failure from all error messages
* @param xs a [[cats.data.NonEmptyList]] of all decoding failures
* @note In practice, these should never occur
*/
final class ErrorResponseDecodingFailure private[postgresql]
(xs: NonEmptyList[String]) extends Failure {
final override def getMessage(): String = xs.foldLeft("")(_ + _ + " ").trim

def canEqual(a: Any) = a.isInstanceOf[ErrorResponseDecodingFailure]

final override def equals(that: Any): Boolean = that match {
case x: ErrorResponseDecodingFailure => x.canEqual(this) && x.getMessage == getMessage
case _ => false
}
}


/** Denotes a State Transition with the Postgresql State Machine that should be impossible.
*
Expand Down
6 changes: 5 additions & 1 deletion core/src/main/scala/roc/postgresql/results.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import cats.data.Xor
import cats.Show
import com.twitter.util.Future
import roc.postgresql.transport.BufferReader
import roc.postgresql.server.PostgresqlError

final class Result(rowDescription: List[RowDescription], data: List[DataRow], cc: String = "") {
final class Result(rowDescription: List[RowDescription], data: List[DataRow], cc: String = "",
postgresqlError: Option[PostgresqlError]) {

val columns = rowDescription match {
case h :: t => h.fields
Expand All @@ -27,6 +29,8 @@ 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
80 changes: 80 additions & 0 deletions core/src/main/scala/roc/postgresql/server/ErrorNoticeMinutia.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package roc
package postgresql
package server

/** The set of ErrorMessage and NoticeResponse Message fields
* @see [[http://www.postgresql.org/docs/current/static/protocol-error-fields.html]]
*/
private[postgresql] object ErrorNoticeMessageFields {
val Severity: Char = 'S'
val Code: Char = 'C'
val Message: Char = 'M'
val Detail: Char = 'D'
val Hint: Char = 'H'
val Position: Char = 'P'
val InternalPosition: Char = 'p'
val InternalQuery: Char = 'q'
val Where: Char = 'W'
val SchemaName: Char = 's'
val TableName: Char = 't'
val ColumnName: Char = 'c'
val DataTypeName: Char = 'd'
val ConstraintName: Char = 'n'
val File: Char = 'F'
val Line: Char = 'L'
val Routine: Char = 'R'
}

/** The set of Postgresql Error Class Codes
*
* Postgresql Errors are categorized into specific categories of errors. For example,
* an ErrorCode of 01000 falls under the Warning Class, as it starts with 01. A Postgresql
* server may emit an Error Code that Roc does not understand, but it should still
* be able to categorize it based off it's Error Class.
* @see [[http://www.postgresql.org/docs/current/static/errcodes-appendix.html]] for more
* information
*/
private[server] object ErrorClassCodes {
val SuccessfulCompletion = "00"
val Warning = "01"
val NoData = "02"
val SQLStatementNotYetComplete = "03"
val ConnectionException = "08"
val TriggeredActionException = "09"
val FeatureNotSupported = "0A"
val InvalidTransactionInitiation = "0B"
val LocatorException = "0F"
val InvalidGrantor = "0L"
val InvalidRoleSpecification = "0P"
val DiagnosisException = "0Z"
val CaseNotFound = "20"
val CardinalityViolation = "21"
val DataException = "22"
val IntegrityConstraintViolation = "23"
val InvalidCursorState = "24"
val InvalidTransactionState = "25"
val InvalidSQLStatementName = "26"
val TriggeredDataChangeViolation = "27"
val InvalidAuthorizationSpecification = "28"
val DependentPrivilegeDescriptorsStillExist = "2B"
val InvalidTransactionTermination = "2D"
val SQLRoutineException = "2F"
val InvalidCursorName = "34"
val ExternalRoutineException = "38"
val ExternalRoutineInvocationException = "39"
val SavepointException = "3B"
val InvalidCatalogName = "3D"
val InvalidSchemaName = "3F"
val TransactionRollback = "40"
val SyntaxErrorOrAccessRuleViolation = "42"
val WithCheckOptionViolation = "44"
val InsufficientResources = "53"
val ProgramLimitExceeded = "54"
val ObjectNotInPrerequisiteState = "55"
val OperatorIntervention = "57"
val SystemError = "58" // these are errors external to Postgres
val ConfigurationFileError = "F0"
val ForeignDataWrapperError = "HV"
val PLpgSQLError = "P0"
val InternalError = "XX"
}
Loading

0 comments on commit 9fe1724

Please sign in to comment.