...

/

Service Contexts

Service Contexts

Learn how to avoid naming conflicts and switch between various graph models seamlessly.

We'll cover the following...

Problem

If we import two graph modules, we’ll run into a conflict in our function names.

iex> import NativeGraph
NativeGraph
iex> import PropertyGraph
PropertyGraph
iex> list_graphs

This won't do. If we try the commands above in the following terminal, we get a CompileError:

#---
# Excerpted from "Exploring Graphs with Elixir",
# published by The Pragmatic Bookshelf.
# Copyrights apply to this code. It may not be used to create training material,
# courses, books, articles, and the like. Contact us if you are in doubt.
# We make no guarantees that this code is fit for any purpose.
# Visit https://pragprog.com/titles/thgraphs for more book information.
#---
defmodule PropertyGraph.Service do
  @behaviour GraphCommons.Service

  @cypher_delete """
  MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n,r
  """

  @cypher_read """
  MATCH (n) OPTIONAL MATCH (n)-[r]-() RETURN DISTINCT n, r
  """

  @cypher_info """
  CALL apoc.meta.stats()
  YIELD labels, labelCount, nodeCount, relCount, relTypeCount
  """

  ## GRAPH

  def graph_create(graph) do
    graph_delete()
    graph_update(graph)
  end

  def graph_delete(), do: Bolt.Sips.query!(Bolt.Sips.conn(), @cypher_delete)

  def graph_read(), do: Bolt.Sips.query!(Bolt.Sips.conn(), @cypher_read)

  def graph_update(%GraphCommons.Graph{} = graph),
    do: Bolt.Sips.query!(Bolt.Sips.conn(), graph.data)

  def graph_info() do
    {:ok, [stats]} =
      @cypher_info
      |> PropertyGraph.new_query()
      |> query_graph

    %GraphCommons.Service.GraphInfo{
      type: :property,
      file: "",
      num_nodes: stats["nodeCount"],
      num_edges: stats["relCount"],
      labels: Map.keys(stats["labels"])
    }
  end

  ## QUERY

  def query_graph(%GraphCommons.Query{} = query), do: query_graph(query, %{})

  def query_graph(%GraphCommons.Query{} = query, params) do
    :property = query.type

    Bolt.Sips.query(Bolt.Sips.conn(), query.data, params)
    |> case do
      {:ok, response} -> parse_response(response, false)
      {:error, error} -> {:error, error}
    end
  end

  def query_graph!(%GraphCommons.Query{} = query), do: query_graph!(query, %{})

  def query_graph!(%GraphCommons.Query{} = query, params) do
    :property = query.type

    Bolt.Sips.query(Bolt.Sips.conn(), query.data, params)
    |> case do
      {:ok, response} -> parse_response(response, true)
      {:error, error} -> raise "! #{inspect error}"
    end
  end

  # 
  # def query_graph!(%GraphCommons.Query{} = query, param \\ %{}) do
  #   :property = query.type
  #
  #   Bolt.Sips.query(Bolt.Sips.conn(), query.data, param)
  #   |> case do
  #     {:ok, response} -> parse_response(response, false)
  #     {:error, error} -> {:error, error}
  #   end
  # end
  # 

  defp parse_response(%Bolt.Sips.Response{} = response, bang) do
    %Bolt.Sips.Response{type: type} = response

    case type do
      r when r in ["r", "rw"] ->
        %Bolt.Sips.Response{results: results} = response
        unless bang, do: {:ok, results}, else: results
      s when s in ["s"] ->
        %Bolt.Sips.Response{results: results} = response
        unless bang, do: {:ok, results}, else: results
      w when w in ["w"] ->
        %Bolt.Sips.Response{stats: stats} = response
        unless bang, do: {:ok, stats}, else: stats
    end
  end

end
Importing the two modules will result in a conflict

Solution

We need to be able to delete any previous graph module import before attempting a new import.

There is a way. Basically, if we do an import, restricting the ...