defmodule Ex4j do @moduledoc """ An Ecto-inspired Cypher DSL and Neo4j driver for Elixir. Ex4j provides a macro-based query builder that compiles Elixir expressions into parameterized Cypher queries, preventing injection and enabling Neo4j query plan caching. ## Setup Add the dependency: def deps do [{:ex4j, "~> 2.0"}] end Configure the connection: config :ex4j, Boltx, url: "bolt://localhost:7687", basic_auth: [username: "neo4j", password: "password"], pool_size: 10 # For Neo4j Aura: config :ex4j, Boltx, url: "neo4j+s://your-instance.databases.neo4j.io", basic_auth: [username: "your_username", password: "your_password"], database: "your-database-name", pool_size: 5 Define a Repo: defmodule MyApp.Repo do use Ex4j.Repo, otp_app: :my_app end ## Defining Schemas defmodule MyApp.User do use Ex4j.Schema node "User" do field :name, :string field :age, :integer field :email, :string end def changeset(user, attrs) do user |> cast(attrs, [:name, :age, :email]) |> validate_required([:name, :email]) end end defmodule MyApp.Comment do use Ex4j.Schema node "Comment" do field :content, :string end end defmodule MyApp.HasComment do use Ex4j.Schema relationship "HAS_COMMENT" do from MyApp.User to MyApp.Comment field :created_at, :utc_datetime end end ## Building Queries import Ex4j.Query.API # Read with macro-based WHERE (parameterized, safe) User |> match(as: :u) |> where([u], u.age > 18 and u.name == "Tiago") |> return([:u]) |> limit(10) |> MyApp.Repo.all() # Runtime values with pin operator name = "Tiago" User |> match(as: :u) |> where([u], u.name == ^name) |> return([u], [:name, :age]) |> MyApp.Repo.all() # Relationship traversal User |> match(as: :u) |> edge(HasComment, as: :r, from: :u, to: :c, direction: :out) |> match(Comment, as: :c) |> where([c], c.content =~ "Article") |> return([:u, :c]) |> MyApp.Repo.all() # CREATE query() |> create(User, as: :u, set: %{name: "Alice", age: 30, email: "alice@example.com"}) |> return([:u]) |> MyApp.Repo.run() # MERGE + SET query() |> merge(User, as: :u, match: %{email: "alice@example.com"}) |> set(:u, :name, "Alice Updated") |> return([:u]) |> MyApp.Repo.run() # DELETE User |> match(as: :u) |> where([u], u.name == "Alice") |> delete(:u, detach: true) |> MyApp.Repo.run() # Dynamic queries for runtime conditions dynamic = Enum.reduce(filters, dynamic([u], true), fn {:name, name}, dyn -> dynamic([u], ^dyn and u.name == ^name) {:min_age, age}, dyn -> dynamic([u], ^dyn and u.age >= ^age) end) User |> match(as: :u) |> where(^dynamic) |> return([:u]) |> MyApp.Repo.all() # Fragment for raw Cypher User |> match(as: :u) |> where([u], fragment("u.score > duration(?)", "P1Y")) |> return([:u]) |> MyApp.Repo.all() # Raw Cypher query (full escape hatch) MyApp.Repo.query("MATCH (n:User) RETURN n LIMIT 25") # Transactions MyApp.Repo.transaction(fn -> MyApp.Repo.run(create_query) MyApp.Repo.run(relationship_query) end) ## Cypher 25 Support Ex4j supports the latest Cypher 25 features including: - Walk semantics (REPEATABLE ELEMENTS) - Vector operations (vector(), vector_distance(), etc.) - Full aggregation function set - Subqueries with CALL {} - UNION / UNION ALL - Variable-length relationship patterns """ end