sheharyarn / memento

Simple + Powerful interface to the Mnesia Distributed Database 💾
http://hexdocs.pm/memento/
MIT License
734 stars 23 forks source link

Is there a way to search for a pattern? #28

Closed annrapid closed 2 years ago

annrapid commented 3 years ago

Hi there, I am using Memento for a small project. It works great but I am lacking a small thing. Is there a way to search for a pattern ?

For example I have list of Movies containing word "Rush". So in a traditional database, we will do something like

where title like '%Rush%'

Is there something similar with Mnesia?

Regards,

sheharyarn commented 2 years ago

Hi @annrapid, You cannot use matchspecs (i.e. select and select_raw methods) for string/binary wildcard queries, however, you might be able to accomplish this if you store your values as charlists instead of strings, as you could match on the list values.

I also found a similar question on the Erlang mailing list, where the goal is to do a "begins with" search on charlists. I'm converting the code to Elixir for your ease below:

Table:

defmodule Movie do
  use Memento.Table,
    type: :ordered_set,
    autoincrement: true,
    attributes: [:id, :title, :year, :director]

  def seed do
    movies = [
      %Movie{id: 1, title: 'Reservoir Dogs', year: 1992, director: 'Quentin Tarantino'},
      %Movie{id: 2, title: 'Rush',           year: 1991, director: 'Lili Zanuck'},
      %Movie{id: 3, title: 'Jurassic Park',  year: 1993, director: 'Steven Spielberg'},
      %Movie{id: 4, title: 'Kill Bill',      year: 2003, director: 'Quentin Tarantino'},
      %Movie{id: 5, title: 'Pulp Fiction',   year: 1994, director: 'Quentin Tarantino'},
      %Movie{id: 6, title: 'Rush',           year: 2013, director: 'Ron Howard'},
      %Movie{id: 7, title: 'Jaws',           year: 1975, director: 'Steven Spielberg'},
    ]

    Memento.transaction!(fn -> Enum.each(movies, &Memento.Query.write/1) end)
  end
end

Search for director name starting with "Steve":

Table.create(Movie)
Movie.seed()
match_spec = :ets.fun2ms(fn {Movie, _, _, _, [?S, ?t, ?e, ?v, ?e | _]} = record -> record end)

Memento.transaction!(fn -> Query.select_raw(Movie, match_spec, coerce: true) end)
# => [
#  %Movie{id: 3, title: 'Jurassic Park', director: 'Steven Spielberg', year: 1993},
#  %Movie{id: 7, title: 'Jaws',          director: 'Steven Spielberg', year: 1975}
# ]

Of course, there are many challenges with this approach, so you might have to modify this quite a bit to get it working exactly according to your needs.


You can learn more about matchspecs here:

You should also take a look at the [Query.select_raw/3]() docs to know how to use custom matchspecs in Memento. I would also highly recommend playing around with :ets.fun2ms/1 which makes it much easier to generate matchspecs.

sheharyarn commented 2 years ago

There's another follow-up response to the question in mailing list mentioned earlier. It matches on strings and not charlists, but does a full table scan using :qlc, which I understand is much slower than matchspecs, but there might not be a better alternative.

I don't have a lot of experience with :qlc, so unfortunately I cannot help you translate that code to Elixir or modify it to work for your use-case. However, there are some very experienced erlangists on ElixirForum that might be able to help.


Also, if you were looking for something more like full-text search support, you won't find that in :mnesia either as it's just a plain database that stores tuples with one of the fields as a key. It doesn't do any text indexing itself, and you'll need to add a layer on top of memento/mnesia for that.


Please do share your solution if you're able to solve it. I would really like to document it in Memento, and would probably even add it as a helper to the library.