packages feed

pg-query-0.0.1.0: src/PgQuery/Internal/Parse.hs

{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NoImplicitPrelude #-}

module PgQuery.Internal.Parse where

import Control.Applicative (pure)
import Control.Monad.Fail (fail)
import Data.Bool (Bool (False, True))
import Data.ByteString (ByteString, packCStringLen)
import Data.Either (Either (Left, Right))
import Data.Eq (Eq, (==))
import Data.Function (($))
import Data.String (String)
import Foreign
  ( Ptr,
    Storable
      ( alignment,
        peek,
        peekByteOff,
        poke,
        sizeOf
      ),
    nullPtr,
  )
import Foreign.C
  ( CInt,
    CSize,
    CString,
    peekCString,
    withCString,
  )
import Foreign.C.Types (CSize (..))
import GHC.Err (undefined)
import GHC.IO (IO)
import GHC.Num ((*), (+))
import GHC.Real (fromIntegral)
import GHC.Show (Show)

-- | Data type corresponding to the following struct in 'pg_query.h':
--
--   @
--     typedef struct {
--       size_t len;
--       char* data;
--     } PgQueryProtobuf;
--   @
data PgQueryProtobuf = PgQueryProtobuf
  { len :: !CSize
  , protobufData :: !CString
  }
  deriving (Show, Eq)

instance Storable PgQueryProtobuf where
  sizeOf _ =
    sizeOf (undefined :: CSize)
      + sizeOf (undefined :: CString)

  alignment _ = alignment (undefined :: PgQueryProtobuf)

  peek ptr = do
    len <- peekByteOff ptr 0
    protobufData <- peekByteOff ptr (1 * sizeOf (undefined :: CSize))
    pure
      PgQueryProtobuf
        { len
        , protobufData
        }

  poke _ _ = fail "PgQueryProtobuf poke: not supported"

-- | Data type corresponding to the following struct in 'pg_query.h':
--
--   @
--     typedef struct {
--     	char* message; // exception message
--     	char* funcname; // source function of exception (e.g. SearchSysCache)
--     	char* filename; // source of exception (e.g. parse.l)
--     	int lineno; // source of exception (e.g. 104)
--     	int cursorpos; // char in query at which exception occurred
--     	char* context; // additional context (optional, can be NULL)
--     } PgQueryError;
--   @
data PgQueryError = PgQueryError
  { message :: !CString
  , funcname :: !CString
  , filename :: !CString
  , lineno :: !CInt
  , cursorpos :: !CInt
  , context :: !CString
  }
  deriving (Show, Eq)

instance Storable PgQueryError where
  sizeOf _ =
    sizeOf (undefined :: CString)
      + sizeOf (undefined :: CString)
      + sizeOf (undefined :: CString)
      + sizeOf (undefined :: CInt)
      + sizeOf (undefined :: CInt)
      + sizeOf (undefined :: CString)

  alignment _ = alignment (undefined :: PgQueryError)

  peek ptr = do
    message <- peekByteOff ptr 0
    funcname <- peekByteOff ptr (1 * sizeOf (undefined :: CString))
    filename <- peekByteOff ptr (2 * sizeOf (undefined :: CString))
    lineno <- peekByteOff ptr (3 * sizeOf (undefined :: CString))
    cursorpos <- peekByteOff ptr ((3 * sizeOf (undefined :: CString)) + sizeOf (undefined :: CInt))
    context <- peekByteOff ptr ((4 * sizeOf (undefined :: CString)) + (2 * sizeOf (undefined :: CInt)))
    pure
      PgQueryError
        { message
        , funcname
        , filename
        , lineno
        , cursorpos
        , context
        }

  poke _ _ = fail "PgQueryError poke: not supported"

-- | Data type corresponding to the following struct in 'pg_query.h':
--
--   @
--     typedef struct {
--       PgQueryProtobuf parse_tree;
--       char* stderr_buffer;
--       PgQueryError* error;
--     } PgQueryProtobufParseResult;
--   @
data PgQueryProtobufParseResult = PgQueryProtobufParseResult
  { parse_tree :: !PgQueryProtobuf
  , stderr_buffer :: !CString
  , pg_query_error :: !(Ptr PgQueryError)
  }
  deriving (Show)

instance Storable PgQueryProtobufParseResult where
  sizeOf _ =
    sizeOf (undefined :: PgQueryProtobuf)
      + sizeOf (undefined :: CString)
      + sizeOf (undefined :: Ptr PgQueryError)

  alignment _ = alignment (undefined :: PgQueryProtobufParseResult)

  peek ptr = do
    parse_tree <- peekByteOff ptr 0
    stderr_buffer <- peekByteOff ptr (sizeOf (undefined :: PgQueryProtobuf))
    pg_query_error <- peekByteOff ptr (sizeOf (undefined :: PgQueryProtobuf) + sizeOf (undefined :: CString))
    pure
      PgQueryProtobufParseResult
        { parse_tree
        , stderr_buffer
        , pg_query_error
        }

  poke _ _ = fail "PgQueryProtobufParseResult poke: not supported"

foreign import ccall "get_sql"
  get_sql ::
    CString ->
    IO (Ptr PgQueryProtobufParseResult)

foreign import ccall "free_sql"
  free_sql ::
    Ptr PgQueryProtobufParseResult ->
    IO ()

getProtobufParseResult :: String -> IO (Either String ByteString)
getProtobufParseResult sql = do
  resultPtr <- withCString sql get_sql
  result <- peek resultPtr

  let errPtr = pg_query_error result
  case errPtr == nullPtr of
    True -> do
      let tree = parse_tree result
      protobufResult <- packCStringLen (protobufData tree, fromIntegral $ len tree)

      free_sql resultPtr

      pure $ Right protobufResult
    False -> do
      errorResult <- peek errPtr
      errorMessage <- peekCString (message errorResult)

      free_sql resultPtr

      pure $ Left errorMessage