{- |
Copyright : Flipstone Technology Partners 2023
License   : MIT
Stability : Stable

@since 1.0.0.0
-}
module Orville.PostgreSQL.Raw.PgTextFormatValue
  ( PgTextFormatValue
  , NULByteFoundError (NULByteFoundError)
  , unsafeFromByteString
  , fromByteString
  , toByteString
  , toBytesForLibPQ
  )
where

import Control.Exception (Exception)
import qualified Data.ByteString as BS

{- |
  A 'PgTextFormatValue' represents raw bytes that will be passed to PostgreSQL
  via LibPQ. These bytes must conform to the TEXT format of values that
  PostgreSQL expects. In all cases, PostgreSQL will be allowed to infer the
  type of the value based on its usage in the query.

  Note that PostgreSQL does not allow NUL bytes in text values, and the LibPQ C
  library expects text values to be given as NULL-terminated C Strings, so
  '\NUL' bytes cannot be included in a 'PgTextFormatValue'. If 'fromByteString'
  is used to construct the 'PgTextFormatValue' (normally what you should do),
  an error will be raised before LibPQ is called to execute the query. If
  'unsafeFromByteString' is used, the caller is expected to ensure that no
  '\NUL' bytes are present. If a '\NUL' byte is included with
  'unsafeFromByteString', the value passed to the database will be truncated at
  the '\NUL' byte because it will be interpreted as the end of the C String by
  LibPQ.

@since 1.0.0.0
-}
data PgTextFormatValue
  = NoAssumptionsMade BS.ByteString
  | AssumedToHaveNoNULValues BS.ByteString
  deriving (Int -> PgTextFormatValue -> ShowS
[PgTextFormatValue] -> ShowS
PgTextFormatValue -> String
(Int -> PgTextFormatValue -> ShowS)
-> (PgTextFormatValue -> String)
-> ([PgTextFormatValue] -> ShowS)
-> Show PgTextFormatValue
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> PgTextFormatValue -> ShowS
showsPrec :: Int -> PgTextFormatValue -> ShowS
$cshow :: PgTextFormatValue -> String
show :: PgTextFormatValue -> String
$cshowList :: [PgTextFormatValue] -> ShowS
showList :: [PgTextFormatValue] -> ShowS
Show)

instance Eq PgTextFormatValue where
  PgTextFormatValue
left == :: PgTextFormatValue -> PgTextFormatValue -> Bool
== PgTextFormatValue
right =
    PgTextFormatValue -> Either NULByteFoundError ByteString
toBytesForLibPQ PgTextFormatValue
left Either NULByteFoundError ByteString
-> Either NULByteFoundError ByteString -> Bool
forall a. Eq a => a -> a -> Bool
== PgTextFormatValue -> Either NULByteFoundError ByteString
toBytesForLibPQ PgTextFormatValue
right

data NULByteFoundError
  = NULByteFoundError
  deriving (Int -> NULByteFoundError -> ShowS
[NULByteFoundError] -> ShowS
NULByteFoundError -> String
(Int -> NULByteFoundError -> ShowS)
-> (NULByteFoundError -> String)
-> ([NULByteFoundError] -> ShowS)
-> Show NULByteFoundError
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> NULByteFoundError -> ShowS
showsPrec :: Int -> NULByteFoundError -> ShowS
$cshow :: NULByteFoundError -> String
show :: NULByteFoundError -> String
$cshowList :: [NULByteFoundError] -> ShowS
showList :: [NULByteFoundError] -> ShowS
Show, NULByteFoundError -> NULByteFoundError -> Bool
(NULByteFoundError -> NULByteFoundError -> Bool)
-> (NULByteFoundError -> NULByteFoundError -> Bool)
-> Eq NULByteFoundError
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: NULByteFoundError -> NULByteFoundError -> Bool
== :: NULByteFoundError -> NULByteFoundError -> Bool
$c/= :: NULByteFoundError -> NULByteFoundError -> Bool
/= :: NULByteFoundError -> NULByteFoundError -> Bool
Eq)

instance Exception NULByteFoundError

{- |
  Constructs a 'PgTextFormatValue' from the given bytes directly, without checking
  whether any of the bytes are '\NUL' or not. If a 'BS.ByteString' containing
  a '\NUL' byte is given, the value will be truncated at the '\NUL' when it
  is passed to LibPQ.

  This function is only safe to use when you have generated the bytestring
  in a way that guarantees no '\NUL' bytes are present, such as when serializing
  an integer value to its decimal representation.

@since 1.0.0.0
-}
unsafeFromByteString :: BS.ByteString -> PgTextFormatValue
unsafeFromByteString :: ByteString -> PgTextFormatValue
unsafeFromByteString =
  ByteString -> PgTextFormatValue
AssumedToHaveNoNULValues

{- |
  Constructs a 'PgTextFormatValue' from the given bytes, which will be checked
  to ensure none of them are '\NUL' before being passed to LibPQ. If a '\NUL'
  byte is found an error will be raised.

@since 1.0.0.0
-}
fromByteString :: BS.ByteString -> PgTextFormatValue
fromByteString :: ByteString -> PgTextFormatValue
fromByteString =
  ByteString -> PgTextFormatValue
NoAssumptionsMade

{- |
  Converts the 'PgTextFormatValue' to bytes intended to be passed to LibPQ.
  If any '\NUL' bytes are found, 'NULByteFoundError' will be returned (unless
  'unsafeFromByteString' was used to construct the value).

@since 1.0.0.0
-}
toBytesForLibPQ :: PgTextFormatValue -> Either NULByteFoundError BS.ByteString
toBytesForLibPQ :: PgTextFormatValue -> Either NULByteFoundError ByteString
toBytesForLibPQ PgTextFormatValue
value =
  case PgTextFormatValue
value of
    AssumedToHaveNoNULValues ByteString
noNULBytes ->
      ByteString -> Either NULByteFoundError ByteString
forall a b. b -> Either a b
Right ByteString
noNULBytes
    NoAssumptionsMade ByteString
anyBytes ->
      if Word8 -> ByteString -> Bool
BS.elem Word8
0 ByteString
anyBytes
        then NULByteFoundError -> Either NULByteFoundError ByteString
forall a b. a -> Either a b
Left NULByteFoundError
NULByteFoundError
        else ByteString -> Either NULByteFoundError ByteString
forall a b. b -> Either a b
Right ByteString
anyBytes

{- |
  Converts the 'PgTextFormatValue' back to the bytes that were used to
  construct it, losing the information about whether it would be checked
  for '\NUL' bytes or not.

@since 1.0.0.0
-}
toByteString :: PgTextFormatValue -> BS.ByteString
toByteString :: PgTextFormatValue -> ByteString
toByteString PgTextFormatValue
value =
  case PgTextFormatValue
value of
    AssumedToHaveNoNULValues ByteString
bytes ->
      ByteString
bytes
    NoAssumptionsMade ByteString
bytes ->
      ByteString
bytes