Refine (Refine v0.3.0)

Copy Markdown

Refine is an Elixir library for fast faceted search.

Summary

Facets

Creates a facets table based on a source table and populates it with bitmap data, facet labels, and facet values.

Identical to create_facets_table/2 but in case the table already exists, skips table creation silently without returning an error.

Removes the facets table and all installed PostgreSQL triggers and functions.

Aggregates and applies membership changes from the deltas table to update the facets table.

Helpers

Tests whether a table exists. Pass a qualified table name (including schema prefix) if the table is not in the "public" schema.

Facets

create_facets_table(config, opts \\ [])

@spec create_facets_table(facets_table_config(), database_options()) ::
  create_facets_table_return()

Creates a facets table based on a source table and populates it with bitmap data, facet labels, and facet values.

Alongside the creation of the table:

  • Creates the roaringbitmap extension (if it isn't created already).
  • With config option add_identity_column_if_not_exists: adds an identity column to the source table (if no valid integer ID column is found in the table).

Configuration

See: Facets table configuration

Options

  • repo - The Ecto Repo module that contains a Postgres adapter. Omit when the repo is configured globally.
  • timeout - Postgrex timeout option.

Examples

config = %{
  facets_table: "articles_facets",
  source_table: "articles",
  add_identity_column_if_not_exists: true,
  identity_column: "identity",
  facets: [
    %{facet_name: "draft"}
  ]
}

Refine.create_facets_table(config,
  repo: MyApp.Repo
)
{:ok, "articles_facets"}

To create a the facets table in a non-default schema:.

config = %{
  facets_table: "classifications.categories_facets",
  source_table: "classifications.categories",
  ...
}

Returns an error if the table exists:

Refine.create_facets_table(config,
  repo: MyApp.Repo
)
{:error, :facets_table_exists, "Table 'articles_facets' exists. ..."}

To recreate the facets table:

Refine.drop_facets_table(config,
  repo: MyApp.Repo
)

Refine.create_facets_table(config,
  repo: MyApp.Repo
)

create_facets_table_if_not_exists(config, opts \\ [])

@spec create_facets_table_if_not_exists(
  facets_table_config(),
  [database_option()]
) :: create_facets_table_return()

Identical to create_facets_table/2 but in case the table already exists, skips table creation silently without returning an error.

If you need to recreate an existing facets table, first call drop_facets_table/2. See also: create_facets_table/2.

Options:

  • repo - The Ecto Repo module that contains a Postgres adapter. Omit when the repo is configured globally.
  • timeout - Postgrex timeout option.

drop_facets_table(config, opts \\ [])

@spec drop_facets_table(facets_table_config(), database_options()) ::
  {:ok, String.t()} | {:error, :table_not_found} | {:error, Exception.t()}

Removes the facets table and all installed PostgreSQL triggers and functions.

Options:

  • repo - The Ecto Repo module that contains a Postgres adapter. Omit when the repo is configured globally.
  • timeout - Postgrex timeout option.

Examples

Refine.drop_facets_table(config, repo: MyApp.Repo)
{:ok, "articles_facets"}

merge_deltas(config, options \\ [])

@spec merge_deltas(facets_table_config(), database_options()) ::
  :ok | {:error, [Exception.t()]}

Aggregates and applies membership changes from the deltas table to update the facets table.

Helpers

table_exists?(qualified_table_name, repo)

@spec table_exists?(String.t(), repo()) :: boolean()

Tests whether a table exists. Pass a qualified table name (including schema prefix) if the table is not in the "public" schema.

Performs a database query using pg_class.

Examples

Refine.table_exists?("articles_facets", MyApp.Repo)

Refine.table_exists?("classifications.tags", MyApp.Repo)

Types

create_facets_table_return()

@type create_facets_table_return() ::
  {:ok, String.t()}
  | {:error, :column_not_found, String.t()}
  | {:error, :column_names_not_unique}
  | {:error, :facets_table_exists, String.t()}
  | {:error, :identity_column_already_exists, String.t()}
  | {:error, :identity_column_invalid_type, String.t()}
  | {:error, :identity_column_not_found, String.t()}
  | {:error, :invalid_table_name, String.t()}
  | {:error, :table_names_not_unique}
  | {:error, Exception.t()}

database_option()

@type database_option() :: {:repo, repo()} | postgrex_option()

database_options()

@type database_options() :: [database_option()]

facet_config()

@type facet_config() :: %{
  facet_label: String.t() | nil,
  facet_name: String.t(),
  join_table: String.t() | nil,
  label_column: String.t() | nil,
  label_path: String.t() | nil,
  value_column: String.t() | nil,
  value_path: String.t() | nil,
  width_bucket: [integer() | float()] | nil
}

facet_configs()

@type facet_configs() :: [facet_config()]

facets_table_config()

@type facets_table_config() ::
  %{
    add_identity_column_if_not_exists: boolean() | nil,
    facets_table: String.t(),
    facets: facet_configs(),
    identity_column: String.t(),
    joins: join_configs() | nil,
    roaringbitmap_type: String.t() | nil,
    source_table: String.t()
  }
  | keyword()

join_config()

@type join_config() :: %{
  table: String.t(),
  match: String.t(),
  to: String.t(),
  on: String.t() | nil
}

join_configs()

@type join_configs() :: [join_config()]

order_by_entry()

@type order_by_entry() ::
  {String.t(), order_direction()}
  | {String.t(), order_direction(), sort_nulls()}

order_direction()

@type order_direction() :: :asc | :desc

postgrex_option()

@type postgrex_option() :: {:timeout, integer() | :infinity} | {:log, boolean()}

postgrex_options()

@type postgrex_options() :: [postgrex_option()]

repo()

@type repo() :: module()

search_option()

@type search_option() ::
  {:facets, map()}
  | {:limit, integer()}
  | {:offset, integer()}
  | {:order_by, [order_by_entry()]}
  | {:query, Ecto.Query.t()}
  | {:repo, repo()}
  | {:result_fields, [String.t()]}
  | postgrex_option()

search_options()

@type search_options() :: [search_option()]

search_return()

@type search_return() :: {:ok, search_return_data()} | {:error, Exception.t()}

search_return_data()

@type search_return_data() :: %{
  facets: map(),
  rows: [map()],
  total_count: integer(),
  types: map(),
  flop_meta: map() | nil
}

sort_nulls()

@type sort_nulls() :: :first | :last | :default