delfos 0.0.1.pre.rc1 → 0.0.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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/lib/delfos.rb +22 -78
  3. data/lib/delfos/call_stack.rb +39 -0
  4. data/lib/delfos/call_stack/stack.rb +59 -0
  5. data/lib/delfos/file_system/common_path.rb +62 -0
  6. data/lib/delfos/{distance/calculation.rb → file_system/distance_calculation.rb} +4 -15
  7. data/lib/delfos/file_system/path_determination.rb +37 -0
  8. data/lib/delfos/{distance → file_system}/relation.rb +1 -1
  9. data/lib/delfos/method_logging.rb +32 -49
  10. data/lib/delfos/method_logging/call_site_parsing.rb +74 -0
  11. data/lib/delfos/method_logging/code_location.rb +34 -98
  12. data/lib/delfos/method_logging/{args.rb → method_parameters.rb} +17 -18
  13. data/lib/delfos/neo4j.rb +58 -0
  14. data/lib/delfos/neo4j/batch/execution.rb +149 -0
  15. data/lib/delfos/neo4j/{execution_persistence.rb → call_stack_query.rb} +12 -22
  16. data/lib/delfos/neo4j/distance/call_site_fetcher.rb +19 -0
  17. data/lib/delfos/neo4j/distance/update.rb +50 -0
  18. data/lib/delfos/neo4j/informer.rb +62 -34
  19. data/lib/delfos/neo4j/query_execution/errors.rb +30 -0
  20. data/lib/delfos/neo4j/query_execution/http.rb +61 -0
  21. data/lib/delfos/neo4j/query_execution/http_query.rb +67 -0
  22. data/lib/delfos/neo4j/query_execution/sync.rb +35 -0
  23. data/lib/delfos/neo4j/query_execution/transactional.rb +68 -0
  24. data/lib/delfos/neo4j/schema.rb +73 -0
  25. data/lib/delfos/patching/basic_object.rb +1 -5
  26. data/lib/delfos/patching/method_cache.rb +83 -0
  27. data/lib/delfos/patching/method_override.rb +76 -66
  28. data/lib/delfos/patching/module_defining_methods.rb +105 -0
  29. data/lib/delfos/patching/unstubber.rb +34 -0
  30. data/lib/delfos/setup.rb +88 -0
  31. metadata +42 -15
  32. data/lib/delfos/common_path.rb +0 -58
  33. data/lib/delfos/execution_chain.rb +0 -75
  34. data/lib/delfos/method_logging/added_methods.rb +0 -67
  35. data/lib/delfos/neo4j/distance_update.rb +0 -73
  36. data/lib/delfos/neo4j/query_execution.rb +0 -79
  37. data/lib/delfos/patching/unstubbing_spec_helper.rb +0 -58
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+ require "json"
3
+ require "uri"
4
+
5
+ require_relative "http_query"
6
+ require_relative "errors"
7
+
8
+ module Delfos
9
+ module Neo4j
10
+ module QueryExecution
11
+ class Sync
12
+ include HttpQuery
13
+
14
+ def perform
15
+ raise InvalidQuery.new(json["errors"], query, params) if errors?
16
+
17
+ strip_out_meta_data
18
+ end
19
+
20
+ private
21
+
22
+ def strip_out_meta_data
23
+ json["results"]
24
+ &.first
25
+ &.[]("data")
26
+ &.map { |r| r["row"] }
27
+ end
28
+
29
+ def uri
30
+ @uri ||= Delfos.neo4j.uri_for("/db/data/transaction/commit")
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+ require "json"
3
+ require "uri"
4
+ require "time"
5
+
6
+ require_relative "http"
7
+ require_relative "http_query"
8
+
9
+ module Delfos
10
+ module Neo4j
11
+ module QueryExecution
12
+ class Transactional
13
+ include HttpQuery
14
+
15
+ def self.flush!(commit_url)
16
+ response = Http.new(commit_url).post({ statements: [] }.to_json)
17
+
18
+ check_for_error(commit_url, response)
19
+
20
+ response.code == "200"
21
+ end
22
+
23
+ VALID_RESPONSE_MATCHER = /\A2\d\d\z/
24
+
25
+ def self.check_for_error(uri, response)
26
+ return if response.code[VALID_RESPONSE_MATCHER]
27
+
28
+ if response.code == "404"
29
+ raise ExpiredTransaction.new(uri, response)
30
+ end
31
+
32
+ raise InvalidCommit.new(uri, response)
33
+ end
34
+
35
+ def perform
36
+ self.class.check_for_error(uri, response)
37
+
38
+ raise InvalidQuery.new(json["errors"], query, params) if errors?
39
+
40
+ transaction_url = URI.parse header("location") if header("location")
41
+ commit_url = URI.parse json["commit"] if json["commit"]
42
+ expires = Time.parse json["transaction"]["expires"] if json["transaction"]
43
+
44
+ [transaction_url, commit_url, expires]
45
+ end
46
+
47
+ private
48
+
49
+ def header(name)
50
+ response.each_header.to_a.find { |n, _| n == name }&.last
51
+ end
52
+
53
+ def uri
54
+ @uri ||= Delfos.neo4j.uri_for("/db/data/transaction")
55
+ end
56
+ end
57
+
58
+ class InvalidCommit < IOError
59
+ def initialize(commit_url, response)
60
+ super ["URL:", commit_url, "response:", response].join("\n ")
61
+ end
62
+ end
63
+
64
+ class ExpiredTransaction < InvalidCommit
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+ module Delfos
3
+ module Neo4j
4
+ module Schema
5
+ extend self
6
+
7
+ def ensure_constraints!(required)
8
+ log "-" * 80
9
+ log "checking constraints"
10
+ log Time.now
11
+ log "-" * 80
12
+
13
+ if satisfies_constraints?(required)
14
+ log "Neo4j schema constraints satisfied"
15
+ else
16
+ error "-" * 80
17
+ error "Neo4j schema constraints not satisfied - adding"
18
+ error Time.now
19
+
20
+ required.each do |label, attribute|
21
+ create_constraint(label, attribute)
22
+ end
23
+
24
+ log "-" * 80
25
+ log "Constraints added"
26
+ log Time.now
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def error(s)
33
+ log(s, :error)
34
+ end
35
+
36
+ def log(s, type = :debug)
37
+ Delfos.logger.send(type, s)
38
+ end
39
+
40
+ def create_constraint(label, attribute)
41
+ Neo4j.execute_sync <<-QUERY
42
+ CREATE CONSTRAINT ON (c:#{label}) ASSERT c.#{attribute} IS UNIQUE
43
+ QUERY
44
+ end
45
+
46
+ def satisfies_constraints?(required)
47
+ existing_constraints = fetch_existing_constraints
48
+
49
+ required.inject(true) do |_result, (label, attribute)|
50
+ constraint = existing_constraints.find { |c| c["label"] == label }
51
+
52
+ constraint && constraint["property_keys"].include?(attribute)
53
+ end
54
+ end
55
+
56
+ def fetch_existing_constraints
57
+ response = QueryExecution::Http.new(constraints_uri).get
58
+
59
+ if response.code == "200"
60
+ JSON.parse response.body
61
+ else
62
+ raise IOError.new uri, response
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def constraints_uri
69
+ Delfos.neo4j.uri_for("/db/data/schema/constraint")
70
+ end
71
+ end
72
+ end
73
+ end
@@ -3,15 +3,11 @@ require_relative "method_override"
3
3
 
4
4
  class BasicObject
5
5
  def self.method_added(name)
6
- return if %i(to_s singleton_method_added method_added).include?(name)
7
-
8
6
  ::Delfos::Patching::MethodOverride.setup(self, name, private_instance_methods, class_method: false)
9
7
  end
10
8
 
11
9
  def self.singleton_method_added(name)
12
- # can't currently support to_s and name as those are used inside the method logging code
13
- return if %i(to_s name extended included inherited method_added singleton_method_added).include?(name)
14
- return if ::Delfos::MethodLogging.exclude?(method(name))
10
+ return if %i(define_method extended included inherited method_added singleton_method_added).include?(name)
15
11
 
16
12
  ::Delfos::Patching::MethodOverride.setup(self, name, private_methods, class_method: true)
17
13
  end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+ require "Forwardable" unless defined? Forwardable
3
+
4
+ module Delfos
5
+ module Patching
6
+ class MethodCache
7
+ class << self
8
+ extend Forwardable
9
+
10
+ def_delegators :instance,
11
+ :files_for,
12
+ :append,
13
+ :find
14
+
15
+ def reset!
16
+ @instance = nil
17
+ end
18
+
19
+ def instance
20
+ @instance ||= new
21
+ end
22
+
23
+ def each_method
24
+ instance.send(:added_methods).each do |klass, methods|
25
+ methods.each do |k, m|
26
+ class_method = !!(k[/^ClassMethod_/])
27
+ yield klass, m, class_method
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ def initialize
34
+ @added_methods = {}
35
+ end
36
+
37
+ def files_for(klass)
38
+ fetch(klass).
39
+ values.
40
+ map(&:source_location).
41
+ compact.
42
+ map(&:first).
43
+ compact.
44
+ uniq
45
+ end
46
+
47
+ def append(klass:, method:)
48
+ class_method = method.respond_to?(:receiver) && method.receiver.is_a?(Class)
49
+ key = key_for(class_method, method.name)
50
+ m = fetch(klass)[key]
51
+
52
+ fetch(klass)[key] = method if m.nil?
53
+ end
54
+
55
+ def find(klass:, method_name:, class_method:)
56
+ key = key_for(class_method, method_name)
57
+
58
+ fetch(klass)[key]
59
+ end
60
+
61
+ private
62
+
63
+ attr_reader :added_methods
64
+
65
+ def key_for(class_method, method_name)
66
+ class_method ? "ClassMethod_#{method_name}" : "InstanceMethod_#{method_name}"
67
+ end
68
+
69
+ def fetch(klass)
70
+ # Find method definitions defined in klass or its ancestors
71
+ super_klass = klass.ancestors.detect do |k|
72
+ (fetch_without_default(k) || {}).values.length.positive?
73
+ end
74
+
75
+ added_methods[(super_klass || klass).to_s] ||= {}
76
+ end
77
+
78
+ def fetch_without_default(klass)
79
+ added_methods[klass.to_s]
80
+ end
81
+ end
82
+ end
83
+ end
@@ -1,25 +1,60 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../method_logging"
3
+ require "delfos/method_logging"
4
+ require "delfos/call_stack"
5
+
6
+ require_relative "module_defining_methods"
7
+ require_relative "unstubber"
4
8
 
5
9
  module Delfos
6
10
  module Patching
11
+ MUTEX = Mutex.new
12
+
7
13
  class MethodOverride
14
+ include ModuleDefiningMethods
15
+
8
16
  class << self
9
17
  def setup(klass, name, private_methods, class_method:)
18
+ return if skip_meta_programming_defined_method?
19
+
20
+ MUTEX.synchronize do
21
+ return if Thread.current[:__delfos_disable_patching]
22
+ end
23
+
10
24
  instance = new(klass, name, private_methods, class_method)
11
25
 
12
26
  instance.ensure_method_recorded_once!
13
27
  end
28
+
29
+ META_PROGRAMMING_REGEX = /`define_method'\z|`attr_accessor'\z|`attr_reader'\z|`attr_writer'\z/
30
+
31
+ def skip_meta_programming_defined_method?
32
+ stack = caller.dup
33
+
34
+ i = stack.index do |l|
35
+ l["delfos/patching/basic_object.rb"]
36
+ end
37
+
38
+ return unless i
39
+
40
+ result = stack[i + 1][META_PROGRAMMING_REGEX]
41
+
42
+ return unless result
43
+
44
+ Delfos.logger.debug "Skipping setting up delfos logging of method defined by #{result} #{stack[i+1]}"
45
+ true
46
+ end
47
+
48
+
14
49
  end
15
50
 
16
51
  attr_reader :klass, :name, :private_methods, :class_method
17
52
 
18
53
  def initialize(klass, name, private_methods, class_method)
19
- @klass = klass
20
- @name = name
54
+ @klass = klass
55
+ @name = name
21
56
  @private_methods = private_methods
22
- @class_method = class_method
57
+ @class_method = class_method
23
58
  original_method # ensure memoized method is the original not the overridden one
24
59
  end
25
60
 
@@ -27,59 +62,55 @@ module Delfos
27
62
  record_method! { setup }
28
63
  end
29
64
 
30
- # Redefine the method (only once) at runtime to enabling logging to Neo4j
65
+
66
+ # Redefine the method at runtime to enabling logging to Neo4j
31
67
  def setup
32
- processor = method(:process)
33
68
  cm = class_method
34
-
35
- method_defining_method.call(name) do |*args, **kw_args, &block|
36
- arguments = MethodArguments.new(args, kw_args, block, cm)
37
-
38
- processor.call(self, caller.dup, binding.dup, arguments)
39
- end
40
- end
41
-
42
- def process(instance, stack, caller_binding, arguments)
43
- method_to_call = method_selector(instance)
44
-
45
- call_site = Delfos::MethodLogging::CodeLocation.from_call_site(stack, caller_binding)
46
-
47
- with_logging(call_site, instance, method_to_call, class_method, arguments) do
48
- arguments.apply_to(method_to_call)
69
+ with_stack = method(:with_stack)
70
+ method_name = name
71
+ om = original_method
72
+
73
+ mod = module_definition(klass, name, class_method) do
74
+ define_method(method_name) do |*args, **kw_args, &block|
75
+ stack = caller.dup
76
+ caller_binding = binding.dup
77
+ parameters = Delfos::MethodLogging::MethodParameters.new(args, kw_args, block)
78
+
79
+ call_site = Delfos::MethodLogging::CodeLocation.from_call_site(stack, caller_binding)
80
+
81
+ if call_site
82
+ Delfos::MethodLogging.log(call_site, self, om, cm, parameters)
83
+ end
84
+
85
+ with_stack.call(call_site) do
86
+ if !kw_args.empty?
87
+ super(*args, **kw_args, &block)
88
+ else
89
+ super(*args, &block)
90
+ end
91
+ end
92
+ end
49
93
  end
50
- end
94
+ return unless mod
51
95
 
52
- def with_logging(call_site, instance, method_to_call, class_method, arguments)
53
- Delfos.method_logging.log(call_site, instance, method_to_call, class_method, arguments) if call_site
54
-
55
- with_stack(call_site) do
56
- yield
96
+ if class_method
97
+ klass.prepend mod
98
+ else
99
+ klass.instance_eval { prepend mod }
57
100
  end
58
101
  end
59
102
 
60
-
61
103
  def with_stack(call_site)
62
104
  return yield unless call_site
63
105
 
64
106
  begin
65
- ExecutionChain.push(call_site)
107
+ CallStack.push(call_site)
66
108
  yield
67
109
  ensure
68
- ExecutionChain.pop
110
+ CallStack.pop
69
111
  end
70
112
  end
71
113
 
72
- MethodArguments = Struct.new(:args, :keyword_args, :block, :class_method) do
73
- def apply_to(method)
74
- if keyword_args.empty?
75
- method.call(*args, &block)
76
- else
77
- method.call(*args, **keyword_args, &block)
78
- end
79
- end
80
- end
81
-
82
-
83
114
  def original_method
84
115
  @original_method ||= if class_method
85
116
  klass.method(name)
@@ -92,22 +123,9 @@ module Delfos
92
123
 
93
124
  def record_method!
94
125
  return true if bail?
95
- yield
126
+ MethodCache.append(klass: klass, method: original_method)
96
127
 
97
- Delfos::MethodLogging::AddedMethods.append(klass, key, original_method)
98
- end
99
-
100
- def method_selector(instance)
101
- if class_method
102
- m = Delfos::MethodLogging::AddedMethods.find(instance, "ClassMethod_#{name}")
103
- m.receiver == instance ? m : m.unbind.bind(instance)
104
- else
105
- original_method.bind(instance)
106
- end
107
- end
108
-
109
- def method_defining_method
110
- class_method ? klass.method(:define_singleton_method) : klass.method(:define_method)
128
+ yield
111
129
  end
112
130
 
113
131
  def bail?
@@ -115,7 +133,7 @@ module Delfos
115
133
  end
116
134
 
117
135
  def method_has_been_added?
118
- Delfos::MethodLogging::AddedMethods.find(klass, key)
136
+ MethodCache.find(klass: klass, class_method: class_method, method_name: name)
119
137
  end
120
138
 
121
139
  def private_method?
@@ -123,15 +141,7 @@ module Delfos
123
141
  end
124
142
 
125
143
  def exclude?
126
- ::Delfos.method_logging.exclude?(original_method)
127
- end
128
-
129
- def key
130
- "#{type}_#{name}"
131
- end
132
-
133
- def type
134
- class_method ? "ClassMethod" : "InstanceMethod"
144
+ ::Delfos::MethodLogging.exclude?(original_method)
135
145
  end
136
146
  end
137
147
  end