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’ll 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 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
69 changes: 69 additions & 0 deletions Guide/database.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,75 @@ do
|> map (\(Only uuid) -> Id uuid :: Id Project)
```

### Implementing `ToField` For Custom Types.

This is a more advanced topic, and before trying to implement it, we encourage you to
try and use the [Query Builder](https://ihp.digitallyinduced.com/Guide/querybuilder.html), as it provides a better type safety. Even the below example can be accomplished with the Query Builder.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even the below example can be accomplished with the Query Builder.

Once we have #1867 , we should link it here.


When you want to use a custom type as a parameter for a SQL query, you need to implement the `ToField` class. This is needed for example when you want to use a tuple as a parameter for a SQL query.
For example, 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 (char8)


instance ToField (ProjectType, Int) where
toField = serializeProjectTypeAndInt

serializeProjectTypeAndInt :: (ProjectType, Int) -> Action
serializeProjectTypeAndInt (projectType, participants) = Many
[ Plain (char8 '(')
, 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 = [(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
Loading