This is an automated email from the ASF dual-hosted git repository.

mattcasters pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new 6ab2e1acd5 Fixes #8836 : Graph index and Graph constraint: resolve 
variables in … (#8849)
6ab2e1acd5 is described below

commit 6ab2e1acd54e9df7f8371c5f05f866178b9bbad4
Author: Bart Maertens <[email protected]>
AuthorDate: Mon Oct 12 11:25:00 2026 +0200

    Fixes #8836 : Graph index and Graph constraint: resolve variables in … 
(#8849)
    
    * Fixes #8836 : Graph index and Graph constraint: resolve variables in 
names, labels and properties, and support Apache AGE indexes
    
    * Issue #8836 : Apache AGE index: ignore the error of a label created 
concurrently by another session
---
 .../apache/hop/core/graph/IGraphConnection.java    |  19 ++
 .../modules/ROOT/pages/technology/age/index.adoc   |   6 +-
 .../pages/workflow/actions/graph-constraint.adoc   |   3 +
 .../ROOT/pages/workflow/actions/graph-index.adoc   |  22 +-
 .../main-graph-index-variables.hwf}                | 227 ++++++++++-----------
 .../graph-vector-search-nodes-validation.hpl       |   2 +-
 .../main-graph-vector-search-nodes.hwf             |  39 ++--
 .../graph-vector-search-nodes-validation.hpl       |   2 +-
 .../main-graph-vector-search-nodes.hwf             |  39 ++--
 .../datasets/graph-vector-search-nodes-golden.csv  |   6 +
 .../dataset/graph-vector-search-nodes-golden.json  |  40 ++++
 .../neo4j-graph-model/VectorSearchDocs.json        |  38 ++++
 .../graph-vector-search-nodes-validation UNIT.json |  40 ++++
 .../graph-vector-search-nodes-build.hpl            | 146 +++++++++++++
 .../graph-vector-search-nodes-validation.hpl       |   2 +-
 .../main-graph-vector-search-nodes.hwf             |  39 ++--
 .../org/apache/hop/age/AgeGraphConnection.java     |  18 ++
 .../java/org/apache/hop/age/AgeGraphDialect.java   |  90 +++++++-
 .../age/AgeGraphDialectCharacterizationTest.java   |  15 +-
 .../src/test/java/org/apache/hop/age/AgeIT.java    |  42 ++++
 .../neo4j/actions/constraint/Neo4jConstraint.java  |  27 ++-
 .../actions/constraint/Neo4jConstraintDialog.java  |  11 +-
 .../apache/hop/neo4j/actions/index/Neo4jIndex.java |  27 ++-
 .../hop/neo4j/actions/index/Neo4jIndexDialog.java  |   9 +-
 .../hop/neo4j/shared/NeoConnectionUtils.java       |  16 +-
 .../constraint/Neo4jConstraintVariablesTest.java   |  67 ++++++
 .../actions/index/Neo4jIndexVariablesTest.java     |  94 +++++++++
 .../neo4j/shared/ThirdPartyGraphDialectTest.java   |  36 ++++
 28 files changed, 927 insertions(+), 195 deletions(-)

diff --git a/core/src/main/java/org/apache/hop/core/graph/IGraphConnection.java 
b/core/src/main/java/org/apache/hop/core/graph/IGraphConnection.java
index fff97d0b8c..6a9ae4930a 100644
--- a/core/src/main/java/org/apache/hop/core/graph/IGraphConnection.java
+++ b/core/src/main/java/org/apache/hop/core/graph/IGraphConnection.java
@@ -57,6 +57,25 @@ public interface IGraphConnection extends AutoCloseable {
     return executeWrite(work);
   }
 
+  /**
+   * Run an index or constraint statement of the dialect. By default the 
statement is run like any
+   * other, in a write transaction or on its own.
+   *
+   * @param statement The statement from the dialect
+   * @param inTransaction True to run it in a write transaction, false to run 
it on its own
+   */
+  default void executeSchemaStatement(String statement, boolean inTransaction) 
throws HopException {
+    if (inTransaction) {
+      executeWrite(
+          transaction -> {
+            transaction.execute(statement, Map.of());
+            return true;
+          });
+    } else {
+      execute(statement, Map.of());
+    }
+  }
+
   /**
    * @return The dialect of the database: what it supports and the syntax of 
its index and
    *     constraint statements. By default Cypher with the syntax of Neo4j 5.
diff --git a/docs/hop-user-manual/modules/ROOT/pages/technology/age/index.adoc 
b/docs/hop-user-manual/modules/ROOT/pages/technology/age/index.adoc
index ae2f5c2a8c..c6e5a2a5ca 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/technology/age/index.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/technology/age/index.adoc
@@ -41,13 +41,14 @@ Hop loads the AGE extension in every connection, so there 
is no need to set `sha
 * xref:pipeline/transforms/cypher-builder.adoc[Cypher builder]
 * xref:pipeline/transforms/split-graph.adoc[Split graph] and 
xref:pipeline/transforms/generate-graph-csvs.adoc[Generate graph CSVs]
 * xref:workflow/actions/graph-script.adoc[Graph script]
+* The xref:workflow/actions/graph-index.adoc[Graph index] action, for named 
indexes on node properties: it creates PostgreSQL indexes, see below.
 * xref:workflow/actions/check-graph-database-connections.adoc[Check graph 
database connections]
 * Execution logging (`HOP_GRAPH_LOGGING_CONNECTION`), the 
xref:pipeline/transforms/get-graph-logging-info.adoc[Get graph logging info] 
transform and the xref:hop-gui/perspective-graph-logging.adoc[Graph logging 
perspective]
 * The xref:metadata-types/graphs/graph-database-location.adoc[graph database 
location] for execution information
 
 == What doesn't work
 
-* The xref:workflow/actions/graph-index.adoc[Graph index] and 
xref:workflow/actions/graph-constraint.adoc[Graph constraint] actions: AGE 
indexes and constraints are PostgreSQL indexes and constraints, created with 
SQL, see below.
+* The xref:workflow/actions/graph-constraint.adoc[Graph constraint] action, 
and the Graph index action for vector indexes and indexes on relationship 
properties: AGE indexes and constraints are PostgreSQL indexes and constraints.
 * *Create indexes* in Graph output and Cypher output, for the same reason.
 * `RETURN *` and statements which return unnamed values.
 * Nodes with more than one label, and labels with spaces.
@@ -59,7 +60,8 @@ Hop loads the AGE extension in every connection, so there is 
no need to set `sha
 * A node has one label, and label names can't contain spaces. Graph names have 
at least 3 characters.
 * AGE has no temporal types: dates and times are stored as ISO strings.
 * Indexes are PostgreSQL indexes on the tables AGE keeps per label, created 
with SQL.
-The xref:workflow/actions/graph-index.adoc[Graph index] and 
xref:workflow/actions/graph-constraint.adoc[Graph constraint] actions don't 
support AGE, but the *Show indexes DDL* button of the graph database location 
gives the SQL for its indexes, for example:
+The xref:workflow/actions/graph-index.adoc[Graph index] action creates them on 
node properties, and creates the label first when it has no nodes yet.
+The *Show indexes DDL* button of the graph database location gives the SQL for 
its indexes, for example:
 +
 [source,sql]
 ----
diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/workflow/actions/graph-constraint.adoc
 
b/docs/hop-user-manual/modules/ROOT/pages/workflow/actions/graph-constraint.adoc
index 0e804cd2bd..629a1630a1 100644
--- 
a/docs/hop-user-manual/modules/ROOT/pages/workflow/actions/graph-constraint.adoc
+++ 
b/docs/hop-user-manual/modules/ROOT/pages/workflow/actions/graph-constraint.adoc
@@ -39,6 +39,9 @@ For similar operations on indexes, check the 
xref:workflow/actions/graph-index.a
 ** Object name: the label of the node or relationship you want to update a 
constraint for
 ** Property: the property to put the constraint on (for NODE_KEY, use 
comma-separated properties like "id,name")
 
+The constraint name, object name and properties can be variables, for example 
`${PERSON_KEY}`.
+The action resolves them before it builds the statement, for create and drop, 
and the Cypher preview shows the resolved values.
+
 [NOTE]
 ====
 NODE_KEY constraints are only available in Neo4j Enterprise Edition. NODE_KEY 
constraints require multiple properties (comma-separated) and can only be 
applied to nodes, not relationships.
diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/workflow/actions/graph-index.adoc 
b/docs/hop-user-manual/modules/ROOT/pages/workflow/actions/graph-index.adoc
index d7bae87e23..ae37987fe6 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/workflow/actions/graph-index.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/workflow/actions/graph-index.adoc
@@ -24,7 +24,7 @@ under the License.
 
 == Description
 
-The `Graph index` action (previously Neo4j index) creates or drops indexes on 
node or relationship properties in a graph database, in the syntax of the 
database: Neo4j, Memgraph or FalkorDB.
+The `Graph index` action (previously Neo4j index) creates or drops indexes on 
node or relationship properties in a graph database, in the syntax of the 
database: Neo4j, Memgraph, FalkorDB or Apache AGE.
 
 For similar operations on constraints, check the 
xref:workflow/actions/graph-constraint.adoc[Graph constraint] action.
 
@@ -42,7 +42,25 @@ For similar operations on constraints, check the 
xref:workflow/actions/graph-con
 ** Vector similarity: VECTOR only, COSINE (the default) or EUCLIDEAN
 ** Vector capacity: VECTOR on Memgraph only, the number of vectors to reserve 
room for, 1000 when empty
 
-Vector dimensions and capacity can be variables.
+The index name, object name, properties, vector dimensions and vector capacity 
can be variables, for example `${CHUNK_INDEX}` to keep a test and a production 
index apart.
+The action resolves them before it builds the statement, for create and drop, 
and the Cypher preview shows the resolved values.
+Use the same variable for the index name in 
xref:pipeline/transforms/graph-vector-search.adoc[Graph vector search].
+
+== Databases
+
+[options="header"]
+|===
+|Database |Range index |Vector index
+
+|Neo4j |Nodes and relationships |Nodes and relationships
+|Memgraph |Nodes and relationships |Nodes and relationships
+|FalkorDB |Nodes and relationships |Nodes and relationships
+|Apache AGE |Nodes, with a name |No
+|Amazon Neptune, Gremlin |No |No
+|===
+
+On Apache AGE an index is a PostgreSQL index on the table of the label, 
created and dropped with SQL.
+The action creates the label first when there are no nodes with it yet, so the 
index can be created before the data is loaded.
 
 == Vector indexes
 
diff --git 
a/integration-tests/memgraph/tests/graph-vector-search-nodes/main-graph-vector-search-nodes.hwf
 
b/integration-tests/age/tests/graph-index-variables/main-graph-index-variables.hwf
similarity index 52%
copy from 
integration-tests/memgraph/tests/graph-vector-search-nodes/main-graph-vector-search-nodes.hwf
copy to 
integration-tests/age/tests/graph-index-variables/main-graph-index-variables.hwf
index 3b95ea27e9..125771bec3 100644
--- 
a/integration-tests/memgraph/tests/graph-vector-search-nodes/main-graph-vector-search-nodes.hwf
+++ 
b/integration-tests/age/tests/graph-index-variables/main-graph-index-variables.hwf
@@ -18,21 +18,36 @@ limitations under the License.
 
 -->
 <workflow>
-  <name>main-graph-vector-search-nodes</name>
+  <name>main-graph-index-variables</name>
   <name_sync_with_filename>Y</name_sync_with_filename>
-  <description>Graph vector search on the vector index of nodes</description>
+  <description>Graph index on Apache AGE with the index name, label and 
property in variables</description>
   <extended_description/>
   <workflow_version/>
   <created_user>-</created_user>
-  <created_date>2026/10/07 12:00:00.000</created_date>
+  <created_date>2026/10/10 12:00:00.000</created_date>
   <modified_user>-</modified_user>
-  <modified_date>2026/10/07 12:00:00.000</modified_date>
+  <modified_date>2026/10/10 12:00:00.000</modified_date>
   <parameters>
     <parameter>
       <name>TEST_FOLDER</name>
-      <default_value>graph-vector-search-nodes</default_value>
+      <default_value>graph-index-variables</default_value>
       <description/>
     </parameter>
+    <parameter>
+      <name>GRAPH_INDEX</name>
+      <default_value>graph_index_variables_id</default_value>
+      <description>The name of the index: the action resolves it</description>
+    </parameter>
+    <parameter>
+      <name>GRAPH_LABEL</name>
+      <default_value>IndexVariablesChunk</default_value>
+      <description>The label of the index: it needs no nodes yet</description>
+    </parameter>
+    <parameter>
+      <name>GRAPH_PROPERTY</name>
+      <default_value>chunk_id</default_value>
+      <description>The indexed property</description>
+    </parameter>
   </parameters>
   <actions>
     <action>
@@ -54,21 +69,19 @@ limitations under the License.
       <attributes_hac/>
     </action>
     <action>
-      <name>drop indexes before</name>
+      <name>drop index before</name>
       <description/>
       <type>NEO4J_INDEX</type>
       <attributes/>
       <connection>Test</connection>
       <updates>
         <update>
-          <index_name>graph_vector_search_nodes</index_name>
-          <object_name>VectorSearchDoc</object_name>
-          <object_properties>embedding</object_properties>
+          <index_name>${GRAPH_INDEX}</index_name>
+          <object_name>${GRAPH_LABEL}</object_name>
+          <object_properties>${GRAPH_PROPERTY}</object_properties>
           <object_type>NODE</object_type>
           <update_type>DROP</update_type>
-          <index_type>VECTOR</index_type>
-          <vector_dimensions>3</vector_dimensions>
-          <vector_similarity>COSINE</vector_similarity>
+          <index_type>RANGE</index_type>
         </update>
       </updates>
       <parallel>N</parallel>
@@ -77,140 +90,105 @@ limitations under the License.
       <attributes_hac/>
     </action>
     <action>
-      <name>cleanup-database.hwf</name>
-      <description/>
-      <type>WORKFLOW</type>
-      <attributes/>
-      <run_configuration>local</run_configuration>
-      <filename>${PROJECT_HOME}/tests/shared/cleanup-database.hwf</filename>
-      <params_from_previous>N</params_from_previous>
-      <exec_per_row>N</exec_per_row>
-      <set_logfile>N</set_logfile>
-      <logfile/>
-      <logext/>
-      <add_date>N</add_date>
-      <add_time>N</add_time>
-      <loglevel>Nothing</loglevel>
-      <wait_until_finished>Y</wait_until_finished>
-      <create_parent_folder>N</create_parent_folder>
-      <parameters>
-        <pass_all_parameters>Y</pass_all_parameters>
-      </parameters>
-      <set_append_logfile>N</set_append_logfile>
-      <parallel>N</parallel>
-      <xloc>384</xloc>
-      <yloc>96</yloc>
-      <attributes_hac/>
-    </action>
-    <action>
-      <name>create indexes</name>
+      <name>create index</name>
       <description/>
       <type>NEO4J_INDEX</type>
       <attributes/>
       <connection>Test</connection>
       <updates>
         <update>
-          <index_name>graph_vector_search_nodes</index_name>
-          <object_name>VectorSearchDoc</object_name>
-          <object_properties>embedding</object_properties>
+          <index_name>${GRAPH_INDEX}</index_name>
+          <object_name>${GRAPH_LABEL}</object_name>
+          <object_properties>${GRAPH_PROPERTY}</object_properties>
           <object_type>NODE</object_type>
           <update_type>CREATE</update_type>
-          <index_type>VECTOR</index_type>
-          <vector_dimensions>3</vector_dimensions>
-          <vector_similarity>COSINE</vector_similarity>
+          <index_type>RANGE</index_type>
         </update>
       </updates>
       <parallel>N</parallel>
-      <xloc>544</xloc>
-      <yloc>96</yloc>
-      <attributes_hac/>
-    </action>
-    <action>
-      <name>graph-vector-search-nodes-build.hpl</name>
-      <description/>
-      <type>PIPELINE</type>
-      <attributes/>
-      
<filename>${PROJECT_HOME}/tests/graph-vector-search-nodes/graph-vector-search-nodes-build.hpl</filename>
-      <params_from_previous>N</params_from_previous>
-      <exec_per_row>N</exec_per_row>
-      <clear_rows>N</clear_rows>
-      <clear_files>N</clear_files>
-      <set_logfile>N</set_logfile>
-      <logfile/>
-      <logext/>
-      <add_date>N</add_date>
-      <add_time>N</add_time>
-      <loglevel>Basic</loglevel>
-      <set_append_logfile>N</set_append_logfile>
-      <wait_until_finished>Y</wait_until_finished>
-      <create_parent_folder>N</create_parent_folder>
-      <run_configuration>local</run_configuration>
-      <parameters>
-        <pass_all_parameters>Y</pass_all_parameters>
-      </parameters>
-      <parallel>N</parallel>
-      <xloc>704</xloc>
+      <xloc>384</xloc>
       <yloc>96</yloc>
       <attributes_hac/>
     </action>
     <action>
-      <name>Run graph-vector-search-nodes tests</name>
-      <description/>
-      <type>RunPipelineTests</type>
+      <name>index created</name>
+      <description>The index exists with the resolved name, on the resolved 
label and property</description>
+      <type>EVAL_TABLE_CONTENT</type>
       <attributes/>
-      <test_names>
-        <test_name>
-          <name>graph-vector-search-nodes-validation UNIT</name>
-        </test_name>
-      </test_names>
+      <connection>age-sql</connection>
+      <schemaname/>
+      <tablename/>
+      <success_condition>rows_count_equal</success_condition>
+      <limit>1</limit>
+      <is_custom_sql>Y</is_custom_sql>
+      <is_usevars>N</is_usevars>
+      <custom_sql>SELECT indexname FROM pg_indexes WHERE schemaname = 'hop_it' 
AND tablename = 'IndexVariablesChunk' AND indexname = 
'graph_index_variables_id' AND indexdef LIKE 
'%&quot;chunk_id&quot;%'</custom_sql>
+      <add_rows_result>N</add_rows_result>
+      <clear_result_rows>Y</clear_result_rows>
       <parallel>N</parallel>
-      <xloc>864</xloc>
+      <xloc>544</xloc>
       <yloc>96</yloc>
       <attributes_hac/>
     </action>
     <action>
-      <name>drop indexes after test</name>
+      <name>drop index</name>
       <description/>
       <type>NEO4J_INDEX</type>
       <attributes/>
       <connection>Test</connection>
       <updates>
         <update>
-          <index_name>graph_vector_search_nodes</index_name>
-          <object_name>VectorSearchDoc</object_name>
-          <object_properties>embedding</object_properties>
+          <index_name>${GRAPH_INDEX}</index_name>
+          <object_name>${GRAPH_LABEL}</object_name>
+          <object_properties>${GRAPH_PROPERTY}</object_properties>
           <object_type>NODE</object_type>
           <update_type>DROP</update_type>
-          <index_type>VECTOR</index_type>
-          <vector_dimensions>3</vector_dimensions>
-          <vector_similarity>COSINE</vector_similarity>
+          <index_type>RANGE</index_type>
         </update>
       </updates>
       <parallel>N</parallel>
-      <xloc>1024</xloc>
+      <xloc>704</xloc>
+      <yloc>96</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>index dropped</name>
+      <description>The index with the resolved name is gone</description>
+      <type>EVAL_TABLE_CONTENT</type>
+      <attributes/>
+      <connection>age-sql</connection>
+      <schemaname/>
+      <tablename/>
+      <success_condition>rows_count_equal</success_condition>
+      <limit>0</limit>
+      <is_custom_sql>Y</is_custom_sql>
+      <is_usevars>N</is_usevars>
+      <custom_sql>SELECT indexname FROM pg_indexes WHERE schemaname = 'hop_it' 
AND tablename = 'IndexVariablesChunk' AND indexname = 
'graph_index_variables_id' AND indexdef LIKE 
'%&quot;chunk_id&quot;%'</custom_sql>
+      <add_rows_result>N</add_rows_result>
+      <clear_result_rows>Y</clear_result_rows>
+      <parallel>N</parallel>
+      <xloc>864</xloc>
       <yloc>96</yloc>
       <attributes_hac/>
     </action>
     <action>
-      <name>drop indexes after failure</name>
+      <name>drop index after failure</name>
       <description/>
       <type>NEO4J_INDEX</type>
       <attributes/>
       <connection>Test</connection>
       <updates>
         <update>
-          <index_name>graph_vector_search_nodes</index_name>
-          <object_name>VectorSearchDoc</object_name>
-          <object_properties>embedding</object_properties>
+          <index_name>${GRAPH_INDEX}</index_name>
+          <object_name>${GRAPH_LABEL}</object_name>
+          <object_properties>${GRAPH_PROPERTY}</object_properties>
           <object_type>NODE</object_type>
           <update_type>DROP</update_type>
-          <index_type>VECTOR</index_type>
-          <vector_dimensions>3</vector_dimensions>
-          <vector_similarity>COSINE</vector_similarity>
+          <index_type>RANGE</index_type>
         </update>
       </updates>
       <parallel>N</parallel>
-      <xloc>1024</xloc>
+      <xloc>704</xloc>
       <yloc>224</yloc>
       <attributes_hac/>
     </action>
@@ -220,7 +198,7 @@ limitations under the License.
       <type>SUCCESS</type>
       <attributes/>
       <parallel>N</parallel>
-      <xloc>1184</xloc>
+      <xloc>1024</xloc>
       <yloc>96</yloc>
       <attributes_hac/>
     </action>
@@ -229,9 +207,9 @@ limitations under the License.
       <description/>
       <type>ABORT</type>
       <attributes/>
-      <message>graph-vector-search-nodes failed</message>
+      <message>graph-index-variables failed</message>
       <parallel>N</parallel>
-      <xloc>1184</xloc>
+      <xloc>1024</xloc>
       <yloc>224</yloc>
       <attributes_hac/>
     </action>
@@ -239,67 +217,74 @@ limitations under the License.
   <hops>
     <hop>
       <from>Start</from>
-      <to>drop indexes before</to>
+      <to>drop index before</to>
       <enabled>Y</enabled>
       <evaluation>Y</evaluation>
       <unconditional>Y</unconditional>
     </hop>
     <hop>
-      <from>drop indexes before</from>
-      <to>cleanup-database.hwf</to>
+      <from>drop index before</from>
+      <to>create index</to>
       <enabled>Y</enabled>
       <evaluation>Y</evaluation>
-      <unconditional>Y</unconditional>
+      <unconditional>N</unconditional>
     </hop>
     <hop>
-      <from>cleanup-database.hwf</from>
-      <to>create indexes</to>
+      <from>create index</from>
+      <to>index created</to>
       <enabled>Y</enabled>
       <evaluation>Y</evaluation>
       <unconditional>N</unconditional>
     </hop>
     <hop>
-      <from>create indexes</from>
-      <to>graph-vector-search-nodes-build.hpl</to>
+      <from>index created</from>
+      <to>drop index</to>
       <enabled>Y</enabled>
       <evaluation>Y</evaluation>
       <unconditional>N</unconditional>
     </hop>
     <hop>
-      <from>graph-vector-search-nodes-build.hpl</from>
-      <to>Run graph-vector-search-nodes tests</to>
+      <from>drop index</from>
+      <to>index dropped</to>
       <enabled>Y</enabled>
       <evaluation>Y</evaluation>
       <unconditional>N</unconditional>
     </hop>
     <hop>
-      <from>Run graph-vector-search-nodes tests</from>
-      <to>drop indexes after test</to>
+      <from>index dropped</from>
+      <to>Success</to>
       <enabled>Y</enabled>
       <evaluation>Y</evaluation>
       <unconditional>N</unconditional>
     </hop>
     <hop>
-      <from>drop indexes after test</from>
-      <to>Success</to>
+      <from>create index</from>
+      <to>drop index after failure</to>
       <enabled>Y</enabled>
-      <evaluation>Y</evaluation>
-      <unconditional>Y</unconditional>
+      <evaluation>N</evaluation>
+      <unconditional>N</unconditional>
     </hop>
     <hop>
-      <from>Run graph-vector-search-nodes tests</from>
-      <to>drop indexes after failure</to>
+      <from>index created</from>
+      <to>drop index after failure</to>
       <enabled>Y</enabled>
       <evaluation>N</evaluation>
       <unconditional>N</unconditional>
     </hop>
     <hop>
-      <from>drop indexes after failure</from>
+      <from>drop index after failure</from>
       <to>Error out</to>
       <enabled>Y</enabled>
       <evaluation>Y</evaluation>
       <unconditional>Y</unconditional>
     </hop>
+    <hop>
+      <from>index dropped</from>
+      <to>Error out</to>
+      <enabled>Y</enabled>
+      <evaluation>N</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
   </hops>
   <notepads>
   </notepads>
diff --git 
a/integration-tests/falkordb/tests/graph-vector-search-nodes/graph-vector-search-nodes-validation.hpl
 
b/integration-tests/falkordb/tests/graph-vector-search-nodes/graph-vector-search-nodes-validation.hpl
index 9b7c53cad9..472bffadf2 100644
--- 
a/integration-tests/falkordb/tests/graph-vector-search-nodes/graph-vector-search-nodes-validation.hpl
+++ 
b/integration-tests/falkordb/tests/graph-vector-search-nodes/graph-vector-search-nodes-validation.hpl
@@ -120,7 +120,7 @@ limitations under the License.
     </partitioning>
     <connection>Test</connection>
     <element_type>NODE</element_type>
-    <index_name>graph_vector_search_nodes</index_name>
+    <index_name>${VECTOR_INDEX}</index_name>
     <label>VectorSearchDoc</label>
     <vector_property>embedding</vector_property>
     <similarity>COSINE</similarity>
diff --git 
a/integration-tests/falkordb/tests/graph-vector-search-nodes/main-graph-vector-search-nodes.hwf
 
b/integration-tests/falkordb/tests/graph-vector-search-nodes/main-graph-vector-search-nodes.hwf
index 3b95ea27e9..2db21b2f30 100644
--- 
a/integration-tests/falkordb/tests/graph-vector-search-nodes/main-graph-vector-search-nodes.hwf
+++ 
b/integration-tests/falkordb/tests/graph-vector-search-nodes/main-graph-vector-search-nodes.hwf
@@ -33,6 +33,21 @@ limitations under the License.
       <default_value>graph-vector-search-nodes</default_value>
       <description/>
     </parameter>
+    <parameter>
+      <name>VECTOR_INDEX</name>
+      <default_value>graph_vector_search_nodes</default_value>
+      <description>The vector index: the actions and Graph vector search 
resolve its name</description>
+    </parameter>
+    <parameter>
+      <name>VECTOR_LABEL</name>
+      <default_value>VectorSearchDoc</default_value>
+      <description>The label of the vector index</description>
+    </parameter>
+    <parameter>
+      <name>VECTOR_PROPERTY</name>
+      <default_value>embedding</default_value>
+      <description>The property of the vector index</description>
+    </parameter>
   </parameters>
   <actions>
     <action>
@@ -61,9 +76,9 @@ limitations under the License.
       <connection>Test</connection>
       <updates>
         <update>
-          <index_name>graph_vector_search_nodes</index_name>
-          <object_name>VectorSearchDoc</object_name>
-          <object_properties>embedding</object_properties>
+          <index_name>${VECTOR_INDEX}</index_name>
+          <object_name>${VECTOR_LABEL}</object_name>
+          <object_properties>${VECTOR_PROPERTY}</object_properties>
           <object_type>NODE</object_type>
           <update_type>DROP</update_type>
           <index_type>VECTOR</index_type>
@@ -110,9 +125,9 @@ limitations under the License.
       <connection>Test</connection>
       <updates>
         <update>
-          <index_name>graph_vector_search_nodes</index_name>
-          <object_name>VectorSearchDoc</object_name>
-          <object_properties>embedding</object_properties>
+          <index_name>${VECTOR_INDEX}</index_name>
+          <object_name>${VECTOR_LABEL}</object_name>
+          <object_properties>${VECTOR_PROPERTY}</object_properties>
           <object_type>NODE</object_type>
           <update_type>CREATE</update_type>
           <index_type>VECTOR</index_type>
@@ -176,9 +191,9 @@ limitations under the License.
       <connection>Test</connection>
       <updates>
         <update>
-          <index_name>graph_vector_search_nodes</index_name>
-          <object_name>VectorSearchDoc</object_name>
-          <object_properties>embedding</object_properties>
+          <index_name>${VECTOR_INDEX}</index_name>
+          <object_name>${VECTOR_LABEL}</object_name>
+          <object_properties>${VECTOR_PROPERTY}</object_properties>
           <object_type>NODE</object_type>
           <update_type>DROP</update_type>
           <index_type>VECTOR</index_type>
@@ -199,9 +214,9 @@ limitations under the License.
       <connection>Test</connection>
       <updates>
         <update>
-          <index_name>graph_vector_search_nodes</index_name>
-          <object_name>VectorSearchDoc</object_name>
-          <object_properties>embedding</object_properties>
+          <index_name>${VECTOR_INDEX}</index_name>
+          <object_name>${VECTOR_LABEL}</object_name>
+          <object_properties>${VECTOR_PROPERTY}</object_properties>
           <object_type>NODE</object_type>
           <update_type>DROP</update_type>
           <index_type>VECTOR</index_type>
diff --git 
a/integration-tests/memgraph/tests/graph-vector-search-nodes/graph-vector-search-nodes-validation.hpl
 
b/integration-tests/memgraph/tests/graph-vector-search-nodes/graph-vector-search-nodes-validation.hpl
index 9b7c53cad9..472bffadf2 100644
--- 
a/integration-tests/memgraph/tests/graph-vector-search-nodes/graph-vector-search-nodes-validation.hpl
+++ 
b/integration-tests/memgraph/tests/graph-vector-search-nodes/graph-vector-search-nodes-validation.hpl
@@ -120,7 +120,7 @@ limitations under the License.
     </partitioning>
     <connection>Test</connection>
     <element_type>NODE</element_type>
-    <index_name>graph_vector_search_nodes</index_name>
+    <index_name>${VECTOR_INDEX}</index_name>
     <label>VectorSearchDoc</label>
     <vector_property>embedding</vector_property>
     <similarity>COSINE</similarity>
diff --git 
a/integration-tests/memgraph/tests/graph-vector-search-nodes/main-graph-vector-search-nodes.hwf
 
b/integration-tests/memgraph/tests/graph-vector-search-nodes/main-graph-vector-search-nodes.hwf
index 3b95ea27e9..2db21b2f30 100644
--- 
a/integration-tests/memgraph/tests/graph-vector-search-nodes/main-graph-vector-search-nodes.hwf
+++ 
b/integration-tests/memgraph/tests/graph-vector-search-nodes/main-graph-vector-search-nodes.hwf
@@ -33,6 +33,21 @@ limitations under the License.
       <default_value>graph-vector-search-nodes</default_value>
       <description/>
     </parameter>
+    <parameter>
+      <name>VECTOR_INDEX</name>
+      <default_value>graph_vector_search_nodes</default_value>
+      <description>The vector index: the actions and Graph vector search 
resolve its name</description>
+    </parameter>
+    <parameter>
+      <name>VECTOR_LABEL</name>
+      <default_value>VectorSearchDoc</default_value>
+      <description>The label of the vector index</description>
+    </parameter>
+    <parameter>
+      <name>VECTOR_PROPERTY</name>
+      <default_value>embedding</default_value>
+      <description>The property of the vector index</description>
+    </parameter>
   </parameters>
   <actions>
     <action>
@@ -61,9 +76,9 @@ limitations under the License.
       <connection>Test</connection>
       <updates>
         <update>
-          <index_name>graph_vector_search_nodes</index_name>
-          <object_name>VectorSearchDoc</object_name>
-          <object_properties>embedding</object_properties>
+          <index_name>${VECTOR_INDEX}</index_name>
+          <object_name>${VECTOR_LABEL}</object_name>
+          <object_properties>${VECTOR_PROPERTY}</object_properties>
           <object_type>NODE</object_type>
           <update_type>DROP</update_type>
           <index_type>VECTOR</index_type>
@@ -110,9 +125,9 @@ limitations under the License.
       <connection>Test</connection>
       <updates>
         <update>
-          <index_name>graph_vector_search_nodes</index_name>
-          <object_name>VectorSearchDoc</object_name>
-          <object_properties>embedding</object_properties>
+          <index_name>${VECTOR_INDEX}</index_name>
+          <object_name>${VECTOR_LABEL}</object_name>
+          <object_properties>${VECTOR_PROPERTY}</object_properties>
           <object_type>NODE</object_type>
           <update_type>CREATE</update_type>
           <index_type>VECTOR</index_type>
@@ -176,9 +191,9 @@ limitations under the License.
       <connection>Test</connection>
       <updates>
         <update>
-          <index_name>graph_vector_search_nodes</index_name>
-          <object_name>VectorSearchDoc</object_name>
-          <object_properties>embedding</object_properties>
+          <index_name>${VECTOR_INDEX}</index_name>
+          <object_name>${VECTOR_LABEL}</object_name>
+          <object_properties>${VECTOR_PROPERTY}</object_properties>
           <object_type>NODE</object_type>
           <update_type>DROP</update_type>
           <index_type>VECTOR</index_type>
@@ -199,9 +214,9 @@ limitations under the License.
       <connection>Test</connection>
       <updates>
         <update>
-          <index_name>graph_vector_search_nodes</index_name>
-          <object_name>VectorSearchDoc</object_name>
-          <object_properties>embedding</object_properties>
+          <index_name>${VECTOR_INDEX}</index_name>
+          <object_name>${VECTOR_LABEL}</object_name>
+          <object_properties>${VECTOR_PROPERTY}</object_properties>
           <object_type>NODE</object_type>
           <update_type>DROP</update_type>
           <index_type>VECTOR</index_type>
diff --git 
a/integration-tests/neo4j/datasets/graph-vector-search-nodes-golden.csv 
b/integration-tests/neo4j/datasets/graph-vector-search-nodes-golden.csv
new file mode 100644
index 0000000000..dafa2fe2e9
--- /dev/null
+++ b/integration-tests/neo4j/datasets/graph-vector-search-nodes-golden.csv
@@ -0,0 +1,6 @@
+query_id,seq,found_id,score
+q1,1,D1,1.00
+q1,2,D2,0.80
+q1,3,D3,0.60
+q2,4,D4,1.00
+q3,5,,
diff --git 
a/integration-tests/neo4j/metadata/dataset/graph-vector-search-nodes-golden.json
 
b/integration-tests/neo4j/metadata/dataset/graph-vector-search-nodes-golden.json
new file mode 100644
index 0000000000..73c04553d1
--- /dev/null
+++ 
b/integration-tests/neo4j/metadata/dataset/graph-vector-search-nodes-golden.json
@@ -0,0 +1,40 @@
+{
+  "base_filename": "graph-vector-search-nodes-golden.csv",
+  "name": "graph-vector-search-nodes-golden",
+  "description": "The rank, id and rounded score of the matches",
+  "dataset_fields": [
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_format": "",
+      "field_name": "query_id"
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 5,
+      "field_precision": -1,
+      "field_format": "",
+      "field_name": "seq"
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_format": "",
+      "field_name": "found_id"
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_format": "",
+      "field_name": "score"
+    }
+  ],
+  "folder_name": ""
+}
diff --git 
a/integration-tests/neo4j/metadata/neo4j-graph-model/VectorSearchDocs.json 
b/integration-tests/neo4j/metadata/neo4j-graph-model/VectorSearchDocs.json
new file mode 100644
index 0000000000..7d546b45d7
--- /dev/null
+++ b/integration-tests/neo4j/metadata/neo4j-graph-model/VectorSearchDocs.json
@@ -0,0 +1,38 @@
+{
+  "relationships": [],
+  "nodes": [
+    {
+      "presentation": {
+        "x": 0,
+        "y": 0
+      },
+      "name": "Doc",
+      "description": "",
+      "properties": [
+        {
+          "indexed": false,
+          "unique": false,
+          "name": "id",
+          "description": "",
+          "type": "String",
+          "mandatory": false,
+          "primary": true
+        },
+        {
+          "indexed": false,
+          "unique": false,
+          "name": "embedding",
+          "description": "",
+          "type": "Vector",
+          "mandatory": false,
+          "primary": false
+        }
+      ],
+      "labels": [
+        "VectorSearchDoc"
+      ]
+    }
+  ],
+  "name": "VectorSearchDocs",
+  "description": "Nodes with an embedding, searched with Graph vector search"
+}
diff --git 
a/integration-tests/neo4j/metadata/unit-test/graph-vector-search-nodes-validation
 UNIT.json 
b/integration-tests/neo4j/metadata/unit-test/graph-vector-search-nodes-validation
 UNIT.json
new file mode 100644
index 0000000000..42ad8b54aa
--- /dev/null
+++ 
b/integration-tests/neo4j/metadata/unit-test/graph-vector-search-nodes-validation
 UNIT.json 
@@ -0,0 +1,40 @@
+{
+  "variableValues": [],
+  "database_replacements": [],
+  "autoOpening": true,
+  "basePath": "",
+  "golden_data_sets": [
+    {
+      "field_mappings": [
+        {
+          "transform_field": "query_id",
+          "data_set_field": "query_id"
+        },
+        {
+          "transform_field": "seq",
+          "data_set_field": "seq"
+        },
+        {
+          "transform_field": "found_id",
+          "data_set_field": "found_id"
+        },
+        {
+          "transform_field": "score",
+          "data_set_field": "score"
+        }
+      ],
+      "field_order": [
+        "seq"
+      ],
+      "transform_name": "Validation",
+      "data_set_name": "graph-vector-search-nodes-golden"
+    }
+  ],
+  "input_data_sets": [],
+  "name": "graph-vector-search-nodes-validation UNIT",
+  "description": "",
+  "trans_test_tweaks": [],
+  "persist_filename": "",
+  "pipeline_filename": 
"./tests/graph-vector-search-nodes/graph-vector-search-nodes-validation.hpl",
+  "test_type": "UNIT_TEST"
+}
diff --git 
a/integration-tests/neo4j/tests/graph-vector-search-nodes/graph-vector-search-nodes-build.hpl
 
b/integration-tests/neo4j/tests/graph-vector-search-nodes/graph-vector-search-nodes-build.hpl
new file mode 100644
index 0000000000..e99df5110b
--- /dev/null
+++ 
b/integration-tests/neo4j/tests/graph-vector-search-nodes/graph-vector-search-nodes-build.hpl
@@ -0,0 +1,146 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+-->
+<pipeline>
+  <info>
+    <name>graph-vector-search-nodes-build</name>
+    <name_sync_with_filename>Y</name_sync_with_filename>
+    <description>Four VectorSearchDoc nodes with an embedding</description>
+    <extended_description/>
+    <pipeline_version/>
+    <pipeline_type>Normal</pipeline_type>
+    <pipeline_status>0</pipeline_status>
+    <parameters>
+    </parameters>
+    <capture_transform_performance>N</capture_transform_performance>
+    
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+    
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+    <created_user>-</created_user>
+    <created_date>2026/10/07 12:00:00.000</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2026/10/07 12:00:00.000</modified_date>
+  </info>
+  <notepads>
+  </notepads>
+  <order>
+    <hop>
+      <from>vectors</from>
+      <to>VectorSearchDocs</to>
+      <enabled>Y</enabled>
+    </hop>
+  </order>
+  <transform>
+    <name>vectors</name>
+    <type>DataGrid</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <fields>
+      <field>
+        <set_empty_string>N</set_empty_string>
+        <length>-1</length>
+        <name>id</name>
+        <precision>-1</precision>
+        <type>String</type>
+      </field>
+      <field>
+        <set_empty_string>N</set_empty_string>
+        <length>-1</length>
+        <name>embedding</name>
+        <precision>-1</precision>
+        <type>Vector</type>
+      </field>
+    </fields>
+    <data>
+      <line>
+        <item>D1</item>
+        <item>[1,0,0]</item>
+      </line>
+      <line>
+        <item>D2</item>
+        <item>[0.8,0.6,0]</item>
+      </line>
+      <line>
+        <item>D3</item>
+        <item>[0.6,0.8,0]</item>
+      </line>
+      <line>
+        <item>D4</item>
+        <item>[0,0,1]</item>
+      </line>
+    </data>
+    <attributes/>
+    <GUI>
+      <xloc>128</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>VectorSearchDocs</name>
+    <type>Neo4jGraphOutput</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <batch_size>100</batch_size>
+    <connection>Test</connection>
+    <create_indexes>N</create_indexes>
+    <mappings>
+      <mapping>
+        <source_field>id</source_field>
+        <target_hint>None</target_hint>
+        <target_name>Doc</target_name>
+        <target_property>id</target_property>
+        <target_type>Node</target_type>
+      </mapping>
+      <mapping>
+        <source_field>embedding</source_field>
+        <target_hint>None</target_hint>
+        <target_name>Doc</target_name>
+        <target_property>embedding</target_property>
+        <target_type>Node</target_type>
+      </mapping>
+    </mappings>
+    <model>VectorSearchDocs</model>
+    <node_mappings/>
+    <out_of_order_allowed>Y</out_of_order_allowed>
+    <relationship_mappings>
+    </relationship_mappings>
+    <return_graph_field>graph</return_graph_field>
+    <returning_graph>N</returning_graph>
+    <validate_against_model>N</validate_against_model>
+    <attributes/>
+    <GUI>
+      <xloc>320</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git 
a/integration-tests/falkordb/tests/graph-vector-search-nodes/graph-vector-search-nodes-validation.hpl
 
b/integration-tests/neo4j/tests/graph-vector-search-nodes/graph-vector-search-nodes-validation.hpl
similarity index 99%
copy from 
integration-tests/falkordb/tests/graph-vector-search-nodes/graph-vector-search-nodes-validation.hpl
copy to 
integration-tests/neo4j/tests/graph-vector-search-nodes/graph-vector-search-nodes-validation.hpl
index 9b7c53cad9..472bffadf2 100644
--- 
a/integration-tests/falkordb/tests/graph-vector-search-nodes/graph-vector-search-nodes-validation.hpl
+++ 
b/integration-tests/neo4j/tests/graph-vector-search-nodes/graph-vector-search-nodes-validation.hpl
@@ -120,7 +120,7 @@ limitations under the License.
     </partitioning>
     <connection>Test</connection>
     <element_type>NODE</element_type>
-    <index_name>graph_vector_search_nodes</index_name>
+    <index_name>${VECTOR_INDEX}</index_name>
     <label>VectorSearchDoc</label>
     <vector_property>embedding</vector_property>
     <similarity>COSINE</similarity>
diff --git 
a/integration-tests/falkordb/tests/graph-vector-search-nodes/main-graph-vector-search-nodes.hwf
 
b/integration-tests/neo4j/tests/graph-vector-search-nodes/main-graph-vector-search-nodes.hwf
similarity index 87%
copy from 
integration-tests/falkordb/tests/graph-vector-search-nodes/main-graph-vector-search-nodes.hwf
copy to 
integration-tests/neo4j/tests/graph-vector-search-nodes/main-graph-vector-search-nodes.hwf
index 3b95ea27e9..2db21b2f30 100644
--- 
a/integration-tests/falkordb/tests/graph-vector-search-nodes/main-graph-vector-search-nodes.hwf
+++ 
b/integration-tests/neo4j/tests/graph-vector-search-nodes/main-graph-vector-search-nodes.hwf
@@ -33,6 +33,21 @@ limitations under the License.
       <default_value>graph-vector-search-nodes</default_value>
       <description/>
     </parameter>
+    <parameter>
+      <name>VECTOR_INDEX</name>
+      <default_value>graph_vector_search_nodes</default_value>
+      <description>The vector index: the actions and Graph vector search 
resolve its name</description>
+    </parameter>
+    <parameter>
+      <name>VECTOR_LABEL</name>
+      <default_value>VectorSearchDoc</default_value>
+      <description>The label of the vector index</description>
+    </parameter>
+    <parameter>
+      <name>VECTOR_PROPERTY</name>
+      <default_value>embedding</default_value>
+      <description>The property of the vector index</description>
+    </parameter>
   </parameters>
   <actions>
     <action>
@@ -61,9 +76,9 @@ limitations under the License.
       <connection>Test</connection>
       <updates>
         <update>
-          <index_name>graph_vector_search_nodes</index_name>
-          <object_name>VectorSearchDoc</object_name>
-          <object_properties>embedding</object_properties>
+          <index_name>${VECTOR_INDEX}</index_name>
+          <object_name>${VECTOR_LABEL}</object_name>
+          <object_properties>${VECTOR_PROPERTY}</object_properties>
           <object_type>NODE</object_type>
           <update_type>DROP</update_type>
           <index_type>VECTOR</index_type>
@@ -110,9 +125,9 @@ limitations under the License.
       <connection>Test</connection>
       <updates>
         <update>
-          <index_name>graph_vector_search_nodes</index_name>
-          <object_name>VectorSearchDoc</object_name>
-          <object_properties>embedding</object_properties>
+          <index_name>${VECTOR_INDEX}</index_name>
+          <object_name>${VECTOR_LABEL}</object_name>
+          <object_properties>${VECTOR_PROPERTY}</object_properties>
           <object_type>NODE</object_type>
           <update_type>CREATE</update_type>
           <index_type>VECTOR</index_type>
@@ -176,9 +191,9 @@ limitations under the License.
       <connection>Test</connection>
       <updates>
         <update>
-          <index_name>graph_vector_search_nodes</index_name>
-          <object_name>VectorSearchDoc</object_name>
-          <object_properties>embedding</object_properties>
+          <index_name>${VECTOR_INDEX}</index_name>
+          <object_name>${VECTOR_LABEL}</object_name>
+          <object_properties>${VECTOR_PROPERTY}</object_properties>
           <object_type>NODE</object_type>
           <update_type>DROP</update_type>
           <index_type>VECTOR</index_type>
@@ -199,9 +214,9 @@ limitations under the License.
       <connection>Test</connection>
       <updates>
         <update>
-          <index_name>graph_vector_search_nodes</index_name>
-          <object_name>VectorSearchDoc</object_name>
-          <object_properties>embedding</object_properties>
+          <index_name>${VECTOR_INDEX}</index_name>
+          <object_name>${VECTOR_LABEL}</object_name>
+          <object_properties>${VECTOR_PROPERTY}</object_properties>
           <object_type>NODE</object_type>
           <update_type>DROP</update_type>
           <index_type>VECTOR</index_type>
diff --git 
a/plugins/tech/age/src/main/java/org/apache/hop/age/AgeGraphConnection.java 
b/plugins/tech/age/src/main/java/org/apache/hop/age/AgeGraphConnection.java
index d5da0cc64f..7dd5bed06e 100644
--- a/plugins/tech/age/src/main/java/org/apache/hop/age/AgeGraphConnection.java
+++ b/plugins/tech/age/src/main/java/org/apache/hop/age/AgeGraphConnection.java
@@ -21,6 +21,7 @@ import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.sql.Statement;
 import java.sql.Types;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
@@ -84,6 +85,23 @@ public class AgeGraphConnection implements IGraphConnection {
     }
   }
 
+  /**
+   * The index statements of {@link AgeGraphDialect} are SQL: they run as they 
are, not in {@code
+   * cypher()}. PostgreSQL runs the statements of one call in one transaction.
+   */
+  @Override
+  public void executeSchemaStatement(String statement, boolean inTransaction) 
throws HopException {
+    if (log != null && log.isDebug()) {
+      log.logDebug("Apache AGE schema statement: " + statement);
+    }
+    try (Statement sqlStatement = connection.createStatement()) {
+      sqlStatement.execute(statement);
+    } catch (SQLException e) {
+      throw new HopException(
+          "Error executing statement on Apache AGE graph " + graphName + ": " 
+ statement, e);
+    }
+  }
+
   @Override
   public IGraphTransaction beginTransaction() throws HopException {
     try {
diff --git 
a/plugins/tech/age/src/main/java/org/apache/hop/age/AgeGraphDialect.java 
b/plugins/tech/age/src/main/java/org/apache/hop/age/AgeGraphDialect.java
index cd84d01157..bcfa426c8d 100644
--- a/plugins/tech/age/src/main/java/org/apache/hop/age/AgeGraphDialect.java
+++ b/plugins/tech/age/src/main/java/org/apache/hop/age/AgeGraphDialect.java
@@ -18,11 +18,16 @@
 package org.apache.hop.age;
 
 import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.graph.GraphIndexDefinition;
 import org.apache.hop.core.graph.IGraphDialect;
 
 /**
  * Apache AGE speaks Cypher, but its indexes and constraints are PostgreSQL 
ones: the Cypher index
- * and constraint statements are not supported. The indexes on node properties 
are created in SQL.
+ * and constraint statements are not supported. The indexes on node properties 
are created in SQL,
+ * run by {@link AgeGraphConnection#executeSchemaStatement}. There are no 
vector indexes, no indexes
+ * on relationship properties and no constraints.
  */
 public class AgeGraphDialect implements IGraphDialect {
 
@@ -45,6 +50,89 @@ public class AgeGraphDialect implements IGraphDialect {
     return getId();
   }
 
+  @Override
+  public boolean isSupportingNodeIndexes() {
+    return true;
+  }
+
+  /**
+   * Create the label if it doesn't exist yet, then the index on its table: 
the index can be created
+   * before the nodes are loaded.
+   */
+  @Override
+  public String getCreateIndexStatement(GraphIndexDefinition index) throws 
HopException {
+    validateIndex(index);
+    if (index.properties().isEmpty()) {
+      throw new HopException("Please specify the properties to index on " + 
index.objectName());
+    }
+    return getCreateLabelStatement(index.objectName())
+        + ";\n"
+        + getCreateNodeIndexStatement(index.name(), index.objectName(), 
index.properties());
+  }
+
+  @Override
+  public String getDropIndexStatement(GraphIndexDefinition index) throws 
HopException {
+    validateIndex(index);
+    return "DROP INDEX IF EXISTS "
+        + quoteIdentifier(graphName)
+        + '.'
+        + quoteIdentifier(index.name());
+  }
+
+  /** Only named indexes on nodes: PostgreSQL needs the name to create the 
index if not exists. */
+  private void validateIndex(GraphIndexDefinition index) throws HopException {
+    if (index.isRelationship()) {
+      throw IGraphDialect.indexesNotSupported(this, index.objectType(), 
index.objectName());
+    }
+    if (StringUtils.isEmpty(index.objectName())) {
+      throw new HopException("Please specify the label of the index " + 
index.name());
+    }
+    if (StringUtils.isEmpty(index.name())) {
+      throw new HopException(
+          "Apache AGE indexes need a name. Label: "
+              + index.objectName()
+              + ", properties: "
+              + String.join(",", index.properties()));
+    }
+  }
+
+  /**
+   * A block creating the node label in the graph unless it exists: AGE 
creates the table of a label
+   * with the first node. Another session can create the same label between 
the check and the
+   * creation: the error is ignored if the label exists when checked again.
+   */
+  String getCreateLabelStatement(String label) {
+    String graphLiteral = quoteLiteral(graphName);
+    String labelLiteral = quoteLiteral(label);
+    String labelExists =
+        "EXISTS (SELECT 1 FROM ag_catalog.ag_label l"
+            + " JOIN ag_catalog.ag_graph g ON g.graphid = l.graph WHERE g.name 
= "
+            + graphLiteral
+            + " AND l.kind = 'v' AND l.name = "
+            + labelLiteral
+            + ")";
+    String body =
+        "BEGIN IF NOT "
+            + labelExists
+            + " THEN BEGIN PERFORM ag_catalog.create_vlabel("
+            + graphLiteral
+            + ", "
+            + labelLiteral
+            + "); EXCEPTION WHEN OTHERS THEN IF NOT "
+            + labelExists
+            + " THEN RAISE; END IF; END; END IF; END";
+    String tag = "$hop$";
+    for (int i = 1; body.contains(tag); i++) {
+      tag = "$hop" + i + "$";
+    }
+    return "DO " + tag + " " + body + " " + tag;
+  }
+
+  /** A PostgreSQL string literal: between single quotes, with the single 
quotes in it doubled. */
+  static String quoteLiteral(String value) {
+    return '\'' + value.replace("'", "''") + '\'';
+  }
+
   /**
    * A PostgreSQL index on the label's table: AGE stores the nodes of each 
label in a table of the
    * graph's schema, their properties in an agtype column. The label needs to 
exist: it does once a
diff --git 
a/plugins/tech/age/src/test/java/org/apache/hop/age/AgeGraphDialectCharacterizationTest.java
 
b/plugins/tech/age/src/test/java/org/apache/hop/age/AgeGraphDialectCharacterizationTest.java
index db47727856..1749fc1abb 100644
--- 
a/plugins/tech/age/src/test/java/org/apache/hop/age/AgeGraphDialectCharacterizationTest.java
+++ 
b/plugins/tech/age/src/test/java/org/apache/hop/age/AgeGraphDialectCharacterizationTest.java
@@ -217,12 +217,19 @@ class AgeGraphDialectCharacterizationTest {
 
   /**
    * Changed on purpose since the dialect SPI: AGE|executionIndex: the SQL 
index statement, as for
-   * an AGE graph database connection before.
+   * an AGE graph database connection before. Since #8836: index.create|A, 
index.drop|A and the
+   * nodeIndexes flag, AGE creates and drops named node indexes.
    */
   static Map<String, String> expected() {
     Map<String, String> e = new LinkedHashMap<>();
-    e.put("AGE|index.create|A", "!ERROR");
-    e.put("AGE|index.drop|A", "!ERROR");
+    e.put(
+        "AGE|index.create|A",
+        "DO $hop$ BEGIN IF NOT EXISTS (SELECT 1 FROM ag_catalog.ag_label l 
JOIN ag_catalog.ag_graph g ON g.graphid = l.graph WHERE g.name = 'hop_graph' 
AND l.kind = 'v' AND l.name = 'Person') THEN BEGIN PERFORM 
ag_catalog.create_vlabel('hop_graph', 'Person');"
+            + " EXCEPTION WHEN OTHERS THEN IF NOT EXISTS (SELECT 1 FROM 
ag_catalog.ag_label l JOIN ag_catalog.ag_graph g ON g.graphid = l.graph WHERE 
g.name = 'hop_graph' AND l.kind = 'v' AND l.name = 'Person') THEN RAISE; END 
IF; END; END IF; END $hop$;\n"
+            + "CREATE INDEX IF NOT EXISTS \"idx\" ON \"hop_graph\".\"Person\" 
("
+            + "ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, 
'\"name\"'::ag_catalog.agtype]), "
+            + "ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, 
'\"age\"'::ag_catalog.agtype]))");
+    e.put("AGE|index.drop|A", "DROP INDEX IF EXISTS \"hop_graph\".\"idx\"");
     e.put("AGE|index.create|B", "!ERROR");
     e.put("AGE|index.drop|B", "!ERROR");
     e.put("AGE|index.create|C", "!ERROR");
@@ -280,7 +287,7 @@ class AgeGraphDialectCharacterizationTest {
     e.put("AGE|vectorValue|$p", "$p");
     e.put("AGE|vectorValue|pr.p", "pr.p");
     e.put("AGE|flag|cypher", "true");
-    e.put("AGE|flag|nodeIndexes", "false");
+    e.put("AGE|flag|nodeIndexes", "true");
     e.put("AGE|flag|relationshipIndexes", "false");
     e.put("AGE|flag|schemaChangesInTransactions", "true");
     e.put("AGE|flag|vectorIndexes", "false");
diff --git a/plugins/tech/age/src/test/java/org/apache/hop/age/AgeIT.java 
b/plugins/tech/age/src/test/java/org/apache/hop/age/AgeIT.java
index 4bfa685088..aeb2652afa 100644
--- a/plugins/tech/age/src/test/java/org/apache/hop/age/AgeIT.java
+++ b/plugins/tech/age/src/test/java/org/apache/hop/age/AgeIT.java
@@ -44,7 +44,9 @@ import java.util.concurrent.TimeUnit;
 import org.apache.hop.core.HopClientEnvironment;
 import org.apache.hop.core.exception.HopException;
 import org.apache.hop.core.graph.GraphIndex;
+import org.apache.hop.core.graph.GraphIndexDefinition;
 import org.apache.hop.core.graph.GraphNodeValue;
+import org.apache.hop.core.graph.GraphObjectType;
 import org.apache.hop.core.graph.GraphPathValue;
 import org.apache.hop.core.graph.GraphRelationshipValue;
 import org.apache.hop.core.graph.GraphSchema;
@@ -226,6 +228,46 @@ class AgeIT {
     }
   }
 
+  /**
+   * The index statements of the dialect run through executeSchemaStatement: 
the label is created
+   * when it doesn't exist yet, creating and dropping twice does nothing the 
second time.
+   */
+  @Test
+  void testCreateAndDropIndexStatements() throws Exception {
+    try (IGraphConnection connection = 
graphDatabase.connect(LogChannel.GENERAL, variables, "it")) {
+      IGraphDialect dialect = connection.getGraphDialect();
+      GraphIndexDefinition index =
+          new GraphIndexDefinition(
+              "it_chunk_id", GraphObjectType.NODE, "ItChunk", List.of("id", 
"the name"));
+      String create = dialect.getCreateIndexStatement(index);
+      connection.executeSchemaStatement(create, true);
+      connection.executeSchemaStatement(create, true);
+      GraphIndex created =
+          connection.getIndexes().stream()
+              .filter(i -> i.name().equals("it_chunk_id"))
+              .findFirst()
+              .orElseThrow();
+      assertEquals(List.of("ItChunk"), created.labelsOrTypes());
+      assertEquals(List.of("id", "the name"), created.properties());
+
+      // The label created with the index takes nodes
+      connection.execute("CREATE (:ItChunk {id: 1})", Map.of());
+      assertEquals(
+          1L,
+          ((Number)
+                  connection
+                      .execute("MATCH (c:ItChunk) RETURN count(c) AS n", 
Map.of())
+                      .get(0)
+                      .get("n"))
+              .longValue());
+
+      String drop = dialect.getDropIndexStatement(index);
+      connection.executeSchemaStatement(drop, true);
+      connection.executeSchemaStatement(drop, true);
+      assertTrue(connection.getIndexes().stream().noneMatch(i -> 
i.name().equals("it_chunk_id")));
+    }
+  }
+
   /** A Vector property arrives as a list of numbers; AGE stores it as an 
agtype array. */
   @Test
   void testVectorWriteAndRead() throws Exception {
diff --git 
a/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/actions/constraint/Neo4jConstraint.java
 
b/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/actions/constraint/Neo4jConstraint.java
index 00ff927a40..fde47a64d6 100644
--- 
a/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/actions/constraint/Neo4jConstraint.java
+++ 
b/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/actions/constraint/Neo4jConstraint.java
@@ -25,6 +25,7 @@ import org.apache.hop.core.exception.HopException;
 import org.apache.hop.core.graph.GraphConstraintDefinition;
 import org.apache.hop.core.graph.GraphObjectType;
 import org.apache.hop.core.graph.IGraphDialect;
+import org.apache.hop.core.variables.IVariables;
 import org.apache.hop.metadata.api.HopMetadataProperty;
 import org.apache.hop.metadata.api.HopMetadataPropertyType;
 import org.apache.hop.neo4j.actions.index.Neo4jIndex;
@@ -142,8 +143,29 @@ public class Neo4jConstraint extends ActionBase implements 
IAction {
         Neo4jIndex.splitProperties(constraintUpdate.getObjectProperties()));
   }
 
+  /**
+   * A copy of the update with the variables resolved in its constraint name, 
object name and
+   * properties.
+   */
+  ConstraintUpdate resolved(ConstraintUpdate constraintUpdate) {
+    return resolved(constraintUpdate, this);
+  }
+
+  /**
+   * A copy of the update with the variables resolved in its constraint name, 
object name and
+   * properties.
+   */
+  public static ConstraintUpdate resolved(ConstraintUpdate constraintUpdate, 
IVariables variables) {
+    ConstraintUpdate copy = new ConstraintUpdate(constraintUpdate);
+    
copy.setConstraintName(variables.resolve(constraintUpdate.getConstraintName()));
+    copy.setObjectName(variables.resolve(constraintUpdate.getObjectName()));
+    
copy.setObjectProperties(variables.resolve(constraintUpdate.getObjectProperties()));
+    return copy;
+  }
+
   private void dropConstraint(final ConstraintUpdate constraintUpdate) throws 
HopException {
-    String cypher = generateDropConstraintCypher(constraintUpdate, 
connection.getDialect());
+    String cypher =
+        generateDropConstraintCypher(resolved(constraintUpdate), 
connection.getDialect(this));
 
     // Run this cypher statement...
     //
@@ -152,7 +174,8 @@ public class Neo4jConstraint extends ActionBase implements 
IAction {
   }
 
   private void createConstraint(ConstraintUpdate constraintUpdate) throws 
HopException {
-    String cypher = generateCreateConstraintCypher(constraintUpdate, 
connection.getDialect());
+    String cypher =
+        generateCreateConstraintCypher(resolved(constraintUpdate), 
connection.getDialect(this));
 
     // Run this cypher statement...
     //
diff --git 
a/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/actions/constraint/Neo4jConstraintDialog.java
 
b/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/actions/constraint/Neo4jConstraintDialog.java
index cdc80222db..d3208cc74e 100644
--- 
a/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/actions/constraint/Neo4jConstraintDialog.java
+++ 
b/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/actions/constraint/Neo4jConstraintDialog.java
@@ -140,6 +140,9 @@ public class Neo4jConstraintDialog extends ActionDialog 
implements IActionDialog
                   PKG, 
"Neo4jConstraintDialog.ConstraintUpdates.Column.ObjectProperties"),
               ColumnInfo.COLUMN_TYPE_TEXT),
         };
+    columns[3].setUsingVariables(true);
+    columns[4].setUsingVariables(true);
+    columns[5].setUsingVariables(true);
 
     // Only offer what the database of the selected connection supports
     //
@@ -231,8 +234,10 @@ public class Neo4jConstraintDialog extends ActionDialog 
implements IActionDialog
         String objectProperties = item.getText(6);
 
         ConstraintUpdate constraintUpdate =
-            new ConstraintUpdate(
-                type, objectType, constraintType, constraintName, objectName, 
objectProperties);
+            Neo4jConstraint.resolved(
+                new ConstraintUpdate(
+                    type, objectType, constraintType, constraintName, 
objectName, objectProperties),
+                variables);
 
         String cypher;
         if (type == UpdateType.CREATE) {
@@ -344,7 +349,7 @@ public class Neo4jConstraintDialog extends ActionDialog 
implements IActionDialog
           NeoConnectionUtils.findGraphConnection(
               getMetadataProvider(), variables.resolve(wConnection.getText()));
       if (connection != null) {
-        return connection.getDialect();
+        return connection.getDialect(variables);
       }
     } catch (Exception e) {
       // Fall back to Neo4j
diff --git 
a/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/actions/index/Neo4jIndex.java
 
b/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/actions/index/Neo4jIndex.java
index f0d1f28009..7d0c77ad91 100644
--- 
a/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/actions/index/Neo4jIndex.java
+++ 
b/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/actions/index/Neo4jIndex.java
@@ -28,6 +28,7 @@ import org.apache.hop.core.graph.GraphIndexDefinition;
 import org.apache.hop.core.graph.GraphObjectType;
 import org.apache.hop.core.graph.GraphVectorIndexDefinition;
 import org.apache.hop.core.graph.IGraphDialect;
+import org.apache.hop.core.variables.IVariables;
 import org.apache.hop.metadata.api.HopMetadataProperty;
 import org.apache.hop.metadata.api.HopMetadataPropertyType;
 import org.apache.hop.neo4j.shared.NamedGraphConnection;
@@ -139,16 +140,30 @@ public class Neo4jIndex extends ActionBase implements 
IAction {
     return dialect.getCreateIndexStatement(toIndexDefinition(indexUpdate));
   }
 
-  /** A copy of the update with the variables in its vector settings resolved. 
*/
-  private IndexUpdate resolved(IndexUpdate indexUpdate) {
+  /**
+   * A copy of the update with the variables resolved in its index name, 
object name, properties and
+   * vector settings.
+   */
+  IndexUpdate resolved(IndexUpdate indexUpdate) {
+    return resolved(indexUpdate, this);
+  }
+
+  /**
+   * A copy of the update with the variables resolved in its index name, 
object name, properties and
+   * vector settings.
+   */
+  public static IndexUpdate resolved(IndexUpdate indexUpdate, IVariables 
variables) {
     IndexUpdate copy = new IndexUpdate(indexUpdate);
-    copy.setVectorDimensions(resolve(indexUpdate.getVectorDimensions()));
-    copy.setVectorCapacity(resolve(indexUpdate.getVectorCapacity()));
+    copy.setIndexName(variables.resolve(indexUpdate.getIndexName()));
+    copy.setObjectName(variables.resolve(indexUpdate.getObjectName()));
+    
copy.setObjectProperties(variables.resolve(indexUpdate.getObjectProperties()));
+    
copy.setVectorDimensions(variables.resolve(indexUpdate.getVectorDimensions()));
+    copy.setVectorCapacity(variables.resolve(indexUpdate.getVectorCapacity()));
     return copy;
   }
 
   private void dropIndex(final IndexUpdate indexUpdate) throws HopException {
-    String cypher = generateDropIndexCypher(resolved(indexUpdate), 
connection.getDialect());
+    String cypher = generateDropIndexCypher(resolved(indexUpdate), 
connection.getDialect(this));
 
     // Run this cypher statement...
     //
@@ -157,7 +172,7 @@ public class Neo4jIndex extends ActionBase implements 
IAction {
   }
 
   private void createIndex(IndexUpdate indexUpdate) throws HopException {
-    String cypher = generateCreateIndexCypher(resolved(indexUpdate), 
connection.getDialect());
+    String cypher = generateCreateIndexCypher(resolved(indexUpdate), 
connection.getDialect(this));
 
     // Run this cypher statement...
     //
diff --git 
a/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/actions/index/Neo4jIndexDialog.java
 
b/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/actions/index/Neo4jIndexDialog.java
index be96aa4833..ddc7e4fc6e 100644
--- 
a/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/actions/index/Neo4jIndexDialog.java
+++ 
b/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/actions/index/Neo4jIndexDialog.java
@@ -146,6 +146,9 @@ public class Neo4jIndexDialog extends ActionDialog 
implements IActionDialog {
         };
     columns[5].setToolTip(
         BaseMessages.getString(PKG, 
"Neo4jIndexDialog.IndexUpdates.Column.IndexType.Tooltip"));
+    columns[2].setUsingVariables(true);
+    columns[3].setUsingVariables(true);
+    columns[4].setUsingVariables(true);
     columns[6].setUsingVariables(true);
     columns[8].setUsingVariables(true);
     columns[8].setToolTip(
@@ -213,10 +216,8 @@ public class Neo4jIndexDialog extends ActionDialog 
implements IActionDialog {
     for (int i = 0; i < items.size(); i++) {
       TableItem item = items.get(i);
       try {
-        IndexUpdate indexUpdate = toIndexUpdate(item);
+        IndexUpdate indexUpdate = Neo4jIndex.resolved(toIndexUpdate(item), 
variables);
         UpdateType type = indexUpdate.getType();
-        
indexUpdate.setVectorDimensions(variables.resolve(indexUpdate.getVectorDimensions()));
-        
indexUpdate.setVectorCapacity(variables.resolve(indexUpdate.getVectorCapacity()));
 
         String cypher;
         if (type == UpdateType.CREATE) {
@@ -341,7 +342,7 @@ public class Neo4jIndexDialog extends ActionDialog 
implements IActionDialog {
           NeoConnectionUtils.findGraphConnection(
               getMetadataProvider(), variables.resolve(wConnection.getText()));
       if (connection != null) {
-        return connection.getDialect();
+        return connection.getDialect(variables);
       }
     } catch (Exception e) {
       // Fall back to Neo4j
diff --git 
a/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/shared/NeoConnectionUtils.java
 
b/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/shared/NeoConnectionUtils.java
index bb916d3288..654a66db4e 100644
--- 
a/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/shared/NeoConnectionUtils.java
+++ 
b/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/shared/NeoConnectionUtils.java
@@ -356,7 +356,7 @@ public class NeoConnectionUtils {
       String cypher,
       String description)
       throws HopException {
-    IGraphDialect dialect = graphConnection.getDialect();
+    IGraphDialect dialect = graphConnection.getDialect(variables);
     // Connecting is outside the try which tolerates existing or missing 
indexes: a connection
     // error always fails, whatever its message says.
     //
@@ -370,16 +370,10 @@ public class NeoConnectionUtils {
     try (connection) {
       log.logDetailed(description + " with cypher: " + cypher);
       try {
-        if (!dialect.isSupportingSchemaChangesInTransactions()
-            || !connection.isSupportingTransactions()) {
-          connection.execute(cypher, Map.of());
-        } else {
-          connection.executeWrite(
-              transaction -> {
-                transaction.execute(cypher, Map.of());
-                return true;
-              });
-        }
+        connection.executeSchemaStatement(
+            cypher,
+            dialect.isSupportingSchemaChangesInTransactions()
+                && connection.isSupportingTransactions());
       } catch (HopException e) {
         if (dialect.isExistingOrMissingIndexError(e)) {
           log.logDetailed(description + ": nothing to do, " + 
Const.getSimpleStackTrace(e));
diff --git 
a/plugins/tech/neo4j/src/test/java/org/apache/hop/neo4j/actions/constraint/Neo4jConstraintVariablesTest.java
 
b/plugins/tech/neo4j/src/test/java/org/apache/hop/neo4j/actions/constraint/Neo4jConstraintVariablesTest.java
new file mode 100644
index 0000000000..a03981135d
--- /dev/null
+++ 
b/plugins/tech/neo4j/src/test/java/org/apache/hop/neo4j/actions/constraint/Neo4jConstraintVariablesTest.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.neo4j.actions.constraint;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.graph.GraphConstraintType;
+import org.apache.hop.neo4j.bolt.Neo4jGraphDialect;
+import org.junit.jupiter.api.Test;
+
+/** The variables in the constraint name, object name and properties are 
resolved. */
+class Neo4jConstraintVariablesTest {
+
+  private static Neo4jConstraint action() {
+    Neo4jConstraint action = new Neo4jConstraint("constraint");
+    action.setVariable("CONSTRAINT", "person_id");
+    action.setVariable("LABEL", "Person");
+    action.setVariable("PROPERTY", "id");
+    return action;
+  }
+
+  private static ConstraintUpdate update(UpdateType type) {
+    return new ConstraintUpdate(
+        type,
+        ObjectType.NODE,
+        GraphConstraintType.UNIQUE,
+        "${CONSTRAINT}",
+        "${LABEL}",
+        "${PROPERTY}");
+  }
+
+  @Test
+  void create() throws HopException {
+    ConstraintUpdate update = update(UpdateType.CREATE);
+    ConstraintUpdate resolved = action().resolved(update);
+    assertEquals(
+        "CREATE CONSTRAINT `person_id` IF NOT EXISTS FOR (n:`Person`) REQUIRE  
n.`id` IS UNIQUE ",
+        Neo4jConstraint.generateCreateConstraintCypher(resolved, 
Neo4jGraphDialect.INSTANCE));
+    // The action itself keeps the variables
+    assertEquals("${CONSTRAINT}", update.getConstraintName());
+    assertEquals(GraphConstraintType.UNIQUE, resolved.getConstraintType());
+  }
+
+  @Test
+  void drop() throws HopException {
+    assertEquals(
+        "DROP CONSTRAINT `person_id` IF EXISTS ",
+        Neo4jConstraint.generateDropConstraintCypher(
+            action().resolved(update(UpdateType.DROP)), 
Neo4jGraphDialect.INSTANCE));
+  }
+}
diff --git 
a/plugins/tech/neo4j/src/test/java/org/apache/hop/neo4j/actions/index/Neo4jIndexVariablesTest.java
 
b/plugins/tech/neo4j/src/test/java/org/apache/hop/neo4j/actions/index/Neo4jIndexVariablesTest.java
new file mode 100644
index 0000000000..ab17ca76c9
--- /dev/null
+++ 
b/plugins/tech/neo4j/src/test/java/org/apache/hop/neo4j/actions/index/Neo4jIndexVariablesTest.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.neo4j.actions.index;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.graph.GraphVectorSimilarity;
+import org.apache.hop.neo4j.bolt.MemgraphGraphDialect;
+import org.apache.hop.neo4j.bolt.Neo4jGraphDialect;
+import org.junit.jupiter.api.Test;
+
+/** The variables in the index name, object name and properties are resolved, 
for every dialect. */
+class Neo4jIndexVariablesTest {
+
+  private static Neo4jIndex action() {
+    Neo4jIndex action = new Neo4jIndex("index");
+    action.setVariable("INDEX", "chunk_index");
+    action.setVariable("LABEL", "Chunk");
+    action.setVariable("PROPERTY", "embedding");
+    action.setVariable("DIMENSIONS", "768");
+    return action;
+  }
+
+  @Test
+  void createRangeIndex() throws HopException {
+    Neo4jIndex action = action();
+    IndexUpdate update =
+        action.resolved(
+            new IndexUpdate(
+                UpdateType.CREATE, ObjectType.NODE, "${INDEX}", "${LABEL}", 
"id, ${PROPERTY}"));
+    assertEquals(
+        "CREATE INDEX `chunk_index` IF NOT EXISTS FOR (n:`Chunk`) ON (n.`id`, 
n.`embedding`)",
+        Neo4jIndex.generateCreateIndexCypher(update, 
Neo4jGraphDialect.INSTANCE));
+  }
+
+  @Test
+  void dropRangeIndex() throws HopException {
+    IndexUpdate update =
+        action()
+            .resolved(
+                new IndexUpdate(UpdateType.DROP, ObjectType.NODE, "${INDEX}", 
"${LABEL}", "id"));
+    assertEquals(
+        "DROP INDEX `chunk_index` IF EXISTS",
+        Neo4jIndex.generateDropIndexCypher(update, 
Neo4jGraphDialect.INSTANCE));
+  }
+
+  @Test
+  void createAndDropVectorIndex() throws HopException {
+    Neo4jIndex action = action();
+    IndexUpdate create = vector(UpdateType.CREATE);
+    IndexUpdate resolved = action.resolved(create);
+
+    assertEquals("chunk_index", resolved.getIndexName());
+    assertEquals("Chunk", resolved.getObjectName());
+    assertEquals("embedding", resolved.getObjectProperties());
+    assertEquals("768", resolved.getVectorDimensions());
+    // The action itself keeps the variables
+    assertEquals("${INDEX}", create.getIndexName());
+
+    String memgraph = Neo4jIndex.generateCreateIndexCypher(resolved, 
MemgraphGraphDialect.INSTANCE);
+    assertEquals(
+        "CREATE VECTOR INDEX `chunk_index` ON :`Chunk`(`embedding`)",
+        memgraph.substring(0, memgraph.indexOf(" WITH CONFIG")));
+    assertEquals(
+        "DROP VECTOR INDEX `chunk_index`",
+        Neo4jIndex.generateDropIndexCypher(
+            action.resolved(vector(UpdateType.DROP)), 
MemgraphGraphDialect.INSTANCE));
+  }
+
+  private static IndexUpdate vector(UpdateType type) {
+    IndexUpdate update =
+        new IndexUpdate(type, ObjectType.NODE, "${INDEX}", "${LABEL}", 
"${PROPERTY}");
+    update.setIndexType(IndexType.VECTOR);
+    update.setVectorDimensions("${DIMENSIONS}");
+    update.setVectorSimilarity(GraphVectorSimilarity.COSINE);
+    return update;
+  }
+}
diff --git 
a/plugins/tech/neo4j/src/test/java/org/apache/hop/neo4j/shared/ThirdPartyGraphDialectTest.java
 
b/plugins/tech/neo4j/src/test/java/org/apache/hop/neo4j/shared/ThirdPartyGraphDialectTest.java
index 8673822662..fbf5da9354 100644
--- 
a/plugins/tech/neo4j/src/test/java/org/apache/hop/neo4j/shared/ThirdPartyGraphDialectTest.java
+++ 
b/plugins/tech/neo4j/src/test/java/org/apache/hop/neo4j/shared/ThirdPartyGraphDialectTest.java
@@ -31,6 +31,7 @@ import org.apache.hop.core.graph.GraphDatabaseMeta;
 import org.apache.hop.core.graph.GraphIndexDefinition;
 import org.apache.hop.core.graph.IGraphDialect;
 import org.apache.hop.core.logging.LogChannel;
+import org.apache.hop.core.variables.IVariables;
 import org.apache.hop.core.variables.Variables;
 import org.apache.hop.metadata.serializer.memory.MemoryMetadataProvider;
 import org.apache.hop.neo4j.actions.index.IndexUpdate;
@@ -106,6 +107,41 @@ class ThirdPartyGraphDialectTest {
     assertEquals("ACME KEY `Person`", database.executed.get(1));
   }
 
+  /**
+   * The actions use the dialect of the connection with its settings resolved: 
the statements of
+   * Apache AGE, for example, name the graph, which can be a variable.
+   */
+  @Test
+  void testActionsUseTheDialectWithResolvedSettings() throws Exception {
+    FakeGraphDatabase database =
+        new FakeGraphDatabase(new AcmeGraphDialect(), null) {
+          @Override
+          public IGraphDialect getGraphDialect(IVariables variables) {
+            String graph = variables.resolve("${ACME_GRAPH}");
+            return new AcmeGraphDialect() {
+              @Override
+              public String getCreateIndexStatement(GraphIndexDefinition 
index) {
+                return "ACME CREATE INDEX ON " + graph + "." + 
quote(index.objectName());
+              }
+            };
+          }
+        };
+    MemoryMetadataProvider metadataProvider = new MemoryMetadataProvider();
+    metadataProvider
+        .getSerializer(GraphDatabaseMeta.class)
+        .save(new GraphDatabaseMeta("acme", database));
+
+    Neo4jIndex action = new Neo4jIndex("index");
+    action.setVariable("ACME_GRAPH", "test_graph");
+    action.setConnectionName("acme");
+    action
+        .getIndexUpdates()
+        .add(new IndexUpdate(UpdateType.CREATE, ObjectType.NODE, "i", 
"Person", "id"));
+    action.setMetadataProvider(metadataProvider);
+    assertTrue(action.execute(new Result(), 0).getResult());
+    assertEquals(List.of("ACME CREATE INDEX ON test_graph.`Person`"), 
database.executed);
+  }
+
   /** The dialect decides which errors mean the index exists already. */
   @Test
   void testThirdPartyExistingIndexIsNotAnError() throws Exception {

Reply via email to