activecypher 0.3.0 → 0.6.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 +4 -4
- data/lib/active_cypher/base.rb +28 -8
- data/lib/active_cypher/bolt/driver.rb +6 -16
- data/lib/active_cypher/bolt/session.rb +62 -50
- data/lib/active_cypher/bolt/transaction.rb +95 -90
- data/lib/active_cypher/connection_adapters/abstract_adapter.rb +28 -0
- data/lib/active_cypher/connection_adapters/abstract_bolt_adapter.rb +40 -32
- data/lib/active_cypher/connection_adapters/memgraph_adapter.rb +44 -0
- data/lib/active_cypher/connection_adapters/neo4j_adapter.rb +27 -1
- data/lib/active_cypher/connection_adapters/registry.rb +94 -0
- data/lib/active_cypher/connection_handler.rb +18 -3
- data/lib/active_cypher/connection_pool.rb +5 -23
- data/lib/active_cypher/connection_url_resolver.rb +14 -3
- data/lib/active_cypher/cypher_config.rb +2 -1
- data/lib/active_cypher/fixtures/dsl_context.rb +41 -0
- data/lib/active_cypher/fixtures/evaluator.rb +37 -0
- data/lib/active_cypher/fixtures/node_builder.rb +53 -0
- data/lib/active_cypher/fixtures/parser.rb +23 -0
- data/lib/active_cypher/fixtures/registry.rb +43 -0
- data/lib/active_cypher/fixtures/rel_builder.rb +96 -0
- data/lib/active_cypher/fixtures.rb +177 -0
- data/lib/active_cypher/generators/node_generator.rb +32 -3
- data/lib/active_cypher/generators/relationship_generator.rb +29 -2
- data/lib/active_cypher/generators/templates/application_graph_node.rb +1 -0
- data/lib/active_cypher/generators/templates/node.rb.erb +10 -6
- data/lib/active_cypher/generators/templates/relationship.rb.erb +7 -6
- data/lib/active_cypher/instrumentation.rb +186 -0
- data/lib/active_cypher/model/callbacks.rb +5 -13
- data/lib/active_cypher/model/connection_handling.rb +37 -52
- data/lib/active_cypher/model/connection_owner.rb +41 -33
- data/lib/active_cypher/model/core.rb +4 -12
- data/lib/active_cypher/model/countable.rb +10 -3
- data/lib/active_cypher/model/destruction.rb +23 -18
- data/lib/active_cypher/model/labelling.rb +45 -0
- data/lib/active_cypher/model/persistence.rb +52 -26
- data/lib/active_cypher/model/querying.rb +49 -25
- data/lib/active_cypher/railtie.rb +40 -5
- data/lib/active_cypher/relation.rb +10 -2
- data/lib/active_cypher/relationship.rb +77 -17
- data/lib/active_cypher/version.rb +1 -1
- data/lib/activecypher.rb +4 -1
- data/lib/cyrel/clause/set.rb +20 -10
- data/lib/cyrel/expression/property_access.rb +2 -0
- data/lib/cyrel/functions.rb +3 -1
- data/lib/cyrel/logging.rb +43 -0
- data/lib/cyrel/plus.rb +11 -0
- data/lib/cyrel/query.rb +7 -1
- data/lib/cyrel.rb +77 -18
- metadata +13 -2
- data/lib/active_cypher/connection_factory.rb +0 -130
@@ -47,7 +47,7 @@ module ActiveCypher
|
|
47
47
|
# --------------------------------------------------------------
|
48
48
|
# Attributes
|
49
49
|
# --------------------------------------------------------------
|
50
|
-
attribute :internal_id, :
|
50
|
+
attribute :internal_id, :integer
|
51
51
|
|
52
52
|
# --------------------------------------------------------------
|
53
53
|
# Connection fallback
|
@@ -59,6 +59,11 @@ module ActiveCypher
|
|
59
59
|
# WorksAtRelationship.connection # -> PersonNode.connection
|
60
60
|
#
|
61
61
|
def self.connection
|
62
|
+
# If a node_base_class is set (directly or by convention), always delegate to its connection
|
63
|
+
if (klass = node_base_class)
|
64
|
+
return klass.connection
|
65
|
+
end
|
66
|
+
|
62
67
|
return @connection if defined?(@connection) && @connection
|
63
68
|
|
64
69
|
begin
|
@@ -74,10 +79,51 @@ module ActiveCypher
|
|
74
79
|
class_attribute :_from_class_name, instance_writer: false
|
75
80
|
class_attribute :_to_class_name, instance_writer: false
|
76
81
|
class_attribute :_relationship_type, instance_writer: false
|
82
|
+
class_attribute :_node_base_class, instance_writer: false
|
77
83
|
|
78
84
|
class << self
|
79
85
|
attr_reader :last_internal_id
|
80
86
|
|
87
|
+
# DSL for setting or getting the node base class for connection delegation
|
88
|
+
def node_base_class(klass = nil)
|
89
|
+
if klass.nil?
|
90
|
+
# If not set, try convention: XxxRelationship -> XxxNode
|
91
|
+
return _node_base_class if _node_base_class
|
92
|
+
|
93
|
+
if name&.end_with?('Relationship')
|
94
|
+
node_base_name = name.sub(/Relationship\z/, 'Node')
|
95
|
+
begin
|
96
|
+
node_base_klass = node_base_name.constantize
|
97
|
+
if node_base_klass.respond_to?(:abstract_class?) && node_base_klass.abstract_class?
|
98
|
+
self._node_base_class = node_base_klass
|
99
|
+
return node_base_klass
|
100
|
+
end
|
101
|
+
rescue NameError
|
102
|
+
# Do nothing, fallback to nil
|
103
|
+
end
|
104
|
+
end
|
105
|
+
return _node_base_class
|
106
|
+
end
|
107
|
+
# Only allow setting on abstract relationship base classes
|
108
|
+
raise "Cannot set node_base_class on non-abstract relationship class #{name}" unless abstract_class?
|
109
|
+
unless klass.respond_to?(:abstract_class?) && klass.abstract_class?
|
110
|
+
raise ArgumentError, "node_base_class must be an abstract node base class (got #{klass})"
|
111
|
+
end
|
112
|
+
|
113
|
+
self._node_base_class = klass
|
114
|
+
end
|
115
|
+
|
116
|
+
# Prevent subclasses from overriding node_base_class
|
117
|
+
def inherited(subclass)
|
118
|
+
super
|
119
|
+
return unless _node_base_class
|
120
|
+
|
121
|
+
subclass._node_base_class = _node_base_class
|
122
|
+
def subclass.node_base_class(*)
|
123
|
+
raise "Cannot override node_base_class in subclass #{name}; it is locked to #{_node_base_class}"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
81
127
|
# -- endpoints ------------------------------------------------
|
82
128
|
def from_class(value = nil)
|
83
129
|
return _from_class_name if value.nil?
|
@@ -160,8 +206,10 @@ module ActiveCypher
|
|
160
206
|
raise 'Cannot destroy a new relationship' if new_record?
|
161
207
|
raise 'Relationship already destroyed' if destroyed?
|
162
208
|
|
163
|
-
|
164
|
-
|
209
|
+
adapter = self.class.connection.id_handler
|
210
|
+
|
211
|
+
cypher = "MATCH ()-[r]-() WHERE #{adapter.with_direct_id(internal_id)} DELETE r"
|
212
|
+
params = {}
|
165
213
|
|
166
214
|
self.class.connection.execute_cypher(cypher, params, 'Destroy Relationship')
|
167
215
|
@destroyed = true
|
@@ -186,22 +234,31 @@ module ActiveCypher
|
|
186
234
|
rel_ty = self.class.relationship_type
|
187
235
|
arrow = '->' # outgoing by default
|
188
236
|
|
189
|
-
|
190
|
-
parts
|
191
|
-
|
192
|
-
|
237
|
+
adapter = self.class.connection.id_handler
|
238
|
+
parts = []
|
239
|
+
|
240
|
+
# Build the Cypher query based on the adapter
|
241
|
+
id_clause = adapter.with_direct_node_ids(from_node.internal_id, to_node.internal_id)
|
242
|
+
parts << "MATCH (p), (h) WHERE #{id_clause}"
|
243
|
+
parts << "CREATE (p)-[r:#{rel_ty}]#{arrow}(h)"
|
193
244
|
parts << 'SET r += $props' unless props.empty? # only if we have props
|
194
|
-
parts <<
|
245
|
+
parts << "RETURN #{adapter.return_id}"
|
195
246
|
|
196
247
|
cypher = parts.join(' ')
|
197
|
-
params = {
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
248
|
+
params = { props: props }
|
249
|
+
|
250
|
+
# Execute Cypher query
|
251
|
+
result = self.class.connection.execute_cypher(cypher, params, 'Create Relationship')
|
252
|
+
|
253
|
+
row = result.first
|
202
254
|
|
203
|
-
|
204
|
-
|
255
|
+
# Try different ways to access the ID
|
256
|
+
rid_sym = row && row[:rid]
|
257
|
+
rid_str = row && row['rid']
|
258
|
+
|
259
|
+
rid = rid_sym || rid_str
|
260
|
+
|
261
|
+
raise 'Relationship creation returned no id' if rid.nil?
|
205
262
|
|
206
263
|
self.internal_id = rid
|
207
264
|
self.class.instance_variable_set(:@last_internal_id, rid)
|
@@ -217,11 +274,14 @@ module ActiveCypher
|
|
217
274
|
changes = changes_to_save
|
218
275
|
return true if changes.empty?
|
219
276
|
|
277
|
+
adapter = self.class.connection.id_handler
|
278
|
+
|
220
279
|
cypher = <<~CYPHER
|
221
|
-
MATCH ()-[r]-() WHERE
|
280
|
+
MATCH ()-[r]-() WHERE #{adapter.with_direct_id(internal_id)}
|
222
281
|
SET r += $props
|
223
282
|
CYPHER
|
224
|
-
|
283
|
+
|
284
|
+
params = { props: changes }
|
225
285
|
|
226
286
|
self.class.connection.execute_cypher(cypher, params, 'Update Relationship')
|
227
287
|
changes_applied
|
data/lib/activecypher.rb
CHANGED
@@ -101,7 +101,10 @@ loader.ignore("#{__dir__}/active_cypher/railtie.rb")
|
|
101
101
|
loader.ignore("#{__dir__}/active_cypher/generators")
|
102
102
|
loader.ignore("#{__dir__}/activecypher.rb")
|
103
103
|
loader.ignore("#{__dir__}/cyrel.rb")
|
104
|
-
loader.inflector.inflect(
|
104
|
+
loader.inflector.inflect(
|
105
|
+
'activecypher' => 'ActiveCypher',
|
106
|
+
'dsl_context' => 'DSLContext'
|
107
|
+
)
|
105
108
|
loader.push_dir("#{__dir__}/cyrel", namespace: Cyrel)
|
106
109
|
loader.setup
|
107
110
|
|
data/lib/cyrel/clause/set.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'cyrel/expression'
|
4
|
+
require 'cyrel/expression/property_access'
|
5
|
+
|
3
6
|
module Cyrel
|
4
7
|
module Clause
|
5
8
|
# Represents a SET clause in a Cypher query.
|
@@ -12,6 +15,7 @@ module Cyrel
|
|
12
15
|
# - Hash: { variable_or_prop_access => value_expression, ... }
|
13
16
|
# e.g., { Cyrel.prop(:n, :name) => "New Name", Cyrel.prop(:r, :weight) => 10 }
|
14
17
|
# e.g., { n: { name: "New Name", age: 30 } } # For SET n = properties or n += properties
|
18
|
+
# e.g., { Cyrel.plus(:n) => { name: "New Name" } } # For SET n += { name: ... }
|
15
19
|
# - Array: [[variable, label_string], ...] # For SET n:Label
|
16
20
|
# e.g., [[:n, "NewLabel"], [:m, "AnotherLabel"]]
|
17
21
|
# Note: Mixing hash and array styles in one call is not directly supported, use multiple SET clauses if needed.
|
@@ -47,17 +51,20 @@ module Cyrel
|
|
47
51
|
case assignments
|
48
52
|
when Hash
|
49
53
|
assignments.flat_map do |key, value|
|
50
|
-
|
54
|
+
case key
|
55
|
+
when Expression::PropertyAccess
|
51
56
|
# SET n.prop = value
|
52
57
|
[[:property, key, Expression.coerce(value)]]
|
53
|
-
|
54
|
-
# SET n = properties
|
55
|
-
# We need to decide which operator (= or +=). Defaulting to = for now.
|
56
|
-
# User might need to specify via a different method/option.
|
57
|
-
# Let's assume the value is a hash for this case.
|
58
|
+
when Symbol, String
|
59
|
+
# SET n = properties
|
58
60
|
raise ArgumentError, 'Value for variable assignment must be a Hash (for SET n = {props})' unless value.is_a?(Hash)
|
59
61
|
|
60
|
-
[[:variable_properties, key.to_sym, Expression.coerce(value)]]
|
62
|
+
[[:variable_properties, key.to_sym, Expression.coerce(value), :assign]]
|
63
|
+
when Cyrel::Plus
|
64
|
+
# SET n += properties
|
65
|
+
raise ArgumentError, 'Value for variable assignment must be a Hash (for SET n += {props})' unless value.is_a?(Hash)
|
66
|
+
|
67
|
+
[[:variable_properties, key.variable.to_sym, Expression.coerce(value), :merge]]
|
61
68
|
else
|
62
69
|
raise ArgumentError, "Invalid key type in SET assignments hash: #{key.class}"
|
63
70
|
end
|
@@ -78,15 +85,18 @@ module Cyrel
|
|
78
85
|
end
|
79
86
|
|
80
87
|
def render_assignment(assignment, query)
|
81
|
-
type, target, value = assignment
|
88
|
+
type, target, value, op = assignment
|
82
89
|
case type
|
83
90
|
when :property
|
84
91
|
# target is PropertyAccess, value is Expression
|
85
92
|
"#{target.render(query)} = #{value.render(query)}"
|
86
93
|
when :variable_properties
|
87
94
|
# target is variable symbol, value is Expression (Literal Hash)
|
88
|
-
|
89
|
-
|
95
|
+
if op == :merge
|
96
|
+
"#{target} += #{value.render(query)}"
|
97
|
+
else
|
98
|
+
"#{target} = #{value.render(query)}"
|
99
|
+
end
|
90
100
|
when :label
|
91
101
|
# target is variable symbol, value is label string
|
92
102
|
"#{target}:#{value}" # Labels are not parameterized
|
data/lib/cyrel/functions.rb
CHANGED
@@ -22,7 +22,9 @@ module Cyrel
|
|
22
22
|
Expression::FunctionCall.new(:elementId, Clause::Return::RawIdentifier.new(node_variable.to_s))
|
23
23
|
end
|
24
24
|
|
25
|
-
|
25
|
+
def id(node_variable)
|
26
|
+
Expression::FunctionCall.new(:id, Clause::Return::RawIdentifier.new(node_variable.to_s))
|
27
|
+
end
|
26
28
|
|
27
29
|
# Because apparently, COUNT(*) isn’t obvious enough.
|
28
30
|
# Handles the 'give me everything and make it snappy' use case.
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'active_support/tagged_logging'
|
5
|
+
|
6
|
+
module Cyrel
|
7
|
+
# Basic logging support for Cyrel.
|
8
|
+
# Debug logging is disabled by default.
|
9
|
+
module Logging
|
10
|
+
LOG_TAG = 'Cyrel'
|
11
|
+
|
12
|
+
class << self
|
13
|
+
# @return [Logger] the configured logger
|
14
|
+
attr_accessor :backend
|
15
|
+
|
16
|
+
def resolve_log_level(log_level_str)
|
17
|
+
Logger.const_get(log_level_str.upcase)
|
18
|
+
rescue StandardError
|
19
|
+
Logger::UNKNOWN
|
20
|
+
end
|
21
|
+
|
22
|
+
def logger
|
23
|
+
self.backend ||= begin
|
24
|
+
log_level = ENV.fetch('CYREL_LOG_LEVEL', 'unknown')
|
25
|
+
logger_base = Logger.new($stdout)
|
26
|
+
logger_base.level = resolve_log_level(log_level)
|
27
|
+
|
28
|
+
# Return a TaggedLogging instance without calling 'tagged!'
|
29
|
+
ActiveSupport::TaggedLogging.new(logger_base)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def logger
|
35
|
+
Logging.logger.tagged(LOG_TAG)
|
36
|
+
end
|
37
|
+
|
38
|
+
def log_debug(msg) = logger.debug { msg }
|
39
|
+
def log_info(msg) = logger.info { msg }
|
40
|
+
def log_warn(msg) = logger.warn { msg }
|
41
|
+
def log_error(msg) = logger.error { msg }
|
42
|
+
end
|
43
|
+
end
|
data/lib/cyrel/plus.rb
ADDED
data/lib/cyrel/query.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
#
|
3
|
+
# Base DSL components
|
4
|
+
require 'cyrel/parameterizable'
|
5
|
+
require 'cyrel/logging'
|
4
6
|
|
5
7
|
# Require all clause types for DSL methods
|
6
8
|
|
@@ -18,6 +20,7 @@ module Cyrel
|
|
18
20
|
# # Manages clauses, parameters, and final query generation, because string interpolation is for amateurs.
|
19
21
|
class Query
|
20
22
|
include Parameterizable
|
23
|
+
include Logging
|
21
24
|
attr_reader :parameters, :clauses # Expose clauses for merge logic
|
22
25
|
|
23
26
|
def initialize
|
@@ -58,6 +61,9 @@ module Cyrel
|
|
58
61
|
.reject(&:blank?)
|
59
62
|
.join("\n")
|
60
63
|
|
64
|
+
log_debug("QUERY: #{cypher_string}")
|
65
|
+
log_debug("PARAMS: #{@parameters.inspect}") unless @parameters.empty?
|
66
|
+
|
61
67
|
[cypher_string, @parameters]
|
62
68
|
end
|
63
69
|
end
|
data/lib/cyrel.rb
CHANGED
@@ -1,72 +1,131 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'irb' # Required for binding.irb
|
4
|
-
|
5
3
|
module Cyrel
|
6
4
|
module_function
|
7
5
|
|
8
|
-
#
|
6
|
+
# Cyrel DSL helper: creates a CALL clause for a procedure.
|
7
|
+
# Example: Cyrel.call('db.labels')
|
9
8
|
def call(procedure)
|
10
9
|
CallProcedure.new(procedure)
|
11
10
|
end
|
12
11
|
|
12
|
+
# Cyrel DSL helper: creates a RETURN clause.
|
13
|
+
# Example: Cyrel.return(name: :n)
|
13
14
|
def return(**return_values)
|
14
15
|
ReturnOnly.new(return_values)
|
15
16
|
end
|
16
|
-
# Now make all defined instance methods module functions
|
17
17
|
|
18
|
-
#
|
18
|
+
# Cyrel DSL helper: creates a node pattern.
|
19
|
+
# Example: Cyrel.node(:n, labels: ['Person'], properties: {name: 'Alice'})
|
19
20
|
def node(alias_name, labels: [], properties: {})
|
20
21
|
Pattern::Node.new(alias_name, labels: labels, properties: properties)
|
21
22
|
end
|
22
|
-
# Add helpers for Relationship, Path if needed
|
23
23
|
|
24
|
-
#
|
24
|
+
# Cyrel DSL helper: starts a CREATE query.
|
25
|
+
# Example: Cyrel.create(pattern)
|
25
26
|
def create(pattern)
|
26
|
-
Query.new.create(pattern)
|
27
|
+
Query.new.create(pattern)
|
27
28
|
end
|
28
29
|
|
30
|
+
# Cyrel DSL helper: starts a MATCH query.
|
31
|
+
# Example: Cyrel.match(pattern)
|
29
32
|
def match(pattern, path_variable: nil)
|
30
|
-
Query.new.match(pattern, path_variable: path_variable)
|
33
|
+
Query.new.match(pattern, path_variable: path_variable)
|
31
34
|
end
|
32
|
-
# Add helpers for merge etc. if desired as query starters
|
33
35
|
|
34
|
-
#
|
35
|
-
#
|
36
|
-
# Delegate to the correct module function
|
36
|
+
# Cyrel DSL helper: returns the element id of a node/relationship.
|
37
|
+
# Example: Cyrel.id(:n)
|
37
38
|
def id(...) = Functions.element_id(...)
|
39
|
+
|
40
|
+
# Cyrel DSL helper: returns the element id of a node/relationship (alias).
|
38
41
|
def element_id(...) = Functions.element_id(...)
|
42
|
+
|
43
|
+
# Cyrel DSL helper: Cypher count() aggregation.
|
44
|
+
# Example: Cyrel.count(:n)
|
39
45
|
def count(...) = Functions.count(...)
|
46
|
+
|
47
|
+
# Cyrel DSL helper: Cypher labels() function.
|
48
|
+
# Example: Cyrel.labels(:n)
|
40
49
|
def labels(...) = Functions.labels(...)
|
50
|
+
|
51
|
+
# Cyrel DSL helper: Cypher type() function.
|
52
|
+
# Example: Cyrel.type(:r)
|
41
53
|
def type(...) = Functions.type(...)
|
54
|
+
|
55
|
+
# Cyrel DSL helper: Cypher properties() function.
|
56
|
+
# Example: Cyrel.properties(:n)
|
42
57
|
def properties(...) = Functions.properties(...)
|
58
|
+
|
59
|
+
# Cyrel DSL helper: Cypher coalesce() function.
|
60
|
+
# Example: Cyrel.coalesce(:a, :b)
|
43
61
|
def coalesce(...) = Functions.coalesce(...)
|
62
|
+
|
63
|
+
# Cyrel DSL helper: Cypher timestamp() function.
|
64
|
+
# Example: Cyrel.timestamp
|
44
65
|
def timestamp(...) = Functions.timestamp(...)
|
66
|
+
|
67
|
+
# Cyrel DSL helper: Cypher toString() function.
|
68
|
+
# Example: Cyrel.to_string(:n)
|
45
69
|
def to_string(...) = Functions.to_string(...)
|
70
|
+
|
71
|
+
# Cyrel DSL helper: Cypher toInteger() function.
|
72
|
+
# Example: Cyrel.to_integer(:n)
|
46
73
|
def to_integer(...) = Functions.to_integer(...)
|
74
|
+
|
75
|
+
# Cyrel DSL helper: Cypher toFloat() function.
|
76
|
+
# Example: Cyrel.to_float(:n)
|
47
77
|
def to_float(...) = Functions.to_float(...)
|
78
|
+
|
79
|
+
# Cyrel DSL helper: Cypher toBoolean() function.
|
80
|
+
# Example: Cyrel.to_boolean(:n)
|
48
81
|
def to_boolean(...) = Functions.to_boolean(...)
|
82
|
+
|
83
|
+
# Cyrel DSL helper: Cypher sum() aggregation.
|
84
|
+
# Example: Cyrel.sum(:n)
|
49
85
|
def sum(...) = Functions.sum(...)
|
86
|
+
|
87
|
+
# Cyrel DSL helper: Cypher avg() aggregation.
|
88
|
+
# Example: Cyrel.avg(:n)
|
50
89
|
def avg(...) = Functions.avg(...)
|
90
|
+
|
91
|
+
# Cyrel DSL helper: Cypher min() aggregation.
|
92
|
+
# Example: Cyrel.min(:n)
|
51
93
|
def min(...) = Functions.min(...)
|
94
|
+
|
95
|
+
# Cyrel DSL helper: Cypher max() aggregation.
|
96
|
+
# Example: Cyrel.max(:n)
|
52
97
|
def max(...) = Functions.max(...)
|
98
|
+
|
99
|
+
# Cyrel DSL helper: Cypher collect() aggregation.
|
100
|
+
# Example: Cyrel.collect(:n)
|
53
101
|
def collect(...) = Functions.collect(...)
|
54
|
-
def size(...) = Functions.size(...)
|
55
102
|
|
56
|
-
#
|
103
|
+
# Cyrel DSL helper: Cypher size() function.
|
104
|
+
# Example: Cyrel.size(:n)
|
105
|
+
def size(...) = Functions.size(...)
|
57
106
|
|
58
|
-
#
|
107
|
+
# Cyrel DSL helper: creates a PropertyAccess expression.
|
108
|
+
# Example: Cyrel.prop(:n, :name)
|
59
109
|
def prop(variable, property_name)
|
60
110
|
Expression.prop(variable, property_name)
|
61
111
|
end
|
62
112
|
|
63
|
-
#
|
113
|
+
# Cyrel DSL helper: creates an Exists expression.
|
114
|
+
# Example: Cyrel.exists(pattern)
|
64
115
|
def exists(pattern)
|
65
116
|
Expression.exists(pattern)
|
66
117
|
end
|
67
118
|
|
68
|
-
#
|
119
|
+
# Cyrel DSL helper: creates a Logical NOT expression.
|
120
|
+
# Example: Cyrel.not(expression)
|
69
121
|
def not(expression)
|
70
122
|
Expression.not(expression)
|
71
123
|
end
|
124
|
+
|
125
|
+
# Cyrel DSL helper: property merging (SET n += {props}).
|
126
|
+
# Use for updating only specified properties on a node or relationship.
|
127
|
+
# Example: Cyrel.plus(:n) => { name: "Alice" } generates SET n += $p1
|
128
|
+
def plus(variable)
|
129
|
+
Plus.new(variable)
|
130
|
+
end
|
72
131
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activecypher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Abdelkader Boudih
|
@@ -119,11 +119,18 @@ files:
|
|
119
119
|
- lib/active_cypher/connection_adapters/abstract_bolt_adapter.rb
|
120
120
|
- lib/active_cypher/connection_adapters/memgraph_adapter.rb
|
121
121
|
- lib/active_cypher/connection_adapters/neo4j_adapter.rb
|
122
|
-
- lib/active_cypher/
|
122
|
+
- lib/active_cypher/connection_adapters/registry.rb
|
123
123
|
- lib/active_cypher/connection_handler.rb
|
124
124
|
- lib/active_cypher/connection_pool.rb
|
125
125
|
- lib/active_cypher/connection_url_resolver.rb
|
126
126
|
- lib/active_cypher/cypher_config.rb
|
127
|
+
- lib/active_cypher/fixtures.rb
|
128
|
+
- lib/active_cypher/fixtures/dsl_context.rb
|
129
|
+
- lib/active_cypher/fixtures/evaluator.rb
|
130
|
+
- lib/active_cypher/fixtures/node_builder.rb
|
131
|
+
- lib/active_cypher/fixtures/parser.rb
|
132
|
+
- lib/active_cypher/fixtures/registry.rb
|
133
|
+
- lib/active_cypher/fixtures/rel_builder.rb
|
127
134
|
- lib/active_cypher/generators/install_generator.rb
|
128
135
|
- lib/active_cypher/generators/node_generator.rb
|
129
136
|
- lib/active_cypher/generators/relationship_generator.rb
|
@@ -132,6 +139,7 @@ files:
|
|
132
139
|
- lib/active_cypher/generators/templates/cypher_databases.yml
|
133
140
|
- lib/active_cypher/generators/templates/node.rb.erb
|
134
141
|
- lib/active_cypher/generators/templates/relationship.rb.erb
|
142
|
+
- lib/active_cypher/instrumentation.rb
|
135
143
|
- lib/active_cypher/logging.rb
|
136
144
|
- lib/active_cypher/model/abstract.rb
|
137
145
|
- lib/active_cypher/model/attributes.rb
|
@@ -142,6 +150,7 @@ files:
|
|
142
150
|
- lib/active_cypher/model/countable.rb
|
143
151
|
- lib/active_cypher/model/destruction.rb
|
144
152
|
- lib/active_cypher/model/inspectable.rb
|
153
|
+
- lib/active_cypher/model/labelling.rb
|
145
154
|
- lib/active_cypher/model/persistence.rb
|
146
155
|
- lib/active_cypher/model/querying.rb
|
147
156
|
- lib/active_cypher/railtie.rb
|
@@ -183,12 +192,14 @@ files:
|
|
183
192
|
- lib/cyrel/expression/pattern_comprehension.rb
|
184
193
|
- lib/cyrel/expression/property_access.rb
|
185
194
|
- lib/cyrel/functions.rb
|
195
|
+
- lib/cyrel/logging.rb
|
186
196
|
- lib/cyrel/node.rb
|
187
197
|
- lib/cyrel/parameterizable.rb
|
188
198
|
- lib/cyrel/pattern.rb
|
189
199
|
- lib/cyrel/pattern/node.rb
|
190
200
|
- lib/cyrel/pattern/path.rb
|
191
201
|
- lib/cyrel/pattern/relationship.rb
|
202
|
+
- lib/cyrel/plus.rb
|
192
203
|
- lib/cyrel/query.rb
|
193
204
|
- lib/cyrel/return_only.rb
|
194
205
|
- lib/cyrel/types/hash_type.rb
|
@@ -1,130 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ActiveCypher
|
4
|
-
# ConnectionFactory provides a simple API for creating connections to Cypher databases
|
5
|
-
# using the ConnectionUrlResolver to parse database URLs.
|
6
|
-
#
|
7
|
-
# This factory simplifies the process of creating database connections by:
|
8
|
-
# 1. Parsing connection URLs with ConnectionUrlResolver
|
9
|
-
# 2. Creating the appropriate adapter based on the URL
|
10
|
-
# 3. Establishing and configuring the connection with the right security settings
|
11
|
-
#
|
12
|
-
# Example:
|
13
|
-
# factory = ConnectionFactory.new("neo4j://user:pass@localhost:7687")
|
14
|
-
# driver = factory.create_driver
|
15
|
-
#
|
16
|
-
# driver.with_session do |session|
|
17
|
-
# result = session.run("RETURN 'Connected!' AS message")
|
18
|
-
# puts result.first[:message]
|
19
|
-
# end
|
20
|
-
class ConnectionFactory
|
21
|
-
# Initialize with a database URL
|
22
|
-
# @param url [String] A database connection URL
|
23
|
-
# @param options [Hash] Additional options for the connection
|
24
|
-
def initialize(url, options = {})
|
25
|
-
@url = url
|
26
|
-
@options = options
|
27
|
-
@config = resolve_url(url)
|
28
|
-
end
|
29
|
-
|
30
|
-
# Create a Bolt driver based on the parsed URL
|
31
|
-
# @param pool_size [Integer] Size of the connection pool
|
32
|
-
# @return [ActiveCypher::Bolt::Driver, nil] The configured driver or nil if URL is invalid
|
33
|
-
def create_driver(pool_size: 5)
|
34
|
-
return nil unless @config
|
35
|
-
|
36
|
-
# Create the adapter based on the resolved configuration
|
37
|
-
adapter = create_adapter
|
38
|
-
return nil unless adapter
|
39
|
-
|
40
|
-
# Create and configure the Bolt driver
|
41
|
-
uri = build_uri
|
42
|
-
auth_token = build_auth_token
|
43
|
-
|
44
|
-
ActiveCypher::Bolt::Driver.new(
|
45
|
-
uri: uri,
|
46
|
-
adapter: adapter,
|
47
|
-
auth_token: auth_token,
|
48
|
-
pool_size: pool_size
|
49
|
-
)
|
50
|
-
end
|
51
|
-
|
52
|
-
# Get the parsed configuration
|
53
|
-
# @return [Hash, nil] The parsed configuration or nil if URL is invalid
|
54
|
-
attr_reader :config
|
55
|
-
|
56
|
-
# Verify if the URL is valid and supported
|
57
|
-
# @return [Boolean] True if the URL is valid and supported
|
58
|
-
def valid?
|
59
|
-
!@config.nil?
|
60
|
-
end
|
61
|
-
|
62
|
-
private
|
63
|
-
|
64
|
-
# Resolve the URL into a configuration hash
|
65
|
-
def resolve_url(url)
|
66
|
-
resolver = ConnectionUrlResolver.new(url)
|
67
|
-
resolver.to_hash
|
68
|
-
end
|
69
|
-
|
70
|
-
# Create the appropriate adapter based on the parsed URL
|
71
|
-
def create_adapter
|
72
|
-
case @config[:adapter]
|
73
|
-
when 'neo4j'
|
74
|
-
create_neo4j_adapter
|
75
|
-
when 'memgraph'
|
76
|
-
create_memgraph_adapter
|
77
|
-
else
|
78
|
-
nil
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
# Create a Neo4j adapter
|
83
|
-
def create_neo4j_adapter
|
84
|
-
ConnectionAdapters::Neo4jAdapter.new({
|
85
|
-
uri: build_uri,
|
86
|
-
username: @config[:username],
|
87
|
-
password: @config[:password],
|
88
|
-
database: @config[:database]
|
89
|
-
})
|
90
|
-
end
|
91
|
-
|
92
|
-
# Create a Memgraph adapter
|
93
|
-
def create_memgraph_adapter
|
94
|
-
ConnectionAdapters::MemgraphAdapter.new({
|
95
|
-
uri: build_uri,
|
96
|
-
username: @config[:username],
|
97
|
-
password: @config[:password],
|
98
|
-
database: @config[:database]
|
99
|
-
})
|
100
|
-
rescue NameError
|
101
|
-
# Fall back to Neo4j adapter if Memgraph adapter is not available
|
102
|
-
ConnectionAdapters::Neo4jAdapter.new({
|
103
|
-
uri: build_uri,
|
104
|
-
username: @config[:username],
|
105
|
-
password: @config[:password],
|
106
|
-
database: @config[:database]
|
107
|
-
})
|
108
|
-
end
|
109
|
-
|
110
|
-
# Build the URI string with the appropriate scheme based on SSL settings
|
111
|
-
def build_uri
|
112
|
-
scheme = if @config[:ssl]
|
113
|
-
@config[:ssc] ? 'bolt+ssc' : 'bolt+s'
|
114
|
-
else
|
115
|
-
'bolt'
|
116
|
-
end
|
117
|
-
|
118
|
-
"#{scheme}://#{@config[:host]}:#{@config[:port]}"
|
119
|
-
end
|
120
|
-
|
121
|
-
# Build the authentication token for the Bolt driver
|
122
|
-
def build_auth_token
|
123
|
-
{
|
124
|
-
scheme: 'basic',
|
125
|
-
principal: @config[:username],
|
126
|
-
credentials: @config[:password]
|
127
|
-
}
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|