{- | Copyright : Flipstone Technology Partners 2023 License : MIT Stability : Stable Entry-level functions for executing based CRUD operations on entity tables. These are the functions that you will use most often for interacting with tables. @since 1.0.0.0 -} module Orville.PostgreSQL.Execution.EntityOperations ( insertEntity , insertEntityAndReturnRowCount , insertAndReturnEntity , insertEntities , insertAndReturnEntities , insertEntitiesAndReturnRowCount , updateEntity , updateEntityAndReturnRowCount , updateAndReturnEntity , updateFields , updateFieldsAndReturnEntities , updateFieldsAndReturnRowCount , deleteEntity , deleteEntityAndReturnRowCount , deleteAndReturnEntity , deleteEntities , deleteEntitiesAndReturnRowCount , deleteAndReturnEntities , findEntitiesBy , findFirstEntityBy , findEntity , findEntities ) where import Control.Exception (Exception, throwIO) import qualified Control.Monad as Monad import Control.Monad.IO.Class (MonadIO (liftIO)) import Data.List.NonEmpty (NonEmpty ((:|))) import Data.Maybe (listToMaybe) import qualified Orville.PostgreSQL.Execution.Delete as Delete import qualified Orville.PostgreSQL.Execution.Insert as Insert import qualified Orville.PostgreSQL.Execution.Select as Select import qualified Orville.PostgreSQL.Execution.SelectOptions as SelectOptions import qualified Orville.PostgreSQL.Execution.Update as Update import qualified Orville.PostgreSQL.Expr as Expr import qualified Orville.PostgreSQL.Internal.RowCountExpectation as RowCountExpectation import qualified Orville.PostgreSQL.Monad as Monad import qualified Orville.PostgreSQL.Schema as Schema {- | Inserts an entity into the specified table. @since 1.0.0.0 -} insertEntity :: Monad.MonadOrville m => Schema.TableDefinition key writeEntity readEntity -> writeEntity -> m () insertEntity entityTable = Monad.void . insertEntityAndReturnRowCount entityTable {- | Inserts an entity into the specified table. Returns the number of rows affected by the query. @since 1.0.0.0 -} insertEntityAndReturnRowCount :: Monad.MonadOrville m => Schema.TableDefinition key writeEntity readEntity -> writeEntity -> m Int insertEntityAndReturnRowCount entityTable entity = insertEntitiesAndReturnRowCount entityTable (entity :| []) {- | Inserts an entity into the specified table, returning the data inserted into the database. You can use this function to obtain any column values filled in by the database, such as auto-incrementing ids. @since 1.0.0.0 -} insertAndReturnEntity :: Monad.MonadOrville m => Schema.TableDefinition key writeEntity readEntity -> writeEntity -> m readEntity insertAndReturnEntity entityTable entity = do returnedEntities <- insertAndReturnEntities entityTable (entity :| []) RowCountExpectation.expectExactlyOneRow "insertAndReturnEntity RETURNING clause" returnedEntities {- | Inserts a non-empty list of entities into the specified table. @since 1.0.0.0 -} insertEntities :: Monad.MonadOrville m => Schema.TableDefinition key writeEntity readEntity -> NonEmpty writeEntity -> m () insertEntities tableDef = Monad.void . insertEntitiesAndReturnRowCount tableDef {- | Inserts a non-empty list of entities into the specified table. Returns the number of rows affected by the query. @since 1.0.0.0 -} insertEntitiesAndReturnRowCount :: Monad.MonadOrville m => Schema.TableDefinition key writeEntity readEntity -> NonEmpty writeEntity -> m Int insertEntitiesAndReturnRowCount tableDef = Insert.executeInsert . Insert.insertToTable tableDef {- | Inserts a non-empty list of entities into the specified table, returning the data that was inserted into the database. You can use this function to obtain any column values filled in by the database, such as auto-incrementing ids. @since 1.0.0.0 -} insertAndReturnEntities :: Monad.MonadOrville m => Schema.TableDefinition key writeEntity readEntity -> NonEmpty writeEntity -> m [readEntity] insertAndReturnEntities tableDef = Insert.executeInsertReturnEntities . Insert.insertToTableReturning tableDef {- | Updates the row with the given key with the data given by @writeEntity@. @since 1.0.0.0 -} updateEntity :: Monad.MonadOrville m => Schema.TableDefinition (Schema.HasKey key) writeEntity readEntity -> key -> writeEntity -> m () updateEntity tableDef key = Monad.void . updateEntityAndReturnRowCount tableDef key {- | Updates the row with the given key with the data given by @writeEntity@. Returns the number of rows affected by the query. @since 1.0.0.0 -} updateEntityAndReturnRowCount :: Monad.MonadOrville m => Schema.TableDefinition (Schema.HasKey key) writeEntity readEntity -> key -> writeEntity -> m Int updateEntityAndReturnRowCount tableDef key writeEntity = case Update.updateToTable tableDef key writeEntity of Nothing -> liftIO . throwIO . EmptyUpdateError . Schema.tableIdentifier $ tableDef Just update -> Update.executeUpdate update {- | Updates the row with the given key with the data given by @writeEntity@, returning the updated row from the database. If no row matches the given key, 'Nothing' will be returned. You can use this function to obtain any column values computed by the database during the update, including columns with triggers attached to them. @since 1.0.0.0 -} updateAndReturnEntity :: Monad.MonadOrville m => Schema.TableDefinition (Schema.HasKey key) writeEntity readEntity -> key -> writeEntity -> m (Maybe readEntity) updateAndReturnEntity tableDef key writeEntity = case Update.updateToTableReturning tableDef key writeEntity of Nothing -> liftIO . throwIO . EmptyUpdateError . Schema.tableIdentifier $ tableDef Just update -> do returnedEntities <- Update.executeUpdateReturnEntities update RowCountExpectation.expectAtMostOneRow "updateAndReturnEntity RETURNING clause" returnedEntities {- | Applies the given 'Expr.SetClause's to the rows in the table that match the given where condition. The easiest way to construct a 'Expr.SetClause' is via the 'Orville.Postgresql.setField' function (also exported as @.:=@). @since 1.0.0.0 -} updateFields :: Monad.MonadOrville m => Schema.TableDefinition (Schema.HasKey key) writeEntity readEntity -> NonEmpty Expr.SetClause -> Maybe Expr.BooleanExpr -> m () updateFields tableDef setClauses = Monad.void . updateFieldsAndReturnRowCount tableDef setClauses {- | Applies the given 'Expr.SetClause's to the rows in the table that match the given where condition. The easiest way to construct a 'Expr.SetClause' is via the 'Orville.Postgresql.setField' function (also exported as @.:=@). Returns the number of rows affected by the query. @since 1.0.0.0 -} updateFieldsAndReturnRowCount :: Monad.MonadOrville m => Schema.TableDefinition (Schema.HasKey key) writeEntity readEntity -> NonEmpty Expr.SetClause -> Maybe Expr.BooleanExpr -> m Int updateFieldsAndReturnRowCount tableDef setClauses mbWhereCondition = Update.executeUpdate $ Update.updateToTableFields tableDef setClauses mbWhereCondition {- | Like 'updateFields', but uses a @RETURNING@ clause to return the updated version of any rows that were affected by the update. @since 1.0.0.0 -} updateFieldsAndReturnEntities :: Monad.MonadOrville m => Schema.TableDefinition (Schema.HasKey key) writeEntity readEntity -> NonEmpty Expr.SetClause -> Maybe Expr.BooleanExpr -> m [readEntity] updateFieldsAndReturnEntities tableDef setClauses mbWhereCondition = Update.executeUpdateReturnEntities $ Update.updateToTableFieldsReturning tableDef setClauses mbWhereCondition {- | Deletes the row with the given key. @since 1.0.0.0 -} deleteEntity :: Monad.MonadOrville m => Schema.TableDefinition (Schema.HasKey key) writeEntity readEntity -> key -> m () deleteEntity entityTable = Monad.void . deleteEntityAndReturnRowCount entityTable {- | Deletes the row with the given key. Returns the number of rows affected by the query. @since 1.0.0.0 -} deleteEntityAndReturnRowCount :: Monad.MonadOrville m => Schema.TableDefinition (Schema.HasKey key) writeEntity readEntity -> key -> m Int deleteEntityAndReturnRowCount entityTable key = let primaryKeyCondition = Schema.primaryKeyEquals (Schema.tablePrimaryKey entityTable) key in Delete.executeDelete $ Delete.deleteFromTable entityTable (Just primaryKeyCondition) {- | Deletes the row with the given key, returning the row that was deleted. If no row matches the given key, 'Nothing' is returned. @since 1.0.0.0 -} deleteAndReturnEntity :: Monad.MonadOrville m => Schema.TableDefinition (Schema.HasKey key) writeEntity readEntity -> key -> m (Maybe readEntity) deleteAndReturnEntity entityTable key = do let primaryKeyCondition = Schema.primaryKeyEquals (Schema.tablePrimaryKey entityTable) key returnedEntities <- deleteAndReturnEntities entityTable (Just primaryKeyCondition) RowCountExpectation.expectAtMostOneRow "deleteAndReturnEntity RETURNING clause" returnedEntities {- | Deletes all rows in the given table that match the where condition. @since 1.0.0.0 -} deleteEntities :: Monad.MonadOrville m => Schema.TableDefinition key writeEntity readEntity -> Maybe Expr.BooleanExpr -> m () deleteEntities entityTable = Monad.void . deleteEntitiesAndReturnRowCount entityTable {- | Deletes all rows in the given table that match the where condition. Returns the number of rows affected by the query. @since 1.0.0.0 -} deleteEntitiesAndReturnRowCount :: Monad.MonadOrville m => Schema.TableDefinition key writeEntity readEntity -> Maybe Expr.BooleanExpr -> m Int deleteEntitiesAndReturnRowCount entityTable whereCondition = Delete.executeDelete $ Delete.deleteFromTable entityTable whereCondition {- | Deletes all rows in the given table that match the where condition, returning the rows that were deleted. @since 1.0.0.0 -} deleteAndReturnEntities :: Monad.MonadOrville m => Schema.TableDefinition key writeEntity readEntity -> Maybe Expr.BooleanExpr -> m [readEntity] deleteAndReturnEntities entityTable whereCondition = Delete.executeDeleteReturnEntities $ Delete.deleteFromTableReturning entityTable whereCondition {- | Finds all the entities in the given table according to the specified 'SelectOptions.SelectOptions', which may include where conditions to match, ordering specifications, etc. @since 1.0.0.0 -} findEntitiesBy :: Monad.MonadOrville m => Schema.TableDefinition key writeEntity readEntity -> SelectOptions.SelectOptions -> m [readEntity] findEntitiesBy entityTable selectOptions = Select.executeSelect $ Select.selectTable entityTable selectOptions {- | Like 'findEntitiesBy', but adds a 'LIMIT 1' to the query and then returns the first item from the list. Usually when you use this you will want to provide an order by clause in the 'SelectOptions.SelectOptions' because the database will not guarantee ordering. @since 1.0.0.0 -} findFirstEntityBy :: Monad.MonadOrville m => Schema.TableDefinition key writeEntity readEntity -> SelectOptions.SelectOptions -> m (Maybe readEntity) findFirstEntityBy entityTable selectOptions = listToMaybe <$> findEntitiesBy entityTable (SelectOptions.limit 1 <> selectOptions) {- | Finds a single entity by the table's primary key value. @since 1.0.0.0 -} findEntity :: Monad.MonadOrville m => Schema.TableDefinition (Schema.HasKey key) writeEntity readEntity -> key -> m (Maybe readEntity) findEntity entityTable key = let primaryKeyCondition = Schema.primaryKeyEquals (Schema.tablePrimaryKey entityTable) key in findFirstEntityBy entityTable (SelectOptions.where_ primaryKeyCondition) {- | Finds multiple entities by the table's primary key. @since 1.0.0.0 -} findEntities :: Monad.MonadOrville m => Schema.TableDefinition (Schema.HasKey key) writeEntity readEntity -> NonEmpty key -> m [readEntity] findEntities entityTable keys = let primaryKeyCondition = Schema.primaryKeyIn (Schema.tablePrimaryKey entityTable) keys in findEntitiesBy entityTable (SelectOptions.where_ primaryKeyCondition) {- | Thrown by 'updateFields' and 'updateFieldsAndReturnEntities' if the 'Schema.TableDefinition' they are given has no columns to update. @since 1.0.0.0 -} newtype EmptyUpdateError = EmptyUpdateError Schema.TableIdentifier -- | @since 1.0.0.0 instance Show EmptyUpdateError where show (EmptyUpdateError tableId) = "EmptyUdateError: " <> Schema.tableIdToString tableId <> " has no columns to update." -- | @since 1.0.0.0 instance Exception EmptyUpdateError