Class: ActiveCypher::Migration
- Inherits:
-
Object
- Object
- ActiveCypher::Migration
- Defined in:
- lib/active_cypher/migration.rb
Overview
Base class for GraphDB migrations. Provides a small DSL for defining index and constraint operations.
Class Attribute Summary collapse
-
.up_block ⇒ Object
readonly
Returns the value of attribute up_block.
Instance Attribute Summary collapse
-
#connection ⇒ Object
readonly
Returns the value of attribute connection.
-
#operations ⇒ Object
readonly
Returns the value of attribute operations.
Class Method Summary collapse
-
.up(&block) ⇒ Object
Define the migration steps.
Instance Method Summary collapse
- #create_fulltext_index(name, label, *props, if_not_exists: true) ⇒ Object
-
#create_fulltext_rel_index(name, rel_type, *props, if_not_exists: true) ⇒ Object
Create a fulltext index on relationships (Neo4j only).
-
#create_node_index(label, *props, unique: false, if_not_exists: true, name: nil, composite: nil) ⇒ Object
Create a node property index.
-
#create_rel_index(rel_type, *props, if_not_exists: true, name: nil, composite: nil) ⇒ Object
Create a relationship property index.
-
#create_text_edge_index(name, rel_type, *props) ⇒ Object
Create a text index on edges (Memgraph 3.6+ only).
- #create_uniqueness_constraint(label, *props, if_not_exists: true, name: nil) ⇒ Object
-
#create_vector_index(name, label, property, dimension:, metric: :cosine, quantization: nil) ⇒ Object
Create a vector index (Memgraph 3.4+, Neo4j 5.0+).
-
#create_vector_rel_index(name, rel_type, property, dimension:, metric: :cosine) ⇒ Object
(also: #create_vector_edge_index)
Create a vector index on relationships (Memgraph 3.4+, Neo4j 2025+).
-
#drop_all_constraints ⇒ Object
Drop all constraints (Memgraph 3.6+ only).
-
#drop_all_indexes ⇒ Object
Drop all indexes (Memgraph 3.6+ only).
- #execute(cypher_string) ⇒ Object
-
#initialize(connection = ActiveCypher::Base.connection) ⇒ Migration
constructor
A new instance of Migration.
-
#run ⇒ Object
Execute the migration.
Constructor Details
#initialize(connection = ActiveCypher::Base.connection) ⇒ Migration
18 19 20 21 |
# File 'lib/active_cypher/migration.rb', line 18 def initialize(connection = ActiveCypher::Base.connection) @connection = connection @operations = [] end |
Class Attribute Details
.up_block ⇒ Object (readonly)
Returns the value of attribute up_block.
8 9 10 |
# File 'lib/active_cypher/migration.rb', line 8 def up_block @up_block end |
Instance Attribute Details
#connection ⇒ Object (readonly)
Returns the value of attribute connection.
16 17 18 |
# File 'lib/active_cypher/migration.rb', line 16 def connection @connection end |
#operations ⇒ Object (readonly)
Returns the value of attribute operations.
16 17 18 |
# File 'lib/active_cypher/migration.rb', line 16 def operations @operations end |
Class Method Details
.up(&block) ⇒ Object
Define the migration steps.
11 12 13 |
# File 'lib/active_cypher/migration.rb', line 11 def up(&block) @up_block = block if block_given? end |
Instance Method Details
#create_fulltext_index(name, label, *props, if_not_exists: true) ⇒ Object
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/active_cypher/migration.rb', line 112 def create_fulltext_index(name, label, *props, if_not_exists: true) cypher = if connection.vendor == :memgraph # Memgraph TEXT INDEX syntax (requires --experimental-enabled='text-search') # Memgraph only supports single property per text index, so create one per prop props.map.with_index do |p, i| index_name = props.size > 1 ? "#{name}_#{p}" : name.to_s "CREATE TEXT INDEX #{index_name} ON :#{label}(#{p})" end else # Neo4j syntax props_clause = props.map { |p| "n.#{p}" }.join(', ') c = +"CREATE FULLTEXT INDEX #{name}" c << ' IF NOT EXISTS' if if_not_exists c << " FOR (n:#{label}) ON EACH [#{props_clause}]" [c] end operations.concat(Array(cypher)) end |
#create_fulltext_rel_index(name, rel_type, *props, if_not_exists: true) ⇒ Object
Create a fulltext index on relationships (Neo4j only).
193 194 195 196 197 198 199 200 201 |
# File 'lib/active_cypher/migration.rb', line 193 def create_fulltext_rel_index(name, rel_type, *props, if_not_exists: true) raise NotImplementedError, 'Fulltext relationship indexes only supported on Neo4j' unless connection.vendor == :neo4j props_clause = props.map { |p| "r.#{p}" }.join(', ') c = +"CREATE FULLTEXT INDEX #{name}" c << ' IF NOT EXISTS' if if_not_exists c << " FOR ()-[r:#{rel_type}]-() ON EACH [#{props_clause}]" operations << c end |
#create_node_index(label, *props, unique: false, if_not_exists: true, name: nil, composite: nil) ⇒ Object
Create a node property index.
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/active_cypher/migration.rb', line 38 def create_node_index(label, *props, unique: false, if_not_exists: true, name: nil, composite: nil) # Default composite to true when multiple properties provided composite = props.size > 1 if composite.nil? cypher = if connection.vendor == :memgraph if composite && props.size > 1 # Memgraph 3.2+ composite index: CREATE INDEX ON :Label(prop1, prop2) props_list = props.join(', ') ["CREATE INDEX ON :#{label}(#{props_list})"] else # Memgraph single property indexes props.map { |p| "CREATE INDEX ON :#{label}(#{p})" } end else # Neo4j syntax props_clause = props.map { |p| "n.#{p}" }.join(', ') c = +'CREATE ' c << 'UNIQUE ' if unique c << 'INDEX' c << " #{name}" if name c << ' IF NOT EXISTS' if if_not_exists c << " FOR (n:#{label}) ON (#{props_clause})" [c] end operations.concat(Array(cypher)) end |
#create_rel_index(rel_type, *props, if_not_exists: true, name: nil, composite: nil) ⇒ Object
Create a relationship property index.
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/active_cypher/migration.rb', line 71 def create_rel_index(rel_type, *props, if_not_exists: true, name: nil, composite: nil) composite = props.size > 1 if composite.nil? cypher = if connection.vendor == :memgraph if composite && props.size > 1 # Memgraph 3.2+ composite edge index props_list = props.join(', ') ["CREATE EDGE INDEX ON :#{rel_type}(#{props_list})"] else props.map { |p| "CREATE EDGE INDEX ON :#{rel_type}(#{p})" } end else # Neo4j syntax props_clause = props.map { |p| "r.#{p}" }.join(', ') c = +'CREATE INDEX' c << " #{name}" if name c << ' IF NOT EXISTS' if if_not_exists c << " FOR ()-[r:#{rel_type}]-() ON (#{props_clause})" [c] end operations.concat(Array(cypher)) end |
#create_text_edge_index(name, rel_type, *props) ⇒ Object
Create a text index on edges (Memgraph 3.6+ only). Neo4j fulltext indexes on relationships use different syntax via create_fulltext_rel_index.
179 180 181 182 183 184 185 186 |
# File 'lib/active_cypher/migration.rb', line 179 def create_text_edge_index(name, rel_type, *props) raise NotImplementedError, 'Text edge indexes only supported on Memgraph 3.6+' unless connection.vendor == :memgraph props.each do |p| index_name = props.size > 1 ? "#{name}_#{p}" : name.to_s operations << "CREATE TEXT EDGE INDEX #{index_name} ON :#{rel_type}(#{p})" end end |
#create_uniqueness_constraint(label, *props, if_not_exists: true, name: nil) ⇒ Object
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/active_cypher/migration.rb', line 94 def create_uniqueness_constraint(label, *props, if_not_exists: true, name: nil) cypher = if connection.vendor == :memgraph # Memgraph syntax: CREATE CONSTRAINT ON (n:Label) ASSERT n.prop IS UNIQUE # Note: Memgraph doesn't support IF NOT EXISTS or named constraints props_clause = props.map { |p| "n.#{p}" }.join(', ') "CREATE CONSTRAINT ON (n:#{label}) ASSERT #{props_clause} IS UNIQUE" else # Neo4j syntax props_clause = props.map { |p| "n.#{p}" }.join(', ') c = +'CREATE CONSTRAINT' c << " #{name}" if name c << ' IF NOT EXISTS' if if_not_exists c << " FOR (n:#{label}) REQUIRE (#{props_clause}) IS UNIQUE" c end operations << cypher end |
#create_vector_index(name, label, property, dimension:, metric: :cosine, quantization: nil) ⇒ Object
Create a vector index (Memgraph 3.4+, Neo4j 5.0+).
138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/active_cypher/migration.rb', line 138 def create_vector_index(name, label, property, dimension:, metric: :cosine, quantization: nil) cypher = if connection.vendor == :memgraph config = { dimension: dimension, metric: metric.to_s } config[:scalar_kind] = 'f32' if quantization == :scalar config_str = config.map { |k, v| "#{k}: #{v.is_a?(String) ? "'#{v}'" : v}" }.join(', ') "CREATE VECTOR INDEX #{name} ON :#{label}(#{property}) WITH CONFIG { #{config_str} }" else # Neo4j syntax = { indexConfig: { 'vector.dimensions' => dimension, 'vector.similarity_function' => metric.to_s.upcase } } opts_str = .to_json.gsub('"', "'") "CREATE VECTOR INDEX #{name} IF NOT EXISTS FOR (n:#{label}) ON (n.#{property}) OPTIONS #{opts_str}" end operations << cypher end |
#create_vector_rel_index(name, rel_type, property, dimension:, metric: :cosine) ⇒ Object Also known as: create_vector_edge_index
Create a vector index on relationships (Memgraph 3.4+, Neo4j 2025+).
159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/active_cypher/migration.rb', line 159 def create_vector_rel_index(name, rel_type, property, dimension:, metric: :cosine) cypher = if connection.vendor == :memgraph config_str = "dimension: #{dimension}, metric: '#{metric}'" "CREATE VECTOR EDGE INDEX #{name} ON :#{rel_type}(#{property}) WITH CONFIG { #{config_str} }" else # Neo4j 2025+ syntax "CREATE VECTOR INDEX #{name} IF NOT EXISTS FOR ()-[r:#{rel_type}]-() ON (r.#{property}) " \ "OPTIONS { indexConfig: { `vector.dimensions`: #{dimension}, `vector.similarity_function`: '#{metric}' } }" end operations << cypher end |
#drop_all_constraints ⇒ Object
Drop all constraints (Memgraph 3.6+ only). Neo4j requires dropping constraints individually.
213 214 215 216 217 |
# File 'lib/active_cypher/migration.rb', line 213 def drop_all_constraints raise NotImplementedError, 'drop_all_constraints only supported on Memgraph 3.6+' unless connection.vendor == :memgraph operations << 'DROP ALL CONSTRAINTS' end |
#drop_all_indexes ⇒ Object
Drop all indexes (Memgraph 3.6+ only). Neo4j requires dropping indexes individually.
205 206 207 208 209 |
# File 'lib/active_cypher/migration.rb', line 205 def drop_all_indexes raise NotImplementedError, 'drop_all_indexes only supported on Memgraph 3.6+' unless connection.vendor == :memgraph operations << 'DROP ALL INDEXES' end |
#execute(cypher_string) ⇒ Object
219 220 221 |
# File 'lib/active_cypher/migration.rb', line 219 def execute(cypher_string) operations << cypher_string.strip end |
#run ⇒ Object
Execute the migration.
24 25 26 27 |
# File 'lib/active_cypher/migration.rb', line 24 def run instance_eval(&self.class.up_block) if self.class.up_block execute_operations end |