Skip to content

Add Test Cases for Advanced Queuing with Object type and JSON #49

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions oracle-simple.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ library
Database.Oracle.Simple.ToRow
Database.Oracle.Simple.Transaction
Database.Oracle.Simple.Queue
Database.Oracle.Simple.Object
hs-source-dirs:
src
c-sources:
Expand Down
1 change: 1 addition & 0 deletions src/Database/Oracle/Simple.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ import Database.Oracle.Simple.ToField as Export
import Database.Oracle.Simple.ToRow as Export
import Database.Oracle.Simple.Transaction as Export
import Database.Oracle.Simple.Queue as Export
import Database.Oracle.Simple.Object as Export
81 changes: 4 additions & 77 deletions src/Database/Oracle/Simple/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,7 @@ module Database.Oracle.Simple.Internal
OracleError (..),
ErrorInfo (..),
VersionInfo (..),
DPIJson (..),
DPIObjectType (..),
DPIObject (..),
genJSON,
genObject,
getObjectType,
renderErrorInfo,
releaseObject,
ping,
fetch,
close,
Expand All @@ -70,6 +63,7 @@ module Database.Oracle.Simple.Internal
bindValueByPos,
freeWriteBuffer,
mkDPIBytesUTF8,
mkStringFromDPIBytesUTF8,
isHealthy,
dpiTimeStampToUTCDPITimeStamp,
throwOracleError,
Expand Down Expand Up @@ -127,18 +121,6 @@ newtype DPIShardingKeyColumn = DPIShardingKeyColumn (Ptr DPIShardingKeyColumn)
deriving (Show, Eq)
deriving newtype (Storable)

newtype DPIJson = DPIJson (Ptr DPIJson)
deriving (Show, Eq)
deriving newtype (Storable)

newtype DPIObjectType = DPIObjectType (Ptr DPIObjectType)
deriving (Show, Eq)
deriving newtype (Storable)

newtype DPIObject = DPIObject (Ptr DPIObject)
deriving (Show, Eq)
deriving newtype (Storable)

data AdditionalConnectionParams = AdditionalConnectionParams
{ minSessions :: Natural
, maxSessions :: Natural
Expand Down Expand Up @@ -1134,6 +1116,9 @@ mkDPIBytesUTF8 str = do
dpiBytesEncoding <- newCString "UTF-8"
pure $ DPIBytes {..}

mkStringFromDPIBytesUTF8 :: DPIBytes -> IO String
mkStringFromDPIBytesUTF8 DPIBytes{..} = peekCString dpiBytesPtr

data DPIIntervalDS = DPIIntervalDS
{ days :: CInt
, hours :: CInt
Expand Down Expand Up @@ -1773,62 +1758,4 @@ newtype Only a = Only {fromOnly :: a}
deriving stock (Eq, Ord, Read, Show, Generic)
deriving newtype (Enum)

genJSON :: Connection -> IO DPIJson
genJSON (Connection fptr) = do
withForeignPtr fptr $ \conn -> do
alloca $ \jsonPtr -> do
throwOracleError =<< dpiConn_newJson conn jsonPtr
peek jsonPtr

foreign import ccall unsafe "dpiConn_newJson"
dpiConn_newJson ::
-- | dpiConn *
Ptr DPIConn ->
-- | dpiJSON **
Ptr DPIJson ->
IO CInt

getObjectType :: Connection -> String -> IO DPIObjectType
getObjectType (Connection fptr) objectName = do
withForeignPtr fptr $ \conn -> do
withCStringLen objectName $ \(objectNameC, fromIntegral -> objectNameLen) -> do
alloca $ \objectTypePtr -> do
throwOracleError =<< dpiConn_getObjectType conn objectNameC objectNameLen objectTypePtr
peek objectTypePtr

foreign import ccall unsafe "dpiConn_getObjectType"
dpiConn_getObjectType ::
-- | dpiConn *
Ptr DPIConn ->
-- | char * name
CString ->
-- | cuint32_t nameLength
CUInt ->
-- | dpiObjectType ** objType
Ptr DPIObjectType ->
IO CInt

genObject :: DPIObjectType -> IO DPIObject
genObject objType = do
alloca $ \objectPtr -> do
throwOracleError =<< dpiObjectType_createObject objType objectPtr
peek objectPtr

foreign import ccall unsafe "dpiObjectType_createObject"
dpiObjectType_createObject ::
-- | dpiObjectType *
DPIObjectType ->
-- | dpiObject ** obj
Ptr DPIObject ->
IO CInt

releaseObject :: DPIObject -> IO ()
releaseObject obj = do
throwOracleError =<< dpiObject_release obj

foreign import ccall unsafe "dpiObject_release"
dpiObject_release ::
-- | dpiObject *
DPIObject ->
IO CInt

101 changes: 48 additions & 53 deletions src/Database/Oracle/Simple/JSON.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -Wno-missed-specialisations #-} -- suppressing fromFloatDigits warning

module Database.Oracle.Simple.JSON (AesonField (..), JsonDecodeError (..), DPIJsonNode(..), getJson) where
module Database.Oracle.Simple.JSON (AesonField (..), JsonDecodeError (..), DPIJsonNode(..), getJson, DPIJson(..), dpiJson_getValue, parseJson) where

import Control.Exception (Exception (displayException), SomeException, catch, evaluate, throwIO)
import Control.Monad (void, (<=<))
Expand Down Expand Up @@ -78,58 +78,53 @@ instance (Aeson.FromJSON a) => FromField (AesonField a) where

getJson :: (Aeson.FromJSON a) => ReadDPIBuffer a
getJson = parseJson <=< peek <=< dpiJson_getValue <=< dpiData_getJson
where
parseJson topNode = do
aesonValue <- buildValue topNode
case Aeson.fromJSON aesonValue of
Aeson.Error msg -> throwIO $ ParseError msg
Aeson.Success a -> pure a

-- Build Aeson values for various cases:

-- Object
buildValue (DPIJsonNode _ DPI_NATIVE_TYPE_JSON_OBJECT nodeValue) = do
DPIJsonObject {..} <- peek =<< dpiDataBuffer_getAsJsonObject nodeValue
fieldNamePtrs <- peekArray (fromIntegral djoNumFields) djoFieldNames
fieldNameLengths <- fmap fromIntegral <$> peekArray (fromIntegral djoNumFields) djoFieldNameLengths
ks <- mapM (fmap fromString . peekCStringLen) (zip fieldNamePtrs fieldNameLengths)
values <- mapM buildValue =<< peekArray (fromIntegral djoNumFields) djoFields
pure $ Aeson.Object $ KeyMap.fromList (zip ks values)

-- Array
buildValue (DPIJsonNode _ DPI_NATIVE_TYPE_JSON_ARRAY nodeValue) = do
DPIJsonArray {..} <- peek =<< dpiDataBuffer_getAsJsonArray nodeValue
values <- mapM buildValue =<< peekArray (fromIntegral djaNumElements) djaElements
pure $ Aeson.Array $ Vector.fromList values

-- Number returned as DPIBytes
buildValue (DPIJsonNode DPI_ORACLE_TYPE_NUMBER DPI_NATIVE_TYPE_BYTES nodeValue) = do
DPIBytes {..} <- peek =<< dpiDataBuffer_getAsBytes nodeValue
bytes <- BS.packCStringLen (dpiBytesPtr, fromIntegral dpiBytesLength)
let numStr = C8.unpack bytes
number <- evaluate (read numStr) `catch` (\(_ :: SomeException) -> throwIO $ InvalidNumber numStr)
pure $ Aeson.Number number

-- String
buildValue (DPIJsonNode _ DPI_NATIVE_TYPE_BYTES nodeValue) = do
DPIBytes {..} <- peek =<< dpiDataBuffer_getAsBytes nodeValue
bytes <- BS.packCStringLen (dpiBytesPtr, fromIntegral dpiBytesLength)
pure $ Aeson.String (decodeUtf8 bytes)

-- Number encoded as Double (will not fire as dpiJsonOptions_numberAsString is set)
buildValue (DPIJsonNode _ DPI_NATIVE_TYPE_DOUBLE nodeValue) = do
doubleVal <- dpiDataBuffer_getAsDouble nodeValue
pure $ Aeson.Number $ fromFloatDigits doubleVal

-- Boolean literals (true, false)
buildValue (DPIJsonNode _ DPI_NATIVE_TYPE_BOOLEAN nodeValue) = do
intVal <- dpiDataBuffer_getAsBoolean nodeValue
pure $ Aeson.Bool (intVal == 1)

-- Null literal (null)
buildValue (DPIJsonNode _ DPI_NATIVE_TYPE_NULL _) = pure Aeson.Null
-- All other DPI native types
buildValue (DPIJsonNode _ nativeType _) = throwIO $ UnsupportedDPINativeType nativeType

parseJson :: Aeson.FromJSON b => DPIJsonNode -> IO b
parseJson topNode = do
aesonValue <- buildValue topNode
case Aeson.fromJSON aesonValue of
Aeson.Error msg -> throwIO $ ParseError msg
Aeson.Success a -> pure a

-- Build Aeson values for various cases:
-- Object
buildValue :: DPIJsonNode -> IO Aeson.Value
buildValue (DPIJsonNode _ DPI_NATIVE_TYPE_JSON_OBJECT nodeValue) = do
DPIJsonObject {..} <- peek =<< dpiDataBuffer_getAsJsonObject nodeValue
fieldNamePtrs <- peekArray (fromIntegral djoNumFields) djoFieldNames
fieldNameLengths <- fmap fromIntegral <$> peekArray (fromIntegral djoNumFields) djoFieldNameLengths
ks <- mapM (fmap fromString . peekCStringLen) (zip fieldNamePtrs fieldNameLengths)
values <- mapM buildValue =<< peekArray (fromIntegral djoNumFields) djoFields
pure $ Aeson.Object $ KeyMap.fromList (zip ks values)
-- Array
buildValue (DPIJsonNode _ DPI_NATIVE_TYPE_JSON_ARRAY nodeValue) = do
DPIJsonArray {..} <- peek =<< dpiDataBuffer_getAsJsonArray nodeValue
values <- mapM buildValue =<< peekArray (fromIntegral djaNumElements) djaElements
pure $ Aeson.Array $ Vector.fromList values
-- Number returned as DPIBytes
buildValue (DPIJsonNode DPI_ORACLE_TYPE_NUMBER DPI_NATIVE_TYPE_BYTES nodeValue) = do
DPIBytes {..} <- peek =<< dpiDataBuffer_getAsBytes nodeValue
bytes <- BS.packCStringLen (dpiBytesPtr, fromIntegral dpiBytesLength)
let numStr = C8.unpack bytes
number <- evaluate (read numStr) `catch` (\(_ :: SomeException) -> throwIO $ InvalidNumber numStr)
pure $ Aeson.Number number
-- String
buildValue (DPIJsonNode _ DPI_NATIVE_TYPE_BYTES nodeValue) = do
DPIBytes {..} <- peek =<< dpiDataBuffer_getAsBytes nodeValue
bytes <- BS.packCStringLen (dpiBytesPtr, fromIntegral dpiBytesLength)
pure $ Aeson.String (decodeUtf8 bytes)
-- Number encoded as Double (will not fire as dpiJsonOptions_numberAsString is set)
buildValue (DPIJsonNode _ DPI_NATIVE_TYPE_DOUBLE nodeValue) = do
doubleVal <- dpiDataBuffer_getAsDouble nodeValue
pure $ Aeson.Number $ fromFloatDigits doubleVal
-- Boolean literals (true, false)
buildValue (DPIJsonNode _ DPI_NATIVE_TYPE_BOOLEAN nodeValue) = do
intVal <- dpiDataBuffer_getAsBoolean nodeValue
pure $ Aeson.Bool (intVal == 1)
-- Null literal (null)
buildValue (DPIJsonNode _ DPI_NATIVE_TYPE_NULL _) = pure Aeson.Null
-- All other DPI native types
buildValue (DPIJsonNode _ nativeType _) = throwIO $ UnsupportedDPINativeType nativeType

newtype DPIJson = DPIJson (Ptr DPIJson)
deriving (Show, Eq)
Expand Down
Loading
Loading