Skip to content

Instantly share code, notes, and snippets.

@greven
Last active October 22, 2022 13:40
Show Gist options
  • Save greven/8acfac78dc381eb82d7c4bcba02efda9 to your computer and use it in GitHub Desktop.
Save greven/8acfac78dc381eb82d7c4bcba02efda9 to your computer and use it in GitHub Desktop.
Ecto Migration add Full Text Search
add_searchable_column("skills", "searchable", "english", [
{:string, "name"},
{:array, "alt_names"},
{:array, "hidden_labels"}
])
add_translated_searchable_column(
"skills",
"searchable_pt",
"portuguese",
"translations",
["$.**.name", "$.**.alt_names", "$.**.hidden_labels"]
)
create(index(:skills, [:name]))
create(index(:skills, [:hit_rank]))
# Indexes for SIMILARITY comparisons
create(index(:skills, ["name gin_trgm_ops"], using: "GIN", concurrently: true))
# Indexes for SIMILARITY comparisons - Portuguese
create(
index(:skills, ["(translations -> 'pt' ->> 'name') gin_trgm_ops"],
using: "GIN",
concurrently: true
)
)
end
defp add_searchable_column(table, column_name, language, search_columns, create_indexes \\ true) do
execute("""
CREATE OR REPLACE FUNCTION immutable_array_to_string(text[]) RETURNS text as $$
SELECT array_to_string($1, ','); $$ LANGUAGE sql IMMUTABLE;
""")
expression =
search_columns
|> Enum.reduce([], fn
{:string, col}, acc ->
["coalesce(#{col}, '') || ' '" | acc]
{:array, col}, acc ->
["coalesce(immutable_array_to_string(#{col}), '')" | acc]
_, acc ->
acc
end)
|> Enum.reverse()
|> Enum.join(" || ")
execute(
"""
ALTER TABLE #{table}
ADD COLUMN #{column_name} tsvector
GENERATED ALWAYS AS (to_tsvector('#{language}', #{expression})) STORED
""",
"ALTER TABLE skills DROP COLUMN #{column_name}"
)
# Create Indexes
if create_indexes do
create(
index(table, ["(to_tsvector('#{language}', #{expression}))"],
name: "#{table}_search_vector_#{language}",
using: "GIN",
concurrently: true
)
)
end
end
defp add_translated_searchable_column(
table,
column_name,
language,
jsonb_column,
paths,
create_indexes \\ true
) do
json_paths =
paths
|> Enum.reduce([], fn path, acc ->
["jsonb_path_query_array(#{jsonb_column}, 'strict #{path}')" | acc]
end)
|> Enum.reverse()
|> Enum.join(" || ")
execute("CREATE TEXT SEARCH CONFIGURATION u_#{language} ( COPY = 'simple' )")
execute("""
ALTER TEXT SEARCH CONFIGURATION u_#{language}
ALTER MAPPING FOR asciiword, asciihword, hword_asciipart, word, hword, hword_part
WITH unaccent, #{language}_stem;
""")
execute(
"""
ALTER TABLE #{table}
ADD COLUMN #{column_name} tsvector
GENERATED ALWAYS AS (jsonb_to_tsvector('u_#{language}', #{json_paths}, '["string"]')) STORED
""",
"ALTER TABLE skills DROP COLUMN #{column_name}"
)
# Create Indexes
if create_indexes do
create(
index(table, ["(to_tsvector('u_#{language}', #{json_paths}))"],
name: "#{table}_search_vector_#{language}",
using: "GIN",
concurrently: true
)
)
end
end
# Querying example
defp or_where_fulltext_search(queryable, search_term) do
queryable
|> or_where(
[s],
fragment("websearch_to_tsquery('english', ?) @@ ?", ^search_term, s.searchable)
)
end
defp or_where_fulltext_translation_search(queryable, search_term, locale) do
language = language_name_by_code(locale) |> String.downcase()
search_column = "searchable_#{locale}" |> String.to_atom()
text_search_config = "u_#{language}"
queryable
|> or_where(
[s],
fragment(
"websearch_to_tsquery('?', unaccent(?)) @@ ?",
literal(^text_search_config),
^search_term,
field(s, ^search_column)
)
)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment