Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Neo4j

livebooks/boltx/neo4j.livemd

Neo4j

Mix.install([
  {:boltx, "~> 0.0.6"},
  {:kino, "~> 0.14.2"}
])

Connect to Neo4j

opts = [
    hostname: "neo4j-for-livebook",
    scheme: "bolt",
    auth: [username: "neo4j", password: ""],
    user_agent: "boltxTest/1",
    pool_size: 15,
    max_overflow: 3,
    prefix: :default
]

{:ok, conn} = Boltx.start_link(opts)
conn
|> Boltx.query!("RETURN 1 AS number")
|> Boltx.Response.first()

Add nodes

node =
  conn
  |> Boltx.query!("""
  CREATE
  (node:Sweet {
    name: "チョコレートケーキ",
    category: "ケーキ",
    brand: "スイーツベーカリー",
    price: 450
  })
  RETURN node
  """)
  |> Map.get(:results)
  |> hd()
entities =
  [
    %{
      label: "Sweet",
      properties: %{
        name: "イチゴのチーズケーキ",
        category: "ケーキ",
        brand: "チーズハウス",
        price: 520
      }
    },
    %{
      label: "Sweet",
      properties: %{
        name: "アップルパイ",
        category: "パイ",
        brand: "パイファクトリー",
        price: 400
      }
    },
    %{
      label: "Sweet",
      properties: %{
        name: "チョコチップクッキー",
        category: "クッキー",
        brand: "クッキーランド",
        price: 300
      }
    },
    %{
      label: "Sweet",
      properties: %{
        name: "ストロベリーキャンディー",
        category: "キャンディー",
        brand: "キャンディーガーデン",
        price: 100
      }
    },
    %{
      label: "Ingredient",
      properties: %{
        name: "小麦粉",
        type: "粉類"
      }
    },
    %{
      label: "Ingredient",
      properties: %{
        name: "砂糖",
        type: "粉類"
      }
    },
    %{
      label: "Ingredient",
      properties: %{
        name: "卵",
        type: "液体"
      }
    },
    %{
      label: "Ingredient",
      properties: %{
        name: "バター",
        type: "乳製品"
      }
    },
    %{
      label: "Ingredient",
      properties: %{
        name: "チョコレート",
        type: "粉類"
      }
    },
    %{
      label: "Ingredient",
      properties: %{
        name: "牛乳",
        type: "乳製品"
      }
    },
    %{
      label: "Ingredient",
      properties: %{
        name: "イチゴ",
        type: "フルーツ"
      }
    },
    %{
      label: "Ingredient",
      properties: %{
        name: "リンゴ",
        type: "フルーツ"
      }
    }
  ]
Boltx.transaction(conn, fn conn ->
  entities
  |> Enum.each(fn entity ->
    properties =
      entity.properties
      |> Enum.map(fn {key, value} -> "#{key}: \"#{value}\"" end)
      |> Enum.join(",")

    query =
      """
      CREATE
      (node:#{entity.label} {
        #{properties}
      })
      """

    Boltx.query!(conn, query)
  end)
end)

Create constraints

Boltx.query!(conn, """
CREATE CONSTRAINT FOR (s:Sweet) REQUIRE (s.name) IS UNIQUE
""")
Boltx.query!(conn, """
CREATE CONSTRAINT FOR (i:Ingredient) REQUIRE (i.name) IS UNIQUE
""")

Create index

Boltx.query!(conn, """
CREATE INDEX FOR (s:Sweet) ON (s.price)
""")

Add edges

edge =
  conn
  |> Boltx.query!("""
  MATCH (s:Sweet {name:"チョコレートケーキ"})
  MATCH (i:Ingredient {name:"小麦粉"})
  CREATE (s)-[r:CONTAINS]->(i)
  RETURN r
  """)
  |> Map.get(:results)
  |> hd()
relations =
  [
    {"チョコレートケーキ", "CONTAINS", "卵"},
    {"チョコレートケーキ", "CONTAINS", "バター"},
    {"チョコレートケーキ", "CONTAINS", "チョコレート"},
    {"イチゴのチーズケーキ", "CONTAINS", "小麦粉"},
    {"イチゴのチーズケーキ", "CONTAINS", "砂糖"},
    {"イチゴのチーズケーキ", "CONTAINS", "卵"},
    {"イチゴのチーズケーキ", "CONTAINS", "バター"},
    {"イチゴのチーズケーキ", "CONTAINS", "牛乳"},
    {"イチゴのチーズケーキ", "CONTAINS", "イチゴ"},
    {"アップルパイ", "CONTAINS", "小麦粉"},
    {"アップルパイ", "CONTAINS", "砂糖"},
    {"アップルパイ", "CONTAINS", "バター"},
    {"アップルパイ", "CONTAINS", "リンゴ"},
    {"チョコチップクッキー", "CONTAINS", "小麦粉"},
    {"チョコチップクッキー", "CONTAINS", "砂糖"},
    {"チョコチップクッキー", "CONTAINS", "バター"},
    {"チョコチップクッキー", "CONTAINS", "チョコレート"},
    {"ストロベリーキャンディー", "CONTAINS", "砂糖"},
    {"ストロベリーキャンディー", "CONTAINS", "イチゴ"}
  ]
Boltx.transaction(conn, fn conn ->
  relations
  |> Enum.each(fn {src_name, relation, dest_name} ->
    query =
      """
      MATCH (s:Sweet {name:"#{src_name}"})
      MATCH (i:Ingredient {name:"#{dest_name}"})
      CREATE (s)-[r:#{relation}]->(i)
      """

    Boltx.query!(conn, query)
  end)
end)

Query

conn
|> Boltx.query!("""
MATCH (i:Ingredient {name: "チョコレート"})
RETURN i.type AS タイプ
""")
|> Map.get(:results)
|> Kino.DataTable.new()
conn
|> Boltx.query!("""
MATCH (s:Sweet) WHERE s.price > 400
RETURN s.name AS お菓子名, s.price AS 価格
""")
|> Map.get(:results)
|> Kino.DataTable.new()
conn
|> Boltx.query!("""
MATCH (s:Sweet)-[:CONTAINS]->(i:Ingredient {name: "チョコレート"})
RETURN s.name AS お菓子名, s.brand AS ブランド
""")
|> Map.get(:results)
|> Kino.DataTable.new()
conn
|> Boltx.query!("""
MATCH (s:Sweet)
WHERE NOT (s)-[:CONTAINS]->(:Ingredient {type: "乳製品"})
RETURN s.name AS 乳製品不使用のお菓子
""")
|> Map.get(:results)
|> Kino.DataTable.new()

Delete data

Boltx.query!(conn, "MATCH (n) DETACH DELETE n")
constraints =
  conn
  |> Boltx.query!("SHOW CONSTRAINTS YIELD name")
  |> Map.get(:results)
constraints
|> Enum.each(fn %{"name" => name} ->
  Boltx.query!(conn, "DROP CONSTRAINT #{name}")
end)
indexes =
  conn
  |> Boltx.query!("SHOW INDEXES YIELD name")
  |> Map.get(:results)
indexes
|> Enum.each(fn %{"name" => name} ->
  Boltx.query!(conn, "DROP INDEX #{name}")
end)