Skip to content

Improvement/ Documentation added in few modules #27

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
192 changes: 183 additions & 9 deletions src/EulerHS/CachedSqlDBQuery.hs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,19 @@ cacheName = "eulerKVDB"
-- | Create a new database entry with the given value.
-- Cache the value if the DB insert succeeds.

{-
Typeclass for executing SQL queries with returning results.

This typeclass defines a common interface for executing SQL queries that return results using different database backends.

Methods:
- 'createReturning': Executes an SQL query to create a record and returns the created record.

Instances:
- 'SqlReturning BM.MySQLM BM.MySQL': Instance for MySQL database backend.
- 'SqlReturning BP.Pg BP.Postgres': Instance for PostgreSQL database backend.
- 'SqlReturning BS.SqliteM BS.Sqlite': Instance for SQLite database backend.
-}
class SqlReturning (beM :: Type -> Type) (be :: Type) where
createReturning ::
forall (table :: (Type -> Type) -> Type)
Expand Down Expand Up @@ -70,7 +83,20 @@ instance SqlReturning BP.Pg BP.Postgres where
instance SqlReturning BS.SqliteM BS.Sqlite where
createReturning = create

{-
Executes an SQL query to create a record and handles caching.

This function is a generic implementation of creating a record in the database. It utilizes the 'createSql' function
to perform the SQL query and handles caching of the created record based on the provided cache key.

Parameters:
- 'dbConf': The 'DBConfig' specifying the database connection details.
- 'value': The record to be created in the database.
- 'mCacheKey': Optional cache key for caching the created record.

Returns:
- An action in the 'L.MonadFlow' monad, producing a 'Either DBError (table Identity)' result.
-}
create ::
forall (be :: Type)
(beM :: Type -> Type)
Expand All @@ -86,10 +112,10 @@ create ::
Show (table Identity),
L.MonadFlow m
) =>
DBConfig beM ->
table Identity ->
Maybe Text ->
m (Either DBError (table Identity))
DBConfig beM -> -- Database configuration for the connection.
table Identity -> -- Record to be created in the database.
Maybe Text -> -- Optional cache key for caching the created record
m (Either DBError (table Identity)) -- Result in the 'L.MonadFlow' monad, containing either an error or the created record.
create dbConf value mCacheKey = do
res <- createSql dbConf value
case res of
Expand Down Expand Up @@ -166,6 +192,18 @@ updateOneWoReturning dbConf (Just _) newVals whereClause = do
return val
updateOneWoReturning dbConf Nothing value whereClause = updateOneSqlWoReturning dbConf value whereClause

{-|
Update one or more elements in the database without returning any specific field.

Parameters:
- 'dbConf': The 'DBConfig' representing the database connection.
- 'newVals': A list of 'Set' operations to modify the values in the target table. Each 'Set' operation represents a field update.
- 'whereClause': The 'Where' clause specifying the conditions for the update.

Returns:
- 'Right ()' if the update is successful.
- 'Left' with 'DBError' if there's an error.
-}
updateOneSqlWoReturning ::
forall m be beM table.
( HasCallStack,
Expand Down Expand Up @@ -197,6 +235,18 @@ updateOneSqlWoReturning dbConf newVals whereClause = do
-- return $ Left $ DBError UnexpectedResult message
Left e -> return $ Left e

{-|
Update one or more records in the database.

Parameters:
- 'dbConf': The 'DBConfig' representing the database connection.
- 'newVals': A list of 'Set' operations to modify the values in the target table. Each 'Set' operation represents a field update.
- 'whereClause': The 'Where' clause specifying the conditions for the update.

Returns:
- 'Right val' if the update is successful, where 'val' is the value of the field specified in the 'RETURNING' clause of the SQL query.
- 'Left' with 'DBError' if there's an error.
-}
updateOneSql ::
forall m be beM table.
( HasCallStack,
Expand Down Expand Up @@ -234,8 +284,31 @@ updateExtended dbConf mKey upd = do
maybe (pure ()) (`cacheWithKey` res) mKey
pure res

-- | Find an element matching the query. Only uses the DB if the cache is empty.
-- Caches the result using the given key.
{-|
Finds an element matching the query. Only uses the DB if the cache is empty.
Caches the result using the given key.

Parameters:
- 'dbConf': The 'DBConfig' representing the database connection.
- 'cacheKey': Optional key to cache the result. If 'Just', attempts to retrieve the result from the cache.
- 'whereClause': The 'Where' clause specifying the conditions.

Returns:
- 'Right (Just val)' if a record is found, where 'val' is the found record.
- 'Right Nothing' if no record is found.
- 'Left' with 'DBError' if there's an error.

Example:
> myFlow = do
> let dbConfig = -- your DBConfig here --
> let cacheKey = -- your cache key here --
> let conditions = -- your Where clause here --
> result <- findOne dbConfig cacheKey conditions
> case result of
> Right (Just foundRecord) -> logInfoT "Found record" $ show foundRecord
> Right Nothing -> logInfoT "No record found"
> Left error -> logErrorT "Query error" $ show error
-}
findOne ::
( HasCallStack,
BeamRuntime be beM,
Expand All @@ -260,9 +333,21 @@ findOne dbConf (Just cacheKey) whereClause = do
return mDBRes
findOne dbConf Nothing whereClause = findOneSql dbConf whereClause

-- | Find all elements matching the query. Only uses the DB if the cache is empty.
-- Caches the result using the given key.
-- NOTE: Can't use the same key as findOne, updateOne or create since it's result is a list.
{-|
Finds all elements matching the query. Only uses the DB if the cache is empty.
Caches the result using the given key.

NOTE: Can't use the same key as 'findOne', 'updateOne', or 'create' since its result is a list.

Parameters:
- 'dbConf': The 'DBConfig' representing the database connection.
- 'cacheKey': Optional key to cache the result. If 'Just', attempts to retrieve the result from the cache.
- 'whereClause': The 'Where' clause specifying the conditions.

Returns:
- 'Right [val]' if records are found, where 'val' is a list of found records.
- 'Left' with 'DBError' if there's an error.
-}
findAll ::
( HasCallStack,
BeamRuntime be beM,
Expand Down Expand Up @@ -317,6 +402,9 @@ findAllExtended dbConf mKey sel = case mKey of
join <$> traverse (\conn -> L.runDB conn . DB.findRows $ sel) eConn

------------ Helper functions ------------
{-
Executes an SQL query using a database connection.
-}
runQuery ::
( HasCallStack,
BeamRuntime be beM, BeamRunner beM,
Expand All @@ -342,13 +430,48 @@ runQueryMySQL dbConf query = do
Right c -> L.runTransaction c query
Left e -> return $ Left e

{-
Generates a SQL insertion query for creating a record in the database.

This function takes a record of type 'table Identity' and generates a SQL insertion query using the 'B.insert' function.
It utilizes the 'modelTableEntity' to define the target table for insertion.

Parameters:
- 'value': The record to be inserted into the database.

Returns:
- A 'B.SqlInsert be table' representing the SQL insertion query.
-}
sqlCreate ::
forall be table.
(HasCallStack, B.HasQBuilder be, Model be table) =>
table Identity ->
B.SqlInsert be table
sqlCreate value = B.insert modelTableEntity (mkExprWithDefault value)

{-
Runs a SQL insertion query using the provided 'DBConfig' and returns the inserted record.

This function takes a 'DBConfig' for database configuration and a record of type 'table Identity' to be inserted.
It executes the insertion query using 'DB.insertRowsReturningList' and 'sqlCreate', and returns the inserted record.

Parameters:
- 'dbConf': The 'DBConfig' representing the database connection.
- 'value': The record to be inserted into the database.

Returns:
- 'Right val' if the insertion is successful, where 'val' is the inserted record.
- 'Left (DBError UnexpectedResult message)' if the database returns an unexpected result.

Example:
> myFlow = do
> let dbConfig = -- your DBConfig here --
> let myRecord = -- your record here --
> result <- createSql dbConfig myRecord
> case result of
> Right insertedRecord -> logInfoT "Inserted record" $ show insertedRecord
> Left error -> logErrorT "Insert error" $ show error
-}
createSql ::
forall m be beM table.
( HasCallStack,
Expand Down Expand Up @@ -396,6 +519,32 @@ createSqlMySQL dbConf value = do
return $ Left $ DBError UnexpectedResult message
Left e -> return $ Left e

{-|
Runs a SQL query to find a single record in the database based on the provided 'DBConfig' and 'Where' clause.

This function takes a 'DBConfig' for database configuration and a 'Where' clause specifying the conditions.
It executes the query using 'DB.findRow' and 'sqlSelect', returning 'Right (Just val)' if a record is found,
'Right Nothing' if no records match the conditions, and 'Left' if there's an error.

Parameters:
- 'dbConf': The 'DBConfig' representing the database connection.
- 'whereClause': The 'Where' clause specifying the conditions.

Returns:
- 'Right (Just val)' if a record is found, where 'val' is the found record.
- 'Right Nothing' if no records match the conditions.
- 'Left' with 'DBError' if there's an error.

Example:
> myFlow = do
> let dbConfig = -- your DBConfig here --
> let conditions = -- your Where clause here --
> result <- findOneSql dbConfig conditions
> case result of
> Right (Just foundRecord) -> logInfoT "Found record" $ show foundRecord
> Right Nothing -> logInfoT "No matching records found" ""
> Left error -> logErrorT "Query error" $ show error
-}
findOneSql ::
( HasCallStack,
BeamRuntime be beM,
Expand All @@ -412,6 +561,31 @@ findOneSql ::
findOneSql dbConf whereClause = runQuery dbConf findQuery
where findQuery = DB.findRow (sqlSelect ! #where_ whereClause ! defaults)


{-|
Runs a SQL query to find all records in the database based on the provided 'Where' clause.

This function takes a 'DBConfig' for database configuration and a 'Where' clause specifying the conditions.
It executes the query using 'DB.findRows' and 'sqlSelect', returning 'Right [val]' if records are found,
and 'Left' if there's an error.

Parameters:
- 'dbConf': The 'DBConfig' representing the database connection.
- 'whereClause': The 'Where' clause specifying the conditions.

Returns:
- 'Right [val]' if records are found, where 'val' is a list of found records.
- 'Left' with 'DBError' if there's an error.

Example:
> myFlow = do
> let dbConfig = -- your DBConfig here --
> let conditions = -- your Where clause here --
> result <- findAllSql dbConfig conditions
> case result of
> Right foundRecords -> logInfoT "Found records" $ show foundRecords
> Left error -> logErrorT "Query error" $ show error
-}
findAllSql ::
( HasCallStack,
BeamRuntime be beM,
Expand Down
63 changes: 63 additions & 0 deletions src/EulerHS/Core/Logger/Impl/TinyLogger.hs
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ data LoggerHandle
| SyncLoggerHandle Loggers
| VoidLoggerHandle

-- | Maps the custom log level type 'T.LogLevel' to the standard log level type 'Log.Level'.
dispatchLogLevel :: T.LogLevel -> Log.Level
dispatchLogLevel T.Debug = Log.Debug
dispatchLogLevel T.Info = Log.Info
dispatchLogLevel T.Warning = Log.Warn
dispatchLogLevel T.Error = Log.Error

-- | Logs a pending message using the provided flow formatter and loggers.
logPendingMsg :: T.FlowFormatter -> Loggers -> T.PendingMsg -> IO ()
logPendingMsg flowFormatter loggers pendingMsg@(T.PendingMsg mbFlowGuid lvl tag msg msgNum logContext) = do
formatter <- flowFormatter mbFlowGuid
Expand All @@ -53,11 +55,13 @@ logPendingMsg flowFormatter loggers pendingMsg@(T.PendingMsg mbFlowGuid lvl tag
T.MsgTransformer f -> f
mapM_ (\logger -> Log.log logger lvl' msg') loggers

-- | Worker function for the logger, reads pending messages from the channel and logs them.
loggerWorker :: T.FlowFormatter -> Chan.OutChan T.PendingMsg -> Loggers -> IO ()
loggerWorker flowFormatter outChan loggers = do
pendingMsg <- Chan.readChan outChan
logPendingMsg flowFormatter loggers pendingMsg

-- | Sends a pending message to the appropriate logger based on the logger handle.
sendPendingMsg :: T.FlowFormatter -> LoggerHandle -> T.PendingMsg -> IO ()
sendPendingMsg _ VoidLoggerHandle = const (pure ())
sendPendingMsg flowFormatter (SyncLoggerHandle loggers) = logPendingMsg flowFormatter loggers
Expand All @@ -66,9 +70,46 @@ sendPendingMsg _ (AsyncLoggerHandle _ (inChan, _) _) = Chan.writeChan inChan
createVoidLogger :: IO LoggerHandle
createVoidLogger = pure VoidLoggerHandle

{-|
Create a logger with the provided flow formatter and logger configuration.
This is the main entry point for creating loggers.

=== Parameters:

* @T.FlowFormatter@: Flow formatter used for formatting log messages.

* @T.LoggerConfig@: Logger configuration specifying various settings for the logger.

=== Returns:

An 'IO' action that produces a 'LoggerHandle'.

-}
createLogger :: T.FlowFormatter -> T.LoggerConfig -> IO LoggerHandle
createLogger = createLogger' defaultDateFormat defaultRenderer defaultBufferSize

{-|
Create a logger with additional settings such as date format, renderer, and buffer size.

NOTE : a Renderer typically refers to a component or function responsible for rendering log messages into a specific format or representation. It determines how the log messages are formatted before being output to the desired output channel, such as a console, file, or another logging endpoint.

=== Parameters:

* @Maybe Log.DateFormat@: Optional date format for log messages.

* @Maybe Log.Renderer@: Optional renderer for log messages.

* @T.BufferSize@: Buffer size for log messages.

* @T.FlowFormatter@: Flow formatter used for formatting log messages.

* @T.LoggerConfig@: Logger configuration specifying various settings for the logger.

=== Returns:

An 'IO' action that produces a 'LoggerHandle'.

-}
createLogger'
:: Maybe Log.DateFormat
-> Maybe Log.Renderer
Expand Down Expand Up @@ -119,6 +160,15 @@ createLogger'
threadIds <- traverse ((flip forkOn) (forever $ loggerWorker flowFormatter outChan loggers)) [1..caps]
pure $ AsyncLoggerHandle threadIds chan loggers

{-
Disposes resources associated with a logger handle.

This function handles the disposal of resources based on the type of logger handle:
- For 'VoidLoggerHandle', no action is performed.
- For 'SyncLoggerHandle', it flushes and closes each logger in the handle.
- For 'AsyncLoggerHandle', it kills the associated threads, logs any remaining messages in the output channel,
flushes and closes each logger in the handle.
-}
disposeLogger :: T.FlowFormatter -> LoggerHandle -> IO ()
disposeLogger _ VoidLoggerHandle = pure ()
disposeLogger _ (SyncLoggerHandle loggers) = do
Expand All @@ -142,6 +192,19 @@ disposeLogger flowFormatter (AsyncLoggerHandle threadIds (_, outChan) loggers) =
logRemaining oc
Nothing -> pure ()

{-

Convenience function to create a logger with specified settings and run an action with the obtained logger.

Acquires a logger using 'createLogger'' or 'createLogger', runs the provided
action, and ensures proper disposal of the logger afterward. Handles exceptions
that might occur during resource acquisition, action execution, or resource release.

NOTE: 'bracket' is used here to manage the lifecycle of the logger. The first
argument acquires the resource (creates the logger), the second argument releases
the resource (disposes of the logger).

-}
withLogger'
:: Maybe Log.DateFormat
-> Maybe Log.Renderer
Expand Down
Loading