AI Features

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 ...

Ask