Skip to content

Commit

Permalink
Abstract primaryKeyCondition
Browse files Browse the repository at this point in the history
  • Loading branch information
MonaMayrhofer committed Mar 26, 2024
1 parent ebebd39 commit 21822a1
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 32 deletions.
51 changes: 30 additions & 21 deletions IHP/ModelSupport.hs
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,28 @@ class
-- [("post_id", "0ace9270-568f-4188-b237-3789aa520588"), ("tag_id", "0b58fdf5-4bbb-4e57-a5b7-aa1c57148e1c")]
primaryKeyConditionForId :: Id record -> [(Text, PG.Action)]

-- | Returns an ActionTuple, representing the parameters that can be passed to a prepared SQL statement
-- >>> toField $ primaryKeyConditionActionTupleForId postTag.id
-- Many [Plain "(", Plain "0ace9270-568f-4188-b237-3789aa520588", Plain "0b58fdf5-4bbb-4e57-a5b7-aa1c57148e1c", Plain ")"]
primaryKeyConditionActionTupleForId :: forall record. (Table record) => Id record -> ActionTuple
primaryKeyConditionActionTupleForId = ActionTuple . map snd . primaryKeyConditionForId @record

-- | Returns ByteString, that represents the part of an SQL where clause, that matches on a tuple consisting of all the primary keys
-- For table with simple primary keys this simply returns the name of the primary key column, without wrapping in a tuple
-- >>> primaryKeyColumnSelector @PostTag
-- "(post_tags.post_id, post_tags.tag_id)"
-- >>> primaryKeyColumnSelector @Post
-- "post_tags.post_id"
primaryKeyConditionColumnSelector :: forall record. (Table record) => ByteString
primaryKeyConditionColumnSelector =
let
qualifyColumnName col = tableNameByteString @record <> "." <> col
in
case primaryKeyColumnNames @record of
[] -> error . cs $ "Impossible happened in primaryKeyConditionColumnSelector. No primary keys found for table " <> tableName @record <> ". At least one primary key is required."
[s] -> qualifyColumnName s
conds -> "(" <> intercalate ", " (map qualifyColumnName conds) <> ")"

-- | Returns WHERE conditions to match an entity by it's primary key
--
-- For tables with a simple primary key this returns a tuple with the id:
Expand Down Expand Up @@ -647,19 +669,11 @@ deleteRecord record =
--
deleteRecordById :: forall record table. (?modelContext :: ModelContext, Table record, Show (PrimaryKey table), GetTableName record ~ table, record ~ GetModelByTableName table) => Id' table -> IO ()
deleteRecordById id = do
let (pkCols, paramPattern, theParameters) = case primaryKeyConditionForId @record id of
[] -> error . cs $ "Impossible happened in deleteRecordById. No primary keys found for table " <> tableName @record <> ". At least one primary key is required."
[(colName, param)] -> (colName, "?", [param])
ps ->
( "(" <> intercalate "," (map fst ps) <> ")",
"(" <> intercalate "," (map (const "?") ps) <> ")",
map snd ps
)

let theQuery = "DELETE FROM " <> tableName @record <> " WHERE " <> pkCols <> " = " <> paramPattern

sqlExec (PG.Query . cs $! theQuery) theParameters
pure ()
let theQuery = "DELETE FROM " <> tableNameByteString @record <> " WHERE " <> (primaryKeyConditionColumnSelector @record) <> " = ?"

let theParameters = PG.Only $ primaryKeyConditionActionTupleForId @record id
sqlExec (PG.Query $! theQuery) theParameters
pure ()
{-# INLINABLE deleteRecordById #-}

-- | Runs a @DELETE@ query for a list of records.
Expand All @@ -682,15 +696,10 @@ deleteRecordByIds :: forall record table. (?modelContext :: ModelContext, Show (
deleteRecordByIds [] = do
pure () -- If there are no ids, we wouldn't even know the pkCols, so we just don't do anything, as nothing happens anyways
deleteRecordByIds ids@(firstId : _) = do
let pkCols = case primaryKeyConditionForId @record firstId of
[] -> error . cs $ "Impossible happened in deleteRecordById. No primary keys found for table " <> (tableName @record) <> ". At least one primary key is required."
[(colName, _)] -> colName
ps -> "(" <> intercalate "," (map fst ps) <> ")"

let theQuery = "DELETE FROM " <> tableName @record <> " WHERE " <> pkCols <> " IN ?"
let theQuery = "DELETE FROM " <> tableNameByteString @record <> " WHERE " <> (primaryKeyConditionColumnSelector @record) <> " IN ?"

let theParameters = PG.Only $ PG.In $ map (ActionTuple . map snd . primaryKeyConditionForId @record) ids
sqlExec (PG.Query . cs $! theQuery) theParameters
let theParameters = PG.Only $ PG.In $ map (primaryKeyConditionActionTupleForId @record) ids
sqlExec (PG.Query $! theQuery) theParameters
pure ()
{-# INLINABLE deleteRecordByIds #-}

Expand Down
13 changes: 2 additions & 11 deletions IHP/QueryBuilder.hs
Original file line number Diff line number Diff line change
Expand Up @@ -835,20 +835,11 @@ filterWhereIdIn :: forall table model queryBuilderProvider (joinRegister :: *).
filterWhereIdIn values queryBuilderProvider =
-- We don't need to treat null values differently here, because primary keys imply not-null
let
qualifyColumnName col = tableNameByteString @model <> "." <> col

pkConds = map (primaryKeyConditionForId @model) values

actionTuples = map (ActionTuple . map snd) pkConds

columnNames = case primaryKeyColumnNames @model of
[] -> error . cs $ "Impossible happened in deleteRecordById. No primary keys found for table " <> tableName @model <> ". At least one primary key is required."
[s] -> cs $ qualifyColumnName s
conds -> cs $ "(" <> ByteString.intercalate ", " (map qualifyColumnName conds) <> ")"
actionTuples = map (primaryKeyConditionActionTupleForId @model) values

queryBuilder = getQueryBuilder queryBuilderProvider

whereInQuery = FilterByQueryBuilder {queryBuilder, queryFilter = (columnNames, InOp, toField (In actionTuples)), applyLeft = Nothing, applyRight = Nothing}
whereInQuery = FilterByQueryBuilder {queryBuilder, queryFilter = (primaryKeyConditionColumnSelector @model, InOp, toField (In actionTuples)), applyLeft = Nothing, applyRight = Nothing}
in
injectQueryBuilder whereInQuery
{-# INLINE filterWhereIdIn #-}
Expand Down

0 comments on commit 21822a1

Please sign in to comment.