activegraph 11.1.0.beta.1 → 11.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4aade7db7557b1222b78e048550606354b0707a60c9375359a4f945b702763c2
4
- data.tar.gz: a22b396cdc491118ecbaf947ac9e00d9ea088fe0df5afa51a3a38b7f085db9ff
3
+ metadata.gz: 4b0ec846225c753d7a2dc2bc3923c95f52dec582324559df182f23df633f564c
4
+ data.tar.gz: 1205c8eaf374112629a173f55e2c9a5fae42324073b2f540815eb984cb87e324
5
5
  SHA512:
6
- metadata.gz: 663bc1323338cc7261cb2281b5ad6a86d794baed1fd2dc82a08f8d1b5eae593a151927529330425d8ffdc71adb17e8a7c70aa3872c63641cd2d77dcfe46016cc
7
- data.tar.gz: c894eff95679b5982e418dccff5deb160ff41a46e661bee2f8278b230ab59fe20befc8aed83b8144dd112485df98d61c6fa1f60d226f63a1df3db6cd34b4a9a4
6
+ metadata.gz: '07091d4bf26b0b2bfb4d6313668e358eb78f8eb6afb7244063a2293a3596963687debf7fe6e72177ba388ac008a9452a8bd580fb48064575354e0e208765f475'
7
+ data.tar.gz: '09dc2f6eaf0126d26b76aba057b9b7e77e0b633f0b13ab99964ed64fd64e7421f22eaa470d9abd0fca4bd896e122ca938814683bc0c618e7d37d2ded400c00de'
data/CHANGELOG.md CHANGED
@@ -3,6 +3,25 @@ All notable changes to this project will be documented in this file.
3
3
  This file should follow the standards specified on [http://keepachangelog.com/]
4
4
  This project adheres to [Semantic Versioning](http://semver.org/).
5
5
 
6
+ ## [11.2.0] 2023-02-06
7
+
8
+ ## Added
9
+
10
+ - support for neo4j:schema:dump and :load for neo4j 4 and 5. No automatic migration of schema on major neo4j upgrades. Schema must be regenerated with neo4j:schema:dump or manually adjusted to new syntax on those upgrades.
11
+
12
+ ## [11.1.0] 2023-01-10
13
+
14
+ ## Added
15
+
16
+ - support for pure ruby driver, neo4j-ruby-driver 4.4
17
+ - support for neo4j 5
18
+
19
+ ## Fixed
20
+
21
+ - moved migrations into their own transactions
22
+ - Uniqueness within a scope defined by a proc
23
+ - Fixed lost @rel_var when branching QueryProxy
24
+
6
25
  ## [11.0.2] 2021-11-05
7
26
 
8
27
  ## Fixed
data/README.md CHANGED
@@ -45,21 +45,22 @@ Neo4j.rb v4.1.0 was released in January of 2015. Its changes are outlined [here]
45
45
 
46
46
  ## Neo4j version support
47
47
 
48
- | **Neo4j Version** | v2.x | v3.x | >= v4.x | >= 7.0.3 | activegraph 10 | activegraph 11 (jRuby only) |
49
- |-------------------|------|-------|---------|----------|------------------|-----------------------------|
50
- | 1.9.x | Yes | No | No | No | No | No
51
- | 2.0.x | No | Yes | No | No | No | No
52
- | 2.1.x | No | Yes | Yes * | Yes | No | No
53
- | 2.2.x | No | No | Yes | Yes | No | No
54
- | 2.3.x | No | No | Yes | Yes | No | No
55
- | 3.0, 3.1, 3.3 | No | No | No | Yes | No | No
56
- | 3.4 | No | No | No | Yes | Yes | No
57
- | 3.5 | No | No | No | Yes | Yes | Yes
58
- | 4.0 | No | No | No | No | Yes | Yes
59
- | 4.1 | No | No | No | No | No | Yes
60
- | 4.2 | No | No | No | No | No | Yes
61
- | 4.3 | No | No | No | No | No | Yes
62
- | 4.4 | No | No | No | No | No | Yes
48
+ | **Neo4j Version** | v2.x | v3.x | >= v4.x | >= 7.0.3 | activegraph 10 | activegraph 11.1 |
49
+ |-------------------|------|-------|---------|----------|------------------|------------------|
50
+ | 1.9.x | Yes | No | No | No | No | No
51
+ | 2.0.x | No | Yes | No | No | No | No
52
+ | 2.1.x | No | Yes | Yes * | Yes | No | No
53
+ | 2.2.x | No | No | Yes | Yes | No | No
54
+ | 2.3.x | No | No | Yes | Yes | No | No
55
+ | 3.0, 3.1, 3.3 | No | No | No | Yes | No | No
56
+ | 3.4 | No | No | No | Yes | Yes | No
57
+ | 3.5 | No | No | No | Yes | Yes | Yes
58
+ | 4.0 | No | No | No | No | Yes | Yes
59
+ | 4.1 | No | No | No | No | No | Yes
60
+ | 4.2 | No | No | No | No | No | Yes
61
+ | 4.3 | No | No | No | No | No | Yes
62
+ | 4.4 | No | No | No | No | No | Yes
63
+ | 5.x | No | No | No | No | No | Yes
63
64
 
64
65
  `*` Neo4j.rb >= 4.x doesn't support Neo4j versions before 2.1.5. To use 2.1.x you should upgrade to a version >= 2.1.5
65
66
 
data/activegraph.gemspec CHANGED
@@ -33,7 +33,7 @@ DESCRIPTION
33
33
  s.add_dependency('activemodel', '>= 4.0')
34
34
  s.add_dependency('activesupport', '>= 4.0')
35
35
  s.add_dependency('i18n', '!= 1.8.8') # https://github.com/jruby/jruby/issues/6547
36
- s.add_dependency('neo4j-ruby-driver', '>= 4.4.0.alpha.7')
36
+ s.add_dependency('neo4j-ruby-driver', '>= 4.4.1')
37
37
  s.add_dependency('orm_adapter', '~> 0.5.0')
38
38
  s.add_dependency('sorted_set')
39
39
  s.add_development_dependency('guard')
@@ -2,20 +2,33 @@ module ActiveGraph
2
2
  module Core
3
3
  class Label
4
4
  attr_reader :name
5
+ delegate :version?, to: ActiveGraph::Base
5
6
 
6
7
  def initialize(name)
7
8
  @name = name
8
9
  end
9
10
 
10
- def create_index(property, options = {})
11
+ def create_index(*properties, **options)
11
12
  validate_index_options!(options)
12
- properties = property.is_a?(Array) ? property.join(',') : property
13
- schema_query("CREATE INDEX ON :`#{@name}`(#{properties})")
13
+ if version?('>=4.4')
14
+ properties = properties.map { |p| "l.#{p}" }
15
+ fragment = "FOR (l:`#{@name}`) ON"
16
+ else
17
+ fragment = "ON :`#{@name}`"
18
+ end
19
+ schema_query("CREATE INDEX #{fragment} (#{properties.join('.')})")
14
20
  end
15
21
 
16
22
  def drop_index(property, options = {})
17
23
  validate_index_options!(options)
18
- schema_query("DROP INDEX ON :`#{@name}`(#{property})")
24
+ if version?('<4.3')
25
+ schema_query("DROP INDEX ON :`#{@name}`(#{property})")
26
+ else
27
+ schema_query("SHOW INDEXES YIELD * WHERE labelsOrTypes = $labels AND properties = $properties",
28
+ labels: [@name], properties: [property]).each do |record|
29
+ schema_query("DROP INDEX #{record[:name]}")
30
+ end
31
+ end
19
32
  end
20
33
 
21
34
  # Creates a neo4j constraint on a property
@@ -27,7 +40,8 @@ module ActiveGraph
27
40
  def create_constraint(property, constraints)
28
41
  cypher = case constraints[:type]
29
42
  when :unique, :uniqueness
30
- "CREATE CONSTRAINT ON (n:`#{name}`) ASSERT n.`#{property}` IS UNIQUE"
43
+ _for, _require = version?('>=4.4') ? %w[FOR REQUIRE] : %w[ON ASSERT]
44
+ "CREATE CONSTRAINT #{_for} (n:`#{name}`) #{_require} n.`#{property}` IS UNIQUE"
31
45
  else
32
46
  fail "Not supported constraint #{constraints.inspect} for property #{property} (expected :type => :unique)"
33
47
  end
@@ -46,15 +60,20 @@ module ActiveGraph
46
60
  # label.drop_constraint(:name, {type: :unique})
47
61
  #
48
62
  def drop_constraint(property, constraint)
49
- cypher = case constraint[:type]
50
- when :unique, :uniqueness
51
- "n.`#{property}` IS UNIQUE"
52
- when :exists
53
- "exists(n.`#{property}`)"
54
- else
55
- fail "Not supported constraint #{constraint.inspect}"
56
- end
57
- schema_query("DROP CONSTRAINT ON (n:`#{name}`) ASSERT #{cypher}")
63
+ return drop_constraint42(property, constraint) if version?('<4.3')
64
+ type = case constraint[:type]
65
+ when :unique, :uniqueness
66
+ 'UNIQUENESS'
67
+ when :exists
68
+ 'NODE_PROPERTY_EXISTENCE'
69
+ else
70
+ fail "Not supported constraint #{constraint.inspect}"
71
+ end
72
+ schema_query(
73
+ 'SHOW CONSTRAINTS YIELD * WHERE type = $type AND labelsOrTypes = $labels AND properties = $properties',
74
+ type: type, labels: [name], properties: [property]).first[:name].tap do |constraint_name|
75
+ schema_query("DROP CONSTRAINT #{constraint_name}")
76
+ end
58
77
  end
59
78
 
60
79
  def drop_uniqueness_constraint(property, options = {})
@@ -105,7 +124,12 @@ module ActiveGraph
105
124
  def drop_indexes
106
125
  indexes.each do |definition|
107
126
  begin
108
- ActiveGraph::Base.query("DROP INDEX ON :`#{definition[:label]}`(#{definition[:properties][0]})")
127
+ ActiveGraph::Base.query(
128
+ if definition[:name]
129
+ "DROP INDEX #{definition[:name]}"
130
+ else
131
+ "DROP INDEX ON :`#{definition[:label]}`(#{definition[:properties][0]})"
132
+ end)
109
133
  rescue Neo4j::Driver::Exceptions::DatabaseException
110
134
  # This will error on each constraint. Ignore and continue.
111
135
  next
@@ -114,7 +138,9 @@ module ActiveGraph
114
138
  end
115
139
 
116
140
  def drop_constraints
117
- result = ActiveGraph::Base.read_transaction { |tx| tx.run('CALL db.constraints').to_a }
141
+ result = ActiveGraph::Base.read_transaction do |tx|
142
+ tx.run(ActiveGraph::Base.version?('<4.3') ? 'CALL db.constraints' : 'SHOW CONSTRAINTS YIELD *').to_a
143
+ end
118
144
  ActiveGraph::Base.write_transaction do |tx|
119
145
  result.each do |record|
120
146
  tx.run("DROP #{record.keys.include?(:name) ? "CONSTRAINT #{record[:name]}" : record[:description]}")
@@ -123,14 +149,26 @@ module ActiveGraph
123
149
  end
124
150
  end
125
151
 
126
- def schema_query(cypher)
127
- ActiveGraph::Base.query(cypher, {})
152
+ def schema_query(cypher, **params)
153
+ ActiveGraph::Base.query(cypher, params)
128
154
  end
129
155
 
130
156
  def validate_index_options!(options)
131
157
  return unless options[:type] && options[:type] != :exact
132
158
  fail "Type #{options[:type]} is not supported"
133
159
  end
160
+
161
+ def drop_constraint42(property, constraint)
162
+ cypher = case constraint[:type]
163
+ when :unique, :uniqueness
164
+ "n.`#{property}` IS UNIQUE"
165
+ when :exists
166
+ "exists(n.`#{property}`)"
167
+ else
168
+ fail "Not supported constraint #{constraint.inspect}"
169
+ end
170
+ schema_query("DROP CONSTRAINT ON (n:`#{name}`) ASSERT #{cypher}")
171
+ end
134
172
  end
135
173
  end
136
174
  end
@@ -1,63 +1,92 @@
1
1
  module ActiveGraph
2
2
  module Core
3
3
  module Schema
4
+ FILTER = {
5
+ 3 => [:type, 'node_unique_property'],
6
+ 4 => [:uniqueness, 'UNIQUE'],
7
+ }
8
+
4
9
  def version
5
- read_transaction do
10
+ @version ||= read_transaction do
6
11
  # BTW: community / enterprise could be retrieved via `result.first.edition`
7
12
  query('CALL dbms.components()', {}, skip_instrumentation: true).first[:versions][0]
13
+ .then(&Gem::Version.method(:new))
8
14
  end
9
15
  end
10
16
 
17
+ def version?(requirement)
18
+ Gem::Requirement.create(requirement).satisfied_by?(Gem::Version.new(version))
19
+ end
20
+
11
21
  def indexes
12
- raw_indexes do |keys, result|
13
- result.map do |row|
14
- { type: row[:type].to_sym, label: label(keys, row), properties: properties(row),
15
- state: row[:state].to_sym }
16
- end
22
+ normalize(raw_indexes, *%i[type state])
23
+ end
24
+
25
+ def normalize(result, *extra)
26
+ result.map do |row|
27
+ definition(row, version?('<4') ? :index_cypher_v3 : :index_cypher)
28
+ .merge(extra.to_h { |key| [key, row[key].to_sym] })
17
29
  end
18
30
  end
19
31
 
20
32
  def constraints
21
- raw_indexes do |keys, result|
22
- result.select(&method(v4?(keys) ? :v4_filter : :v3_filter)).map do |row|
23
- { type: :uniqueness, label: label(keys, row), properties: properties(row) }
24
- end
33
+ if version?('<4.3')
34
+ raw_indexes.select(&method(:constraint_owned?))
35
+ else
36
+ raw_constraints.select(&method(:constraint_filter))
37
+ end.map { |row| definition(row, :constraint_cypher).merge(type: :uniqueness) }
38
+ end
39
+
40
+ private def raw_constraints
41
+ read_transaction do
42
+ query('SHOW CONSTRAINTS YIELD *', {}, skip_instrumentation: true).to_a
25
43
  end
26
44
  end
27
45
 
28
46
  def raw_indexes
29
47
  read_transaction do
30
- result = query('CALL db.indexes()', {}, skip_instrumentation: true)
31
- yield result.keys, result.reject { |row| row[:type] == 'LOOKUP' }
48
+ query(version?('<4.3') ? 'CALL db.indexes()' : 'SHOW INDEXES YIELD *', {}, skip_instrumentation: true)
49
+ .reject { |row| row[:type] == 'LOOKUP' }
32
50
  end
33
51
  end
34
52
 
53
+ def constraint_owned?(record)
54
+ FILTER[major]&.then { |(key, value)| record[key] == value } || record[:owningConstraint]
55
+ end
56
+
35
57
  private
36
58
 
37
- def v4_filter(row)
38
- row[:uniqueness] == 'UNIQUE'
59
+ def major
60
+ @major ||= version.segments.first
39
61
  end
40
62
 
41
- def v3_filter(row)
42
- row[:type] == 'node_unique_property'
63
+ def constraint_filter(record)
64
+ %w[UNIQUENESS RELATIONSHIP_PROPERTY_EXISTENCE NODE_PROPERTY_EXISTENCE NODE_KEY].include?(record[:type])
43
65
  end
44
66
 
45
- def label(keys, row)
46
- if v34?(keys)
47
- row[:label]
48
- else
49
- (v4?(keys) ? row[:labelsOrTypes] : row[:tokenNames]).first
50
- end.to_sym
67
+ def index_cypher_v3(label, properties)
68
+ "INDEX ON :#{label}#{com_sep(properties, nil)}"
69
+ end
70
+
71
+ def index_cypher(label, properties)
72
+ "INDEX FOR (n:#{label}) ON #{com_sep(properties)}"
73
+ end
74
+
75
+ def constraint_cypher(label, properties)
76
+ "CONSTRAINT ON (n:#{label}) ASSERT #{com_sep(properties)} IS UNIQUE"
77
+ end
78
+
79
+ def com_sep(properties, prefix = 'n.')
80
+ "(#{properties.map { |prop| "#{prefix}#{prop}" }.join(', ')})"
51
81
  end
52
82
 
53
- def v4?(keys)
54
- return @v4 unless @v4.nil?
55
- @v4 = keys.include?(:labelsOrTypes)
83
+ def definition(row, template)
84
+ { label: label(row), properties: properties(row), name: row[:name],
85
+ create_statement: row[:createStatement] || send(template,label(row), row[:properties]) }
56
86
  end
57
87
 
58
- def v34?(keys)
59
- return @v34 unless @v34.nil?
60
- @v34 = keys.include?(:label)
88
+ def label(row)
89
+ row[version?('>=4') ? :labelsOrTypes : :tokenNames].first.to_sym
61
90
  end
62
91
 
63
92
  def properties(row)
@@ -5,7 +5,7 @@ require 'active_graph/core/query'
5
5
  require 'active_graph/core/record'
6
6
  require 'active_graph/core/wrappable'
7
7
  require 'active_graph/transaction'
8
- require 'neo4j_ruby_driver'
8
+ require 'neo4j/driver'
9
9
 
10
10
  Neo4j::Driver::Types::Entity.include ActiveGraph::Core::Wrappable
11
11
  Neo4j::Driver::Types::Entity.prepend ActiveGraph::Core::Entity
@@ -23,12 +23,12 @@ module ActiveGraph
23
23
  protected
24
24
 
25
25
  def idless_count(label, id_property)
26
- query.match(n: label).where("NOT EXISTS(n.#{id_property})").pluck('COUNT(n) AS ids').first
26
+ query.match(n: label).where("n.#{id_property} IS NULL").pluck('COUNT(n) AS ids').first
27
27
  end
28
28
 
29
29
  def id_batch_set(label, id_property, new_ids, count)
30
30
  ActiveGraph::Base.transaction do
31
- execute("MATCH (n:`#{label}`) WHERE NOT EXISTS(n.#{id_property})
31
+ execute("MATCH (n:`#{label}`) WHERE n.#{id_property} IS NULL
32
32
  with COLLECT(n) as nodes, #{new_ids} as ids
33
33
  FOREACH(i in range(0,#{count - 1})|
34
34
  FOREACH(node in [nodes[i]]|
@@ -83,7 +83,7 @@ module ActiveGraph
83
83
  private
84
84
 
85
85
  def property_exists?(label, property)
86
- by_label(label).where("EXISTS(n.#{property})").return(:n).any?
86
+ by_label(label).where("n.#{property} IS NOT NULL").return(:n).any?
87
87
  end
88
88
 
89
89
  def by_label(label, options = {})
@@ -3,15 +3,15 @@ module ActiveGraph
3
3
  module Schema
4
4
  class << self
5
5
  def fetch_schema_data
6
- { constraints: fetch_constraint_descriptions.sort, indexes: fetch_index_descriptions.sort }
6
+ %i[constraints indexes].to_h { |schema_elem| [schema_elem, send("fetch_#{schema_elem}_descriptions").keys] }
7
7
  end
8
8
 
9
9
  def synchronize_schema_data(schema_data, remove_missing)
10
- queries = []
11
- ActiveGraph::Base.read_transaction do
12
- queries += drop_and_create_queries(fetch_constraint_descriptions, schema_data[:constraints], remove_missing)
13
- queries += drop_and_create_queries(fetch_index_descriptions, schema_data[:indexes], remove_missing)
14
- end
10
+ queries =
11
+ ActiveGraph::Base.read_transaction do
12
+ drop_and_create_queries(fetch_constraints_descriptions, schema_data[:constraints], 'CONSTRAINT', remove_missing) +
13
+ drop_and_create_queries(fetch_indexes_descriptions, schema_data[:indexes], 'INDEX', remove_missing)
14
+ end
15
15
  ActiveGraph::Base.write_transaction do
16
16
  queries.each(&ActiveGraph::Base.method(:query))
17
17
  end
@@ -19,46 +19,30 @@ module ActiveGraph
19
19
 
20
20
  private
21
21
 
22
- def fetch_constraint_descriptions
23
- ActiveGraph::Base.query('CALL db.constraints() YIELD description').map(&:first)
22
+ def fetch_indexes_descriptions
23
+ ActiveGraph::Base.raw_indexes.reject(&ActiveGraph::Base.method(:constraint_owned?))
24
+ .then(&ActiveGraph::Base.method(:normalize)).then(&method(:fetch_descriptions))
24
25
  end
25
26
 
26
- def fetch_index_descriptions
27
- ActiveGraph::Base.raw_indexes do |keys, result|
28
- if keys.include?(:description)
29
- v3_indexes(result)
30
- else
31
- v4_indexes(result)
32
- end
33
- end
27
+ def fetch_constraints_descriptions
28
+ fetch_descriptions(ActiveGraph::Base.constraints)
34
29
  end
35
30
 
36
- def v3_indexes(result)
37
- result.reject do |row|
38
- # These indexes are created automagically when the corresponding constraints are created
39
- row[:type] == 'node_unique_property'
40
- end.map { |row| row[:description] }
31
+ def fetch_descriptions(results)
32
+ results.map { |definition| definition.values_at(:create_statement, :name) }.sort.to_h
41
33
  end
42
34
 
43
- def v4_indexes(result)
44
- result.reject do |row|
45
- # These indexes are created automagically when the corresponding constraints are created
46
- row[:uniqueness] == 'UNIQUE'
47
- end.map(&method(:description))
35
+ def drop_and_create_queries(existing, specified, schema_elem, remove_missing)
36
+ (remove_missing ? existing.except(*specified).map { |stmt, name| drop_statement(schema_elem, stmt, name) } : []) +
37
+ (specified - existing.keys).map(&method(:create_statement))
48
38
  end
49
39
 
50
- def description(row)
51
- "INDEX FOR (n:#{row[:labelsOrTypes].first}) ON (#{row[:properties].map { |prop| "n.#{prop}" }.join(', ')})"
40
+ def drop_statement(schema_elem, create_statement, name)
41
+ "DROP #{name&.then { |name| "#{schema_elem} #{name}" } || create_statement}"
52
42
  end
53
43
 
54
- def drop_and_create_queries(existing, specified, remove_missing)
55
- [].tap do |queries|
56
- if remove_missing
57
- (existing - specified).each { |description| queries << "DROP #{description}" }
58
- end
59
-
60
- (specified - existing).each { |description| queries << "CREATE #{description}" }
61
- end
44
+ def create_statement(stmt)
45
+ stmt.start_with?('CREATE ') ? stmt : "CREATE #{stmt}"
62
46
  end
63
47
  end
64
48
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveGraph
2
- VERSION = '11.1.0.beta.1'
2
+ VERSION = '11.2.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activegraph
3
3
  version: !ruby/object:Gem::Version
4
- version: 11.1.0.beta.1
4
+ version: 11.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andreas Ronge, Brian Underwood, Chris Grigg, Heinrich Klobuczek
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-23 00:00:00.000000000 Z
11
+ date: 2023-02-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: 4.4.0.alpha.7
61
+ version: 4.4.1
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: 4.4.0.alpha.7
68
+ version: 4.4.1
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: orm_adapter
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -440,11 +440,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
440
440
  version: '2.6'
441
441
  required_rubygems_version: !ruby/object:Gem::Requirement
442
442
  requirements:
443
- - - ">"
443
+ - - ">="
444
444
  - !ruby/object:Gem::Version
445
- version: 1.3.1
445
+ version: '0'
446
446
  requirements: []
447
- rubygems_version: 3.3.3
447
+ rubygems_version: 3.4.1
448
448
  signing_key:
449
449
  specification_version: 4
450
450
  summary: A graph database for Ruby