Skip to content
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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docs on how to use ToField to query custom tuples in IN clause #1863

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
66 changes: 66 additions & 0 deletions Guide/database.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,72 @@ do
|> map (\(Only uuid) -> Id uuid :: Id Project)
```

### `IN` Clause Queries with Multiple Parameters

At times, you may need to utilize the IN clause in SQL queries with multiple parameters. This approach differs from searching for records that match a single parameter, such as an ID. Instead, you might search for records that meet a combination of two parameters, like project_id and user_id.

Consider a Project record, which includes a project type enum and the number of participants:

```
# Schema.sql
CREATE TYPE project_type AS ENUM ('project_type_ongoing', 'project_type_not_started', 'project_type_finished');

CREATE TABLE projects (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
project_type project_type NOT NULL,
participants TEXT NOT NULL
);
```

In our Fixtures.sql, we can insert some sample data:

```sql
ALTER TABLE public.projects DISABLE TRIGGER ALL;

INSERT INTO public.projects (id, project_type, participants) VALUES ('e767f087-d1b9-42ea-898e-c2a2bf39999e', 'project_type_ongoing', '2');
INSERT INTO public.projects (id, project_type, participants) VALUES ('429177d7-f425-4c5e-b379-5e1a0f72bfb5', 'project_type_ongoing', '2');
INSERT INTO public.projects (id, project_type, participants) VALUES ('84825fa3-2cce-4b4a-872b-81fe554e2076', 'project_type_not_started', '3');
INSERT INTO public.projects (id, project_type, participants) VALUES ('687e32f5-8e8b-4a6d-b4a7-ead9d8a39f91', 'project_type_finished', '2');


ALTER TABLE public.projects ENABLE TRIGGER ALL;
```

Now, let's say we want to retrieve all projects that are of type Ongoing and have 1 participant, and the Finished ones with 2 participants. The filterWhere function is not suitable here since we need to query by two parameters.

First, we must implement ToField to allow using a tuple of `(ProjectType, Int)` as a parameter:

```haskell
-- Web/Types.hs
import Database.PostgreSQL.Simple.ToField
import Data.ByteString.Builder (byteString, char8)


instance ToField (ProjectType, Int) where
toField = serializeProjectTypeAndInt

serializeProjectTypeAndInt :: (ProjectType, Int) -> Action
serializeProjectTypeAndInt (projectType, participants) = Many
[ Plain (byteString "(")
, toField projectType
, Plain (char8 ',')
, toField $ show participants
, Plain (char8 ')')
]
```

Then, in our controller, we can execute the query:

```haskell
-- Web/Controller/Projects.hs
action ProjectsAction = do
-- let pairs = projects |> fmap (\project -> (project.projectType, project.participants))
amitaibu marked this conversation as resolved.
Show resolved Hide resolved
let pairs = [(ProjectTypeOngoing, 1 :: Int), (ProjectTypeFinished, 2)]
projects :: [Project] <- sqlQuery "SELECT * FROM projects WHERE (project_type, participants) IN ?" (Only $ In pairs)
amitaibu marked this conversation as resolved.
Show resolved Hide resolved

render IndexView { .. }
```

### Scalar Results

The [`sqlQuery`](https://ihp.digitallyinduced.com/api-docs/IHP-ModelSupport.html#v:sqlQuery) function always returns a list of rows as the result. When the result of your query is a single value (such as an integer or string) use [`sqlQueryScalar`](https://ihp.digitallyinduced.com/api-docs/IHP-ModelSupport.html#v:sqlQueryScalar):
Expand Down