Convenience helpers for creating the Neo4j POINT indexes that back AshNeo4j's spatial pushdown (#275).
After #274, the indexable form of a geometry is a scalar Neo4j POINT companion that the data layer writes automatically alongside the canonical RFC 7946 GeoJSON:
- a
%Geo.Point{}gets one companion at<property>.point; - any other geometry gets two,
<property>.bbSW/<property>.bbNE(the bounding-box cornerspoint.withinBBoxreads).
Hand-writing the index Cypher means knowing the resource's Neo4j label, the attribute → property translation, the companion suffix convention, and — for bbox geometries — that there are two companions. This module applies that internal knowledge for you, resolving everything from the resource module and attribute name.
# top-level Point attribute → one `.point` index
AshNeo4j.Spatial.create_index(Place, :location)
# non-Point geometry → both bbSW and bbNE indexes
AshNeo4j.Spatial.create_index(Place, :bounds)
# nested geometry — [attribute, field...] path into a TypedStruct
AshNeo4j.Spatial.create_index(Place, [:pet, :home])
# rebuild (DROP IF EXISTS + CREATE) after a storage-shape change
AshNeo4j.Spatial.create_index(Place, :location, recreate: true)Consistent with AshNeo4j's "no migrations, index lifecycle is the
operator's concern" stance (#45) — this is an ergonomic tool you
choose to call (e.g. from a start-up task), not automatic
behaviour. create_index/3 uses IF NOT EXISTS, so it's safe to run
repeatedly. Indexes are schema objects independent of data: clearing
nodes does not drop them, and they re-populate as nodes are written.
Naming
Each companion gets its own index named <base>_<suffix>, where
suffix is point / bbSW / bbNE and base defaults to
<label>_<dotted_property> (label lower-cased, dots as underscores) —
e.g. place_location_point, place_bounds_bbSW. Pass name: to
override the base.
Dry run
index_statements/3 returns the exact CREATE Cypher without
touching the database — useful for review, a migration file, or
testing.
Summary
Types
An attribute name, or a [attribute, field, ...] path into a nested geometry.
Functions
Creates the POINT index(es) backing spatial pushdown for attr_or_path.
Drops the POINT index(es) for attr_or_path (both corners for a bbox
geometry). Uses IF EXISTS, so it's a no-op when absent.
Returns {:ok, statements} — the CREATE POINT INDEX Cypher that
create_index/3 would run — without touching the database, or
{:error, reason}. Honours :name; ignores :recreate.
Types
Functions
@spec create_index(Ash.Resource.t(), attr_or_path(), keyword()) :: {:ok, [Bolty.Response.t()]} | {:error, term()}
Creates the POINT index(es) backing spatial pushdown for attr_or_path.
Returns {:ok, responses} — a list with one %Bolty.Response{} per
index created (one for a Point, two for a bbox geometry) — or
{:error, reason} on the first failure or if the attribute can't be
resolved to a geometry.
Options
:recreate— whentrue,DROP INDEX ... IF EXISTSprecedes eachCREATE. Needed only when the index definition changes; data churn never requires it. Defaults tofalse.:name— override the auto-derived base index name. The companion suffix (_point/_bbSW/_bbNE) is still appended.
@spec drop_index(Ash.Resource.t(), attr_or_path(), keyword()) :: {:ok, [Bolty.Response.t()]} | {:error, term()}
Drops the POINT index(es) for attr_or_path (both corners for a bbox
geometry). Uses IF EXISTS, so it's a no-op when absent.
Returns {:ok, responses} or {:error, reason}. Pass :name to match
a base name overridden at create time.
@spec index_statements(Ash.Resource.t(), attr_or_path(), keyword()) :: {:ok, [String.t()]} | {:error, term()}
Returns {:ok, statements} — the CREATE POINT INDEX Cypher that
create_index/3 would run — without touching the database, or
{:error, reason}. Honours :name; ignores :recreate.
AshNeo4j.Spatial.index_statements(Place, :location)
#=> {:ok, ["CREATE POINT INDEX place_location_point IF NOT EXISTS FOR (n:Place) ON (n.`location.point`)"]}