Hello everyone !
I am struggling with macros in order to dynamically create a SQL query to search for various topics in multiple languages. To provide some context, I have a full text search query and I would like to perform queries based on the languages selected on the search bar.
For example, If a user selects english and italian, the query should look like this :
"""
SELECT topic_id, topic_title, topic_description
FROM (SELECT topics.id as topic_id, topics.title as topic_title, topics.description as topic_description
setweight(topics.title_tsvector, 'A') ||
setweight(topics.description_tsvector, 'B') as document
FROM topics) topics_search
WHERE topics_search.document @@ websearch_to_tsquery(english::text::regconfig, 'query') OR
topics_search.document @@ websearch_to_tsquery(italian::text::regconfig, 'query')
"""
I took inspiration from this article and implemented the following :
topics.exs
defmacro multilang_search_topics(languages) do
quote bind_quoted: [languages: languages] do
sql =
Enum.map(
languages,
fn _lang ->
"topics_search.document @@ websearch_to_tsquery(?::text::regconfig, ?)"
end
)
|> Enum.join(" OR ")
"""
SELECT topic_id, topic_title, topic_description
FROM (SELECT topics.id as topic_id, topics.title as topic_title, topics.description as topic_description,
setweight(topics.title_tsvector, 'A') ||
setweight(topics.description_tsvector, 'B') as document
FROM topics) topics_search
WHERE #{sql}
"""
end
end
defmacro multilang_search_topics(languages, search_query) do
languages = quote(do: unquote(languages))
arguments =
languages
|> Enum.map(
&[
quote(do: unquote(&1)),
quote(do: ^unquote(search_query)),
]
)
|> List.flatten()
|> List.insert_at(0, multilang_search_topics(languages))
quote do
fragment(unquote_splicing(arguments))
end
end
def search(query \\ Topic, search_query, languages) do
from topic in query,
join: search in multilang_search_topics(languages, search_query),
on: search.topic_id == topic.id
end
I got tricked into thinking my solution was working because I hardcoded the languages argument like so : join: search in multilang_search_topics(["english"], search_query)
However, when I switched it back the search method triggers the following error : ** (Protocol.UndefinedError) protocol Enumerable not implemented for {:languages, [line: 121], nil} of type Tuple
Now, I get that macros are receiving representation of arguments. I was wondering if there is a way to handle list argument without triggering the error ?
Thanks in advance to whoever might read and help.