GreenFairy.Adapter behaviour (GreenFairy v0.3.0)

View Source

Behaviour for backing adapters that integrate data sources with GreenFairy.

A backing adapter provides a unified interface for different data source concerns:

  • CQL (Query Language): Field detection, type mapping, and operator inference
  • DataLoader: Batched data loading configuration
  • Extensions: Additional data source-specific capabilities

Implementing an Adapter

defmodule MyApp.ElasticsearchAdapter do
  @behaviour GreenFairy.Adapter

  # Core
  @impl true
  def handles?(module), do: function_exported?(module, :__es_index__, 0)

  # CQL Capabilities
  @impl true
  def queryable_fields(module), do: module.__es_mappings__() |> Map.keys()

  @impl true
  def field_type(module, field), do: module.__es_mappings__()[field][:type]

  @impl true
  def operators_for_type(type) do
    case type do
      :text -> [:eq, :contains, :in]
      :keyword -> [:eq, :neq, :in]
      :integer -> [:eq, :neq, :gt, :lt, :gte, :lte, :in]
      _ -> [:eq, :in]
    end
  end

  # DataLoader Capabilities
  @impl true
  def dataloader_source(_module), do: :elasticsearch

  @impl true
  def dataloader_batch_key(module, field, args) do
    {module, field, args}
  end

  # Extensions (optional)
  @impl true
  def capabilities, do: [:cql, :dataloader, :full_text_search]
end

Registering Adapters

Register adapters globally in config:

config :green_fairy, :adapters, [
  MyApp.ElasticsearchAdapter,
  GreenFairy.Adapters.Ecto
]

Or per-type:

type "User", struct: MyApp.User do
  use GreenFairy.Extensions.CQL, adapter: MyApp.CustomAdapter
end

Built-in Adapters

Summary

Callbacks

Returns the capabilities supported by this adapter.

Returns the batch key for DataLoader.

Returns default args to merge into DataLoader queries for a field.

Returns the DataLoader source name for this module.

Returns the type of a field for operator inference.

Returns true if this adapter can handle the given module.

Returns a list of operators supported for the given type.

Returns a list of queryable field atoms for the module.

Functions

Use this module to get default implementations for optional callbacks.

Returns configured adapters from application environment.

Returns the default adapters.

Returns the filter adapter struct for the given struct module.

Finds an adapter that can handle the given module.

Checks if an adapter supports a capability.

Callbacks

capabilities()

(optional)
@callback capabilities() :: [atom()]

Returns the capabilities supported by this adapter.

Common capabilities include:

  • :cql - Query language support (field filtering)
  • :dataloader - Batched data loading
  • :full_text_search - Full text search support
  • :aggregations - Aggregation queries

Default implementation returns [:cql, :dataloader].

dataloader_batch_key(module, field, args)

(optional)
@callback dataloader_batch_key(module :: module(), field :: atom(), args :: map()) ::
  term()

Returns the batch key for DataLoader.

This determines how items are batched together for loading.

dataloader_default_args(module, field)

(optional)
@callback dataloader_default_args(module :: module(), field :: atom()) :: map()

Returns default args to merge into DataLoader queries for a field.

This can be used to add default ordering, filters, or other constraints.

dataloader_source(module)

(optional)
@callback dataloader_source(module :: module()) :: atom()

Returns the DataLoader source name for this module.

This is used to determine which DataLoader source to use when loading associations. Defaults to :repo.

field_type(module, field)

@callback field_type(module :: module(), field :: atom()) :: atom() | tuple() | nil

Returns the type of a field for operator inference.

The return value should be an atom or tuple that can be matched in operators_for_type/1.

handles?(module)

@callback handles?(module :: module()) :: boolean()

Returns true if this adapter can handle the given module.

This is called during adapter discovery to find the appropriate adapter for a struct module.

operators_for_type(type)

@callback operators_for_type(type :: atom() | tuple()) :: [atom()]

Returns a list of operators supported for the given type.

Common operators include:

  • :eq - equals
  • :neq - not equals
  • :gt, :lt, :gte, :lte - comparisons
  • :in - in list
  • :contains, :starts_with, :ends_with - string operations
  • :is_nil - null check

queryable_fields(module)

@callback queryable_fields(module :: module()) :: [atom()]

Returns a list of queryable field atoms for the module.

These are fields that can be filtered on using CQL.

Functions

__using__(opts)

(macro)

Use this module to get default implementations for optional callbacks.

configured_adapters()

Returns configured adapters from application environment.

default_adapters()

Returns the default adapters.

filter_adapter_for(struct_module, opts \\ [])

Returns the filter adapter struct for the given struct module.

This auto-detects the appropriate filter adapter based on the backing data source (Ecto repo, Elasticsearch, etc.).

Options

  • :repo - Ecto repo module (auto-detected from struct if not provided)
  • :extensions - Database extensions (e.g., [:postgis])
  • :filter_adapter - Explicit filter adapter struct to use

Examples

# Auto-detect from Ecto schema
filter_adapter = Adapter.filter_adapter_for(MyApp.User)
#=> %GreenFairy.Adapters.Ecto.Postgres{repo: MyApp.Repo}

# With PostGIS extension
filter_adapter = Adapter.filter_adapter_for(MyApp.User, extensions: [:postgis])
#=> %GreenFairy.Adapters.Ecto.Postgres{repo: MyApp.Repo, extensions: [:postgis]}

# Explicit adapter
filter_adapter = Adapter.filter_adapter_for(MyApp.User,
  filter_adapter: %Adapters.Elasticsearch{index: "users"}
)

find_adapter(module, override)

Finds an adapter that can handle the given module.

Checks adapters in order:

  1. Explicit adapter override
  2. Configured adapters from :green_fairy, :adapters
  3. Default adapters (Ecto)

supports?(adapter, capability)

Checks if an adapter supports a capability.