neo4j 9.6.2 → 10.0.0.pre.alpha.1

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +0 -13
  3. data/CONTRIBUTORS +4 -0
  4. data/Gemfile +2 -33
  5. data/lib/neo4j.rb +6 -2
  6. data/lib/neo4j/active_base.rb +19 -22
  7. data/lib/neo4j/active_node/has_n.rb +1 -1
  8. data/lib/neo4j/active_node/labels.rb +1 -11
  9. data/lib/neo4j/active_node/node_wrapper.rb +1 -1
  10. data/lib/neo4j/active_node/query/query_proxy_methods_of_mass_updating.rb +1 -1
  11. data/lib/neo4j/active_rel/rel_wrapper.rb +2 -2
  12. data/lib/neo4j/ansi.rb +14 -0
  13. data/lib/neo4j/core.rb +14 -0
  14. data/lib/neo4j/core/connection_failed_error.rb +6 -0
  15. data/lib/neo4j/core/cypher_error.rb +37 -0
  16. data/lib/neo4j/core/driver.rb +83 -0
  17. data/lib/neo4j/core/has_uri.rb +63 -0
  18. data/lib/neo4j/core/instrumentable.rb +36 -0
  19. data/lib/neo4j/core/label.rb +158 -0
  20. data/lib/neo4j/core/logging.rb +44 -0
  21. data/lib/neo4j/core/node.rb +23 -0
  22. data/lib/neo4j/core/querable.rb +88 -0
  23. data/lib/neo4j/core/query.rb +487 -0
  24. data/lib/neo4j/core/query_builder.rb +32 -0
  25. data/lib/neo4j/core/query_clauses.rb +727 -0
  26. data/lib/neo4j/core/query_find_in_batches.rb +49 -0
  27. data/lib/neo4j/core/relationship.rb +13 -0
  28. data/lib/neo4j/core/responses.rb +50 -0
  29. data/lib/neo4j/core/result.rb +33 -0
  30. data/lib/neo4j/core/schema.rb +30 -0
  31. data/lib/neo4j/core/schema_errors.rb +12 -0
  32. data/lib/neo4j/core/wrappable.rb +30 -0
  33. data/lib/neo4j/migration.rb +2 -2
  34. data/lib/neo4j/migrations/base.rb +1 -1
  35. data/lib/neo4j/model_schema.rb +2 -2
  36. data/lib/neo4j/railtie.rb +8 -52
  37. data/lib/neo4j/schema/operation.rb +1 -1
  38. data/lib/neo4j/shared.rb +1 -1
  39. data/lib/neo4j/shared/property.rb +1 -1
  40. data/lib/neo4j/tasks/migration.rake +5 -4
  41. data/lib/neo4j/transaction.rb +137 -0
  42. data/lib/neo4j/version.rb +1 -1
  43. data/neo4j.gemspec +5 -5
  44. metadata +59 -26
  45. data/bin/neo4j-jars +0 -33
  46. data/lib/neo4j/active_base/session_registry.rb +0 -12
  47. data/lib/neo4j/session_manager.rb +0 -78
@@ -0,0 +1,49 @@
1
+ module Neo4j
2
+ module Core
3
+ module QueryFindInBatches
4
+ def find_in_batches(node_var, prop_var, options = {})
5
+ validate_find_in_batches_options!(options)
6
+
7
+ batch_size = options.delete(:batch_size) || 1000
8
+
9
+ query = reorder(node_var => prop_var).limit(batch_size)
10
+
11
+ records = query.to_a
12
+
13
+ while records.any?
14
+ records_size = records.size
15
+ primary_key_offset = primary_key_offset(records.last, node_var, prop_var)
16
+
17
+ yield records
18
+
19
+ break if records_size < batch_size
20
+
21
+ primary_key_var = Neo4j::Core::QueryClauses::Clause.from_key_and_single_value(node_var, prop_var)
22
+ records = query.where("#{primary_key_var} > {primary_key_offset}")
23
+ .params(primary_key_offset: primary_key_offset).to_a
24
+ end
25
+ end
26
+
27
+ def find_each(*args, &block)
28
+ find_in_batches(*args) { |batch| batch.each(&block) }
29
+ end
30
+
31
+ private
32
+
33
+ def validate_find_in_batches_options!(options)
34
+ invalid_keys = options.keys.map(&:to_sym) - [:batch_size]
35
+ fail ArgumentError, "Invalid keys: #{invalid_keys.join(', ')}" if not invalid_keys.empty?
36
+ end
37
+
38
+ def primary_key_offset(last_record, node_var, prop_var)
39
+ last_record.send(node_var).send(prop_var)
40
+ rescue NoMethodError
41
+ begin
42
+ last_record.send(node_var).properties[prop_var.to_sym]
43
+ rescue NoMethodError
44
+ last_record.send("#{node_var}.#{prop_var}") # In case we're explicitly returning it
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,13 @@
1
+ require 'neo4j/core/wrappable'
2
+
3
+ module Neo4j
4
+ module Core
5
+ module Relationship
6
+ def props; properties; end
7
+ def neo_id; id; end
8
+ def start_node_neo_id; start_node_id; end
9
+ def end_node_neo_id; end_node_id; end
10
+ def rel_type; type; end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,50 @@
1
+ require 'neo4j/core/result'
2
+ require 'active_support/core_ext/module/attribute_accessors'
3
+
4
+ module Neo4j
5
+ module Core
6
+ module Responses
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ mattr_accessor :wrap_level
11
+ end
12
+
13
+ class_methods do
14
+ def result_from_data(entities_data)
15
+ rows = entities_data.map do |entity_data|
16
+ wrap(entity_data.values)
17
+ end
18
+
19
+ Neo4j::Core::Result.new(entities_data.keys, rows)
20
+ end
21
+
22
+ private
23
+
24
+ def wrap(value)
25
+ case value
26
+ when Neo4j::Driver::Types::Entity
27
+ wrap_by_level(value)
28
+ when Neo4j::Driver::Types::Path
29
+ value
30
+ when Hash
31
+ value.map { |key, val| [key, wrap(val)] }.to_h
32
+ when Enumerable
33
+ value.map(&method(:wrap))
34
+ else
35
+ value
36
+ end
37
+ end
38
+
39
+ def wrap_by_level(entity)
40
+ case wrap_level
41
+ when :core_entity
42
+ entity
43
+ else
44
+ entity.wrap
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,33 @@
1
+ module Neo4j
2
+ module Core
3
+ class Result
4
+ attr_reader :columns, :rows
5
+
6
+ def initialize(columns, rows)
7
+ @columns = columns.map(&:to_sym)
8
+ @rows = rows
9
+ @struct_class = Struct.new(:index, *@columns)
10
+ end
11
+
12
+ include Enumerable
13
+
14
+ def each
15
+ structs.each do |struct|
16
+ yield struct
17
+ end
18
+ end
19
+
20
+ def structs
21
+ @structs ||= rows.each_with_index.map do |row, index|
22
+ @struct_class.new(index, *row)
23
+ end
24
+ end
25
+
26
+ def hashes
27
+ @hashes ||= rows.map do |row|
28
+ Hash[@columns.zip(row)]
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,30 @@
1
+ module Neo4j
2
+ module Core
3
+ module Schema
4
+ def version
5
+ result = query('CALL dbms.components()', {}, skip_instrumentation: true)
6
+
7
+ # BTW: community / enterprise could be retrieved via `result.first.edition`
8
+ result.first.versions[0]
9
+ end
10
+
11
+ def indexes
12
+ result = query('CALL db.indexes()', {}, skip_instrumentation: true)
13
+
14
+ result.map do |row|
15
+ label, property = row.description.match(/INDEX ON :([^\(]+)\(([^\)]+)\)/)[1, 2]
16
+ { type: row.type.to_sym, label: label.to_sym, properties: [property.to_sym], state: row.state.to_sym }
17
+ end
18
+ end
19
+
20
+ def constraints
21
+ result = query('CALL db.indexes()', {}, skip_instrumentation: true)
22
+
23
+ result.select { |row| row.type == 'node_unique_property' }.map do |row|
24
+ label, property = row.description.match(/INDEX ON :([^\(]+)\(([^\)]+)\)/)[1, 2]
25
+ { type: :uniqueness, label: label.to_sym, properties: [property.to_sym] }
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,12 @@
1
+ module Neo4j
2
+ module Core
3
+ module SchemaErrors
4
+ class ConstraintValidationFailedError < CypherError;
5
+ end
6
+ class ConstraintAlreadyExistsError < CypherError;
7
+ end
8
+ class IndexAlreadyExistsError < CypherError;
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,30 @@
1
+ module Neo4j
2
+ module Core
3
+ module Wrappable
4
+ extend ActiveSupport::Concern
5
+
6
+ def wrap
7
+ self.class.wrap(self)
8
+ end
9
+
10
+ class_methods do
11
+ def wrapper_callback(proc)
12
+ fail 'Callback already specified!' if @wrapper_callback
13
+ @wrapper_callback = proc
14
+ end
15
+
16
+ def clear_wrapper_callback
17
+ @wrapper_callback = nil
18
+ end
19
+
20
+ def wrap(node)
21
+ if @wrapper_callback
22
+ @wrapper_callback.call(node)
23
+ else
24
+ node
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -28,7 +28,7 @@ module Neo4j
28
28
  end
29
29
 
30
30
  def query(*args)
31
- Neo4j::ActiveBase.current_session.query(*args)
31
+ Neo4j::ActiveBase.current_driver.query(*args)
32
32
  end
33
33
 
34
34
  class AddIdProperty < Neo4j::Migration
@@ -115,7 +115,7 @@ MESSAGE
115
115
  # def id_batch_set(label, id_property, new_ids, count)
116
116
  # tx = Neo4j::ActiveBase.new_transaction
117
117
 
118
- # Neo4j::ActiveBase.current_session.query("MATCH (n:`#{label}`) WHERE NOT EXISTS(n.#{id_property})
118
+ # Neo4j::ActiveBase.current_driver.query("MATCH (n:`#{label}`) WHERE NOT EXISTS(n.#{id_property})
119
119
  # with COLLECT(n) as nodes, #{new_ids} as ids
120
120
  # FOREACH(i in range(0,#{count - 1})|
121
121
  # FOREACH(node in [nodes[i]]|
@@ -67,7 +67,7 @@ module Neo4j
67
67
  end
68
68
 
69
69
  def log_queries
70
- subscriber = Neo4j::Core::CypherSession::Adaptors::Base.subscribe_to_query(&method(:output))
70
+ subscriber = Neo4j::Transaction.subscribe_to_query(&method(:output))
71
71
  yield
72
72
  ensure
73
73
  ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
@@ -32,7 +32,7 @@ module Neo4j
32
32
  def model_constraints
33
33
  return @model_constraints if @model_constraints
34
34
 
35
- constraints = Neo4j::ActiveBase.current_session.constraints.each_with_object({}) do |row, result|
35
+ constraints = Neo4j::Transaction.constraints.each_with_object({}) do |row, result|
36
36
  result[row[:label]] ||= []
37
37
  result[row[:label]] << row[:properties]
38
38
  end
@@ -43,7 +43,7 @@ module Neo4j
43
43
  def model_indexes
44
44
  return @model_indexes if @model_indexes
45
45
 
46
- indexes = Neo4j::ActiveBase.current_session.indexes.each_with_object({}) do |row, result|
46
+ indexes = Neo4j::Transaction.indexes.each_with_object({}) do |row, result|
47
47
  result[row[:label]] ||= []
48
48
  result[row[:label]] << row[:properties]
49
49
  end
data/lib/neo4j/railtie.rb CHANGED
@@ -1,9 +1,8 @@
1
1
  require 'active_support/notifications'
2
2
  require 'rails/railtie'
3
- require 'neo4j/session_manager'
4
3
  # Need the action_dispatch railtie to have action_dispatch.rescue_responses initialized correctly
5
4
  require 'action_dispatch/railtie'
6
- require 'neo4j/core/cypher_session/adaptors'
5
+ require 'neo4j/core/driver'
7
6
 
8
7
  module Neo4j
9
8
  class Railtie < ::Rails::Railtie
@@ -59,49 +58,15 @@ module Neo4j
59
58
  end
60
59
 
61
60
  def setup!(neo4j_config = empty_config)
62
- wait_for_connection = neo4j_config.wait_for_connection
63
- type, url, path, options = final_session_config!(neo4j_config).values_at(:type, :url, :path, :options)
64
- type ||= default_session_type
61
+ url, path, options = final_session_config!(neo4j_config).values_at(:url, :path, :options)
65
62
  options ||= {}
66
- register_neo4j_cypher_logging(type, options)
63
+ register_neo4j_cypher_logging
67
64
 
68
- Neo4j::SessionManager.open_neo4j_session(type,
69
- url || path || default_session_path_or_url,
70
- wait_for_connection,
71
- options)
65
+ Neo4j::ActiveBase.new_driver( url || path || default_session_path_or_url, options)
72
66
  end
73
67
 
74
68
  def final_session_config!(neo4j_config)
75
- support_deprecated_session_configs!(neo4j_config)
76
-
77
- (neo4j_config[:session].empty? ? yaml_config_data : neo4j_config[:session]).dup.tap do |result|
78
- result[:type] ||= URI(result[:url]).scheme if result[:url]
79
- end
80
- end
81
-
82
- def support_deprecated_session_configs!(neo4j_config)
83
- if neo4j_config.sessions.present?
84
- ActiveSupport::Deprecation.warn('neo4j.config.sessions is deprecated, please use neo4j.config.session (not an array)')
85
- neo4j_config[:session] = neo4j_config.sessions[0] if neo4j_config[:session].empty?
86
- end
87
-
88
- %w(type path url options).each do |key|
89
- value = neo4j_config.send("session_#{key}")
90
- if value.present?
91
- ActiveSupport::Deprecation.warn("neo4j.config.session_#{key} is deprecated, please use neo4j.config.session.#{key}")
92
- neo4j_config[:session][key] = value
93
- end
94
- end
95
- end
96
-
97
- def default_session_type
98
- if ENV['NEO4J_URL']
99
- scheme = URI(ENV['NEO4J_URL']).scheme
100
- fail "Invalid scheme for NEO4J_URL: #{scheme}" if !%w(http https bolt).include?(scheme)
101
- scheme == 'https' ? 'http' : scheme
102
- else
103
- ENV['NEO4J_TYPE'] || :http
104
- end.to_sym
69
+ (neo4j_config[:session].empty? ? yaml_config_data : neo4j_config[:session]).dup
105
70
  end
106
71
 
107
72
  def default_session_path_or_url
@@ -123,7 +88,7 @@ module Neo4j
123
88
  end.detect(&:exist?)
124
89
  end
125
90
 
126
- def register_neo4j_cypher_logging(session_type, options)
91
+ def register_neo4j_cypher_logging
127
92
  return if @neo4j_cypher_logging_registered
128
93
 
129
94
  Neo4j::Core::Query.pretty_cypher = Neo4j::Config[:pretty_logged_cypher_queries]
@@ -131,19 +96,10 @@ module Neo4j
131
96
  logger_proc = ->(message) do
132
97
  (Neo4j::Config[:logger] ||= Rails.logger).debug message
133
98
  end
134
- Neo4j::Core::CypherSession::Adaptors::Base.subscribe_to_query(&logger_proc)
135
- subscribe_to_session_type_logging!(session_type, options, logger_proc)
99
+ Neo4j::Transaction.subscribe_to_query(&logger_proc)
100
+ Neo4j::Transaction.subscribe_to_request(&logger_proc)
136
101
 
137
102
  @neo4j_cypher_logging_registered = true
138
103
  end
139
-
140
- def subscribe_to_session_type_logging!(session_type, options, logger_proc)
141
- SessionManager
142
- .adaptor_class(session_type, options)
143
- .send(
144
- [:embedded, :embedded_db].include?(session_type.to_sym) ? :subscribe_to_transaction : :subscribe_to_request,
145
- &logger_proc
146
- )
147
- end
148
104
  end
149
105
  end
@@ -7,7 +7,7 @@ module Neo4j
7
7
  @label = if label.is_a?(Neo4j::Core::Label)
8
8
  label
9
9
  else
10
- Neo4j::Core::Label.new(label, ActiveBase.current_session)
10
+ Neo4j::Core::Label.new(label)
11
11
  end
12
12
 
13
13
  @property = property.to_sym
data/lib/neo4j/shared.rb CHANGED
@@ -14,7 +14,7 @@ module Neo4j
14
14
 
15
15
  # remove?
16
16
  def neo4j_session
17
- Neo4j::ActiveBase.current_session
17
+ Neo4j::ActiveBase.current_driver
18
18
  end
19
19
 
20
20
  # remove?
@@ -80,7 +80,7 @@ module Neo4j::Shared
80
80
  end
81
81
  end
82
82
 
83
- DATE_KEY_REGEX = /\A([^\(]+)\((\d+)([if])\)$/
83
+ DATE_KEY_REGEX = /\A([^\(]+)\((\d+)([ifs])\)$/
84
84
  # Gives support for Rails date_select, datetime_select, time_select helpers.
85
85
  def process_attributes(attributes = nil)
86
86
  return attributes if attributes.blank?
@@ -10,8 +10,7 @@ if !defined?(Rails) && !Rake::Task.task_defined?('environment')
10
10
  neo4j_url = ENV['NEO4J_URL'] || 'http://localhost:7474'
11
11
  $LOAD_PATH.unshift File.dirname('./')
12
12
  Neo4j::ActiveBase.on_establish_session do
13
- type = neo4j_url =~ /^bolt/ ? :bolt : :http
14
- Neo4j::SessionManager.open_neo4j_session(type, neo4j_url)
13
+ Neo4j::ActiveBase.new_driver(neo4j_url)
15
14
  end
16
15
  end
17
16
  end
@@ -95,12 +94,14 @@ COMMENT
95
94
  require 'neo4j/migrations/schema'
96
95
 
97
96
  args.with_defaults(remove_missing: false)
97
+
98
98
  schema_data = YAML.safe_load(File.read(SCHEMA_YAML_PATH), [Symbol])
99
+
99
100
  Neo4j::Core::CypherSession::Adaptors::Base.subscribe_to_query(&method(:puts))
101
+
100
102
  Neo4j::ActiveBase.run_transaction do
101
103
  Neo4j::Migrations::Schema.synchronize_schema_data(Neo4j::ActiveBase.current_session, schema_data, args[:remove_missing])
102
- end
103
- Neo4j::ActiveBase.run_transaction do
104
+
104
105
  runner = Neo4j::Migrations::Runner.new
105
106
  runner.mark_versions_as_complete(schema_data[:versions]) # Run in test mode?
106
107
  end
@@ -0,0 +1,137 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+ require 'active_support/core_ext/module/attribute_accessors_per_thread'
3
+ require 'neo4j/core/querable'
4
+ require 'neo4j/core/schema'
5
+
6
+ module Neo4j
7
+ class Transaction
8
+ include Neo4j::Core::Querable
9
+ extend Neo4j::Core::Schema
10
+
11
+ thread_mattr_accessor :stack
12
+ attr_reader :root
13
+ attr_reader :driver_tx, :driver_session
14
+
15
+ class << self
16
+ # Runs the given block in a new transaction.
17
+ # @param [Boolean] run_in_tx if true a new transaction will not be created, instead if will simply yield to the given block
18
+ # @@yield [Neo4j::Transaction::Instance]
19
+ def run(driver, run_in_tx)
20
+ return yield(nil) unless run_in_tx
21
+
22
+ tx = Neo4j::Transaction.new
23
+ yield tx
24
+ rescue Exception => e # rubocop:disable Lint/RescueException
25
+
26
+ tx.mark_failed unless tx.nil?
27
+ raise e
28
+ ensure
29
+ tx.close unless tx.nil?
30
+ end
31
+
32
+ def root
33
+ stack.first
34
+ end
35
+ end
36
+
37
+ def initialize(_options = {})
38
+ (self.stack ||= []) << self
39
+
40
+ @root = stack.first
41
+ return unless root?
42
+ @driver_session = Neo4j::Core::Driver.singleton.driver.session(Neo4j::Driver::AccessMode::WRITE)
43
+ @driver_tx = @driver_session.begin_transaction
44
+ rescue StandardError => e
45
+ self.stack = []
46
+ @driver_tx.close if @driver_tx
47
+ @driver_session.close if @driver_session
48
+ raise e
49
+ end
50
+
51
+ # Commits or marks this transaction for rollback, depending on whether #mark_failed has been previously invoked.
52
+ def close
53
+ fail 'Tried closing when transaction stack is empty (maybe you closed too many?)' if stack.empty?
54
+ fail "Closed transaction which wasn't the most recent on the stack (maybe you forgot to close one?)" if stack.pop != self
55
+
56
+ post_close! if stack.empty?
57
+ end
58
+
59
+ # Marks this transaction as failed,
60
+ # which means that it will unconditionally be rolled back
61
+ # when #close is called.
62
+ # Aliased for legacy purposes.
63
+ def mark_failed
64
+ root.mark_failed if root && root != self
65
+ @failure = true
66
+ end
67
+
68
+ alias failure mark_failed
69
+
70
+ # If it has been marked as failed.
71
+ # Aliased for legacy purposes.
72
+ def failed?
73
+ !!@failure
74
+ end
75
+
76
+ alias failure? failed?
77
+
78
+ def root?
79
+ @root == self
80
+ end
81
+
82
+ def query(*args)
83
+ options = if args[0].is_a?(::Neo4j::Core::Query)
84
+ args[1] ||= {}
85
+ else
86
+ args[1] ||= {}
87
+ args[2] ||= {}
88
+ end
89
+ options[:transaction] ||= self
90
+
91
+ self.class.query(*args)
92
+ end
93
+
94
+ def queries(options = {}, &block)
95
+ self.class.queries({ transaction: self }.merge(options), &block)
96
+ end
97
+
98
+ def after_commit_registry
99
+ @after_commit_registry ||= []
100
+ end
101
+
102
+ def after_commit(&block)
103
+ after_commit_registry << block
104
+ end
105
+
106
+ def commit
107
+ return unless root?
108
+ begin
109
+ @driver_tx.success
110
+ @driver_tx.close
111
+ ensure
112
+ @driver_session.close
113
+ end
114
+ end
115
+
116
+ def delete
117
+ root.driver_tx.failure
118
+ root.driver_tx.close
119
+ root.driver_session.close
120
+ end
121
+
122
+ def root_tx
123
+ root.driver_tx
124
+ end
125
+
126
+ private
127
+
128
+ def post_close!
129
+ if failed?
130
+ delete
131
+ else
132
+ commit
133
+ after_commit_registry.each(&:call)
134
+ end
135
+ end
136
+ end
137
+ end