Sorcery.Query behaviour (sorcery v0.4.4)

A query module defines, in plain elixir data structures, the kind of data we want to watch for.

The syntax takes some inspiration from Datalog, but with many differences as well.

defmodule Src.Queries.GetBattle do

  use Sorcery.Query, %{

    # Args will be passed in later when the query is called.
    args: %{
      player_id: :integer
    },

    # This is the meat of any query. Read it one row at a time.
    # Every row has either 3, 4, columns
    where: [
      # 4 column syntax:
      # [lvar,        tk,            attr,         value]
      #
      #
      # So we start with an lvar (or 'Logic Variable') called "?player"
      # It represents a set of entities with the Schema of :player
      # And we are filtering them such that ?player.id == args.player_id
      # The arg MUST be declared in the args map.
      [ "?player",    :player,       :id,          :args_player_id],

      # Now we make a new lvar called "?team"
      # See how it now references the previous lvar?
      # So we are filtering all teams such that team.id matches ANY ?player.team_id
      [ "?team",      :team,         :id,          "?player.team_id"],
      [ "?arena",     :battle_arena, :id,          "?team.location_id"],

      # This is not the same as ?team.
      # Its a new lvar using the same schema, but with a different set of filters
      # So we're getting all teams such that team.location_id == ?arena.id
      [ "?all_teams", :team,         :location_id, "?arena.id"],

      # Now we use the 3 column syntax, just to avoid repetition.
      # This could also be rewritten as two rows:
      # ["?all_players", :player, :team_id, "?all_teams.id"],
      # ["?all_players", :player, :health, {:>, 0}],
      [ "?all_players", :player, [
        {:team_id, "?all_teams.id"},
        {:health, {:>, 0}},
      ]],
      # Notice the value above {:>, 0}
      # By default, every value automatically expands under the hood to {:==, value}
      # But if you want to manually use an operator, you can.
      # Possible operators: :==, :!=, :>, :>=, :<, :<=, :in


      [ "?spells", :spell_instance, :player_id, "?all_players.id"],
      [ "?spell_types", :spell_type, :id, "?spells.type_id"],
    ],

    # Without a find map, the query returns no results.
    # We do not necessarily need all the lvars, nor all the fields
    # If we want all available fields, use :*
    # Otherwise pass in a list of specific ones. The :id attr is automatically added.
    find: %{
      "?arena" => :*,
      "?all_teams" => [:name, :location_id],
      "?all_players" => :*,
      "?spells" => :*,
      "?spell_types" => :*,
    }
  }

end

Summary

Callbacks

Return a list of WhereClause structs for the Query module

Returns the finds. This differs from raw_struct().finds because :id fields have been added.

Returns a Sorcery.Query struct.

A unique list of all tks mentioned by this query.

Functions

If you have a map in the format of %{tk => %{id => %{...entity...}}} Then you can use it like a database and run the query against it.

Types

@type t() :: %Sorcery.Query{
  args: term(),
  find: map(),
  lvar_tks: term(),
  refstr: String.t(),
  where: [Sorcery.Query.WhereClause]
}

Callbacks

@callback clauses(args :: map()) :: [Sorcery.Query.WhereClause]

Return a list of WhereClause structs for the Query module

Examples

iex> [clause1 | _] = Src.Queries.GetBattle.clauses(%{player_id: 1})
iex> clause1
%Sorcery.Query.WhereClause{lvar: :"?player", tk: :player, attr: :id, left: nil, right: 1, op: :==, other_lvar: nil, other_lvar_attr: nil, arg_name: nil, right_type: :literal}
@callback finds() :: map()

Returns the finds. This differs from raw_struct().finds because :id fields have been added.

Examples

iex> Src.Queries.GetBattle.finds()
%{"?arena": :*, "?all_teams": [:location_id, :id, :name], "?all_players": :*, "?spells": :*, "?spell_types": :*}
@callback raw_struct() :: %Sorcery.Query{
  args: term(),
  find: term(),
  lvar_tks: term(),
  refstr: term(),
  where: term()
}

Returns a Sorcery.Query struct.

Examples

iex> q = Src.Queries.GetBattle.raw_struct()
iex> is_struct(q, Sorcery.Query)
iex> q.find["?all_players"]
:*
iex> [clause1 | _] = q.where
iex> clause1
["?player", :player, :id, :args_player_id]
iex> Enum.at(q.lvar_tks, 3)
{"?all_teams", :team}
@callback tks_affected() :: [atom()]

A unique list of all tks mentioned by this query.

Examples

iex> Src.Queries.GetBattle.tks_affected()
[:player, :team, :battle_arena, :spell_instance, :spell_type]

Functions

Link to this function

from_tk_map(query_mod, args, data)

If you have a map in the format of %{tk => %{id => %{...entity...}}} Then you can use it like a database and run the query against it.