activecypher 0.11.0 → 0.11.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.
- checksums.yaml +4 -4
- data/lib/active_cypher/base.rb +18 -1
- data/lib/active_cypher/bolt/connection.rb +1 -1
- data/lib/active_cypher/bolt/driver.rb +3 -17
- data/lib/active_cypher/bolt/session.rb +10 -9
- data/lib/active_cypher/bolt/transaction.rb +3 -10
- data/lib/active_cypher/connection_adapters/abstract_adapter.rb +0 -28
- data/lib/active_cypher/connection_adapters/abstract_bolt_adapter.rb +2 -1
- data/lib/active_cypher/connection_adapters/memgraph_adapter.rb +1 -2
- data/lib/active_cypher/connection_adapters/neo4j_adapter.rb +1 -2
- data/lib/active_cypher/connection_pool.rb +11 -14
- data/lib/active_cypher/fixtures.rb +9 -21
- data/lib/active_cypher/instrumentation.rb +1 -1
- data/lib/active_cypher/logging.rb +1 -5
- data/lib/active_cypher/migrator.rb +0 -2
- data/lib/active_cypher/model/connection_owner.rb +2 -1
- data/lib/active_cypher/relationship.rb +9 -11
- data/lib/active_cypher/version.rb +1 -1
- data/lib/cyrel/ast/clause_adapter.rb +4 -10
- data/lib/cyrel/ast/compiler.rb +5 -18
- data/lib/cyrel/logging.rb +0 -2
- data/lib/cyrel/query.rb +1 -0
- metadata +2 -4
- data/lib/active_cypher/model/inspectable.rb +0 -28
- data/lib/cyrel/ast/simple_cache.rb +0 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a39f60243be9073a1c3e2f05e1239239de9412d95184a321d22c4f582b0cdbdb
|
4
|
+
data.tar.gz: f125c98717432f24b3e0e28e4066f962cc2cb46bbab556bda52a44c3f4f12e7c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3bea961b864932446e0c667c7f88c9a428204f62089696b65d32dc62c4297e10e7d86e1f2ce67db8eb32e3a771944d882840b5d416345eed3296a3b23dec1d09
|
7
|
+
data.tar.gz: 1803663082f2fa06c7af69af355d0bd05ad78901c217c8b5f2aa2486b3adc009a6784d11b09731b1bd88d0b533e12420c13b11254b4b0852de5636b1e0d530f3
|
data/lib/active_cypher/base.rb
CHANGED
@@ -34,7 +34,6 @@ module ActiveCypher
|
|
34
34
|
include Model::Persistence
|
35
35
|
include Model::Destruction
|
36
36
|
include Model::Countable
|
37
|
-
include Model::Inspectable
|
38
37
|
|
39
38
|
class << self
|
40
39
|
# Attempts to retrieve a connection from the handler.
|
@@ -61,6 +60,24 @@ module ActiveCypher
|
|
61
60
|
end
|
62
61
|
end
|
63
62
|
|
63
|
+
# Custom object inspection method for pretty-printing a compact,
|
64
|
+
# single-line summary of the object. Output examples:
|
65
|
+
#
|
66
|
+
# #<UserNode id="26" name="Alice" age=34> => persisted object
|
67
|
+
# #<UserNode (new) name="Bob"> => object not yet saved
|
68
|
+
#
|
69
|
+
def inspect
|
70
|
+
# Put 'internal_id' first like it's the main character (even if it's nil)
|
71
|
+
ordered = attributes.dup
|
72
|
+
ordered = ordered.slice('internal_id').merge(ordered.except('internal_id'))
|
73
|
+
|
74
|
+
# Turn each attr into "key: value" because we humans fear raw hashes
|
75
|
+
parts = ordered.map { |k, v| "#{k}: #{v.inspect}" }
|
76
|
+
|
77
|
+
# Wrap it all up in a fake-sane object string, so you can pretend your data is organized.
|
78
|
+
"#<#{self.class} #{parts.join(', ')}>"
|
79
|
+
end
|
80
|
+
|
64
81
|
# Because Rails needs to feel included, too.
|
65
82
|
ActiveSupport.run_load_hooks(:active_cypher, self)
|
66
83
|
end
|
@@ -47,11 +47,7 @@ module ActiveCypher
|
|
47
47
|
# Check if connection is viable before using it
|
48
48
|
unless conn.viable?
|
49
49
|
# Create a fresh connection, because hope springs eternal
|
50
|
-
|
51
|
-
conn.close
|
52
|
-
rescue StandardError
|
53
|
-
nil
|
54
|
-
end
|
50
|
+
conn.close
|
55
51
|
conn = build_connection
|
56
52
|
end
|
57
53
|
|
@@ -64,11 +60,7 @@ module ActiveCypher
|
|
64
60
|
# Check if connection is viable before using it
|
65
61
|
unless conn.viable?
|
66
62
|
# Create a fresh connection, because why not
|
67
|
-
|
68
|
-
conn.close
|
69
|
-
rescue StandardError
|
70
|
-
nil
|
71
|
-
end
|
63
|
+
conn.close
|
72
64
|
conn = build_connection
|
73
65
|
end
|
74
66
|
|
@@ -88,8 +80,6 @@ module ActiveCypher
|
|
88
80
|
def verify_connectivity
|
89
81
|
with_session { |s| s.run('RETURN 1') }
|
90
82
|
true
|
91
|
-
rescue StandardError
|
92
|
-
false
|
93
83
|
end
|
94
84
|
|
95
85
|
# Closes the connection pool. Because sometimes you just need to let go.
|
@@ -119,11 +109,7 @@ module ActiveCypher
|
|
119
109
|
begin
|
120
110
|
connection.connect
|
121
111
|
rescue StandardError => e
|
122
|
-
|
123
|
-
connection.close
|
124
|
-
rescue StandardError
|
125
|
-
nil
|
126
|
-
end
|
112
|
+
connection.close
|
127
113
|
raise e
|
128
114
|
end
|
129
115
|
|
@@ -7,6 +7,7 @@ module ActiveCypher
|
|
7
7
|
# It maintains a connection to the database server and allows running queries.
|
8
8
|
class Session
|
9
9
|
include Instrumentation
|
10
|
+
|
10
11
|
attr_reader :connection, :database
|
11
12
|
|
12
13
|
def initialize(connection, database: nil)
|
@@ -119,15 +120,15 @@ module ActiveCypher
|
|
119
120
|
# @param metadata [Hash] Transaction metadata to send to the server.
|
120
121
|
# @yield [tx] The transaction to use for queries.
|
121
122
|
# @return The result of the block.
|
122
|
-
def run_transaction(mode = :write, db: nil, timeout: nil, metadata: nil, &
|
123
|
+
def run_transaction(mode = :write, db: nil, timeout: nil, metadata: nil, &)
|
123
124
|
if Async::Task.current?
|
124
125
|
# Already in an async context, just run the block.
|
125
126
|
# The block will run asynchronously within the current task.
|
126
|
-
_execute_transaction_block(mode, db, timeout, metadata, &
|
127
|
+
_execute_transaction_block(mode, db, timeout, metadata, &)
|
127
128
|
else
|
128
129
|
# Not in an async context, so we need to create one and wait for it to complete.
|
129
130
|
Async do
|
130
|
-
_execute_transaction_block(mode, db, timeout, metadata, &
|
131
|
+
_execute_transaction_block(mode, db, timeout, metadata, &)
|
131
132
|
end.wait
|
132
133
|
end
|
133
134
|
end
|
@@ -141,12 +142,12 @@ module ActiveCypher
|
|
141
142
|
# @param metadata [Hash] Transaction metadata to send to the server.
|
142
143
|
# @yield [tx] The transaction to use for queries.
|
143
144
|
# @return [Async::Task] A task that will complete with the result of the block.
|
144
|
-
def async_run_transaction(mode = :write, db: nil, timeout: nil, metadata: nil, &
|
145
|
+
def async_run_transaction(mode = :write, db: nil, timeout: nil, metadata: nil, &)
|
145
146
|
# Ensure we are in an async task, otherwise the behavior is undefined.
|
146
147
|
raise 'Cannot run an async transaction outside of an Async task' unless Async::Task.current?
|
147
148
|
|
148
149
|
Async do
|
149
|
-
_execute_transaction_block(mode, db, timeout, metadata, &
|
150
|
+
_execute_transaction_block(mode, db, timeout, metadata, &)
|
150
151
|
end
|
151
152
|
end
|
152
153
|
|
@@ -158,12 +159,12 @@ module ActiveCypher
|
|
158
159
|
run_transaction(:read, db: db, timeout: timeout, metadata: metadata, &)
|
159
160
|
end
|
160
161
|
|
161
|
-
def async_write_transaction(db: nil, timeout: nil, metadata: nil, &
|
162
|
-
async_run_transaction(:write, db: db, timeout: timeout, metadata: metadata, &
|
162
|
+
def async_write_transaction(db: nil, timeout: nil, metadata: nil, &)
|
163
|
+
async_run_transaction(:write, db: db, timeout: timeout, metadata: metadata, &)
|
163
164
|
end
|
164
165
|
|
165
|
-
def async_read_transaction(db: nil, timeout: nil, metadata: nil, &
|
166
|
-
async_run_transaction(:read, db: db, timeout: timeout, metadata: metadata, &
|
166
|
+
def async_read_transaction(db: nil, timeout: nil, metadata: nil, &)
|
167
|
+
async_run_transaction(:read, db: db, timeout: timeout, metadata: metadata, &)
|
167
168
|
end
|
168
169
|
|
169
170
|
# Close the session and any active transaction.
|
@@ -5,6 +5,7 @@ module ActiveCypher
|
|
5
5
|
# Manages transaction state (BEGIN/COMMIT/ROLLBACK) and runs queries within a transaction.
|
6
6
|
class Transaction
|
7
7
|
include Instrumentation
|
8
|
+
|
8
9
|
attr_reader :bookmarks, :metadata, :connection
|
9
10
|
|
10
11
|
# Initializes a new Transaction instance.
|
@@ -147,11 +148,7 @@ module ActiveCypher
|
|
147
148
|
rescue ConnectionError
|
148
149
|
@state = :failed
|
149
150
|
# Mark transaction as completed in the session
|
150
|
-
|
151
|
-
@session.complete_transaction(self)
|
152
|
-
rescue StandardError
|
153
|
-
nil
|
154
|
-
end
|
151
|
+
@session.complete_transaction(self)
|
155
152
|
raise
|
156
153
|
end
|
157
154
|
|
@@ -184,11 +181,7 @@ module ActiveCypher
|
|
184
181
|
ensure
|
185
182
|
# Always mark as rolled back and complete the transaction
|
186
183
|
@state = :rolled_back
|
187
|
-
|
188
|
-
@session.complete_transaction(self)
|
189
|
-
rescue StandardError
|
190
|
-
nil
|
191
|
-
end
|
184
|
+
@session.complete_transaction(self)
|
192
185
|
end
|
193
186
|
end
|
194
187
|
|
@@ -57,34 +57,6 @@ module ActiveCypher
|
|
57
57
|
raise NotImplementedError, "#{self.class} must implement #hydrate_record"
|
58
58
|
end
|
59
59
|
|
60
|
-
# Get current adapter type for ID handling
|
61
|
-
# Helper for generating ID-related Cypher functions that are database-specific
|
62
|
-
module CypherFunction
|
63
|
-
# Generate ID equality clause with the ID value embedded in the query for Memgraph
|
64
|
-
def self.id_equals(var, id_value, adapter)
|
65
|
-
func = id_function(adapter)
|
66
|
-
"#{func}(#{var}) = #{id_value}"
|
67
|
-
end
|
68
|
-
|
69
|
-
# Generate ID equality clause using a parameterized ID value for Neo4j
|
70
|
-
def self.id_equals_param(var, param_name, adapter)
|
71
|
-
func = id_function(adapter)
|
72
|
-
"#{func}(#{var}) = $#{param_name}"
|
73
|
-
end
|
74
|
-
|
75
|
-
# Generate a node variable with ID predicate
|
76
|
-
def self.node_with_id(node_var, id_value, adapter)
|
77
|
-
func = id_function(adapter)
|
78
|
-
"#{func}(#{node_var}) = #{id_value}"
|
79
|
-
end
|
80
|
-
|
81
|
-
# Return ID expression
|
82
|
-
def self.return_id(var, as_name, adapter)
|
83
|
-
func = id_function(adapter)
|
84
|
-
"#{func}(#{var}) AS #{as_name}"
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
60
|
# Turns rows into symbols, because Rubyists fear strings.
|
89
61
|
# @param rows [Array<Hash>] The rows to process
|
90
62
|
# @return [Array<Hash>] The processed rows
|
@@ -10,6 +10,7 @@ module ActiveCypher
|
|
10
10
|
# It's like ActiveRecord::ConnectionAdapter, but for weirdos like me who use graph databases.
|
11
11
|
class AbstractBoltAdapter < AbstractAdapter
|
12
12
|
include Instrumentation
|
13
|
+
|
13
14
|
attr_reader :connection
|
14
15
|
|
15
16
|
# Returns the raw Bolt connection object
|
@@ -71,7 +72,7 @@ module ActiveCypher
|
|
71
72
|
# Clean disconnection. Resets the internal state.
|
72
73
|
def disconnect
|
73
74
|
instrument_connection(:disconnect) do
|
74
|
-
@connection
|
75
|
+
@connection.close if @connection
|
75
76
|
@connection = nil
|
76
77
|
true
|
77
78
|
end
|
@@ -11,8 +11,6 @@ module ActiveCypher
|
|
11
11
|
def schema_catalog
|
12
12
|
rows = run('SHOW SCHEMA')
|
13
13
|
parse_schema(rows)
|
14
|
-
rescue StandardError
|
15
|
-
introspect_fallback
|
16
14
|
end
|
17
15
|
|
18
16
|
# Use id() for Memgraph instead of elementId()
|
@@ -198,6 +196,7 @@ module ActiveCypher
|
|
198
196
|
|
199
197
|
module Persistence
|
200
198
|
include PersistenceMethods
|
199
|
+
|
201
200
|
module_function :create_record, :update_record, :destroy_record
|
202
201
|
end
|
203
202
|
|
@@ -33,8 +33,6 @@ module ActiveCypher
|
|
33
33
|
|
34
34
|
Schema::Catalog.new(indexes: idx_defs, constraints: con_defs,
|
35
35
|
node_types: [], edge_types: [])
|
36
|
-
rescue StandardError
|
37
|
-
Schema::Catalog.new(indexes: [], constraints: [], node_types: [], edge_types: [])
|
38
36
|
end
|
39
37
|
|
40
38
|
# Use elementId() for Neo4j
|
@@ -156,6 +154,7 @@ module ActiveCypher
|
|
156
154
|
|
157
155
|
module Persistence
|
158
156
|
include PersistenceMethods
|
157
|
+
|
159
158
|
module_function :create_record, :update_record, :destroy_record
|
160
159
|
end
|
161
160
|
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'concurrent/atomic/atomic_reference'
|
4
3
|
require 'timeout'
|
5
4
|
|
6
5
|
module ActiveCypher
|
@@ -24,29 +23,27 @@ module ActiveCypher
|
|
24
23
|
|
25
24
|
# Merge the resolved config with any additional options
|
26
25
|
@spec = resolved_config.merge(@spec.except(:url))
|
27
|
-
|
28
26
|
end
|
29
27
|
|
30
|
-
@conn_ref =
|
28
|
+
@conn_ref = nil # holds the adapter instance
|
31
29
|
@creation_mutex = Mutex.new # prevents multiple threads from creating connections simultaneously
|
32
|
-
@retry_count = Concurrent::AtomicReference.new(0)
|
33
30
|
end
|
34
31
|
|
35
|
-
# Returns a live adapter,
|
32
|
+
# Returns a live adapter, initializing it once in a thread‑safe way.
|
36
33
|
def connection
|
37
|
-
# Fast path —already connected and alive
|
38
|
-
conn = @conn_ref
|
34
|
+
# Fast path — already connected and alive
|
35
|
+
conn = @conn_ref
|
39
36
|
return conn if conn&.active?
|
40
37
|
|
41
38
|
# Use mutex for the slow path to prevent thundering herd
|
42
39
|
@creation_mutex.synchronize do
|
43
40
|
# Check again inside the mutex in case another thread created it
|
44
|
-
conn = @conn_ref
|
41
|
+
conn = @conn_ref
|
45
42
|
return conn if conn&.active?
|
46
43
|
|
47
44
|
# Create a new connection
|
48
45
|
new_conn = build_connection
|
49
|
-
@conn_ref
|
46
|
+
@conn_ref = new_conn
|
50
47
|
return new_conn
|
51
48
|
end
|
52
49
|
end
|
@@ -54,12 +51,12 @@ module ActiveCypher
|
|
54
51
|
|
55
52
|
# Check if the pool has a persistent connection issue
|
56
53
|
def troubled?
|
57
|
-
@retry_count
|
54
|
+
@retry_count >= @spec[:max_retries]
|
58
55
|
end
|
59
56
|
|
60
57
|
# Explicitly close and reset the connection
|
61
58
|
def disconnect
|
62
|
-
conn = @conn_ref
|
59
|
+
conn = @conn_ref
|
63
60
|
return unless conn
|
64
61
|
|
65
62
|
begin
|
@@ -68,7 +65,7 @@ module ActiveCypher
|
|
68
65
|
# Log but don't raise to ensure cleanup continues
|
69
66
|
puts "Warning: Error disconnecting: #{e.message}" if ENV['DEBUG']
|
70
67
|
ensure
|
71
|
-
@conn_ref
|
68
|
+
@conn_ref = nil
|
72
69
|
end
|
73
70
|
end
|
74
71
|
|
@@ -91,8 +88,8 @@ module ActiveCypher
|
|
91
88
|
rescue Timeout::Error
|
92
89
|
begin
|
93
90
|
adapter.disconnect
|
94
|
-
rescue StandardError
|
95
|
-
|
91
|
+
rescue StandardError => e
|
92
|
+
puts "Warning: Error disconnecting during timeout cleanup: #{e.message}" if ENV['DEBUG']
|
96
93
|
end
|
97
94
|
raise ConnectionError, "Connection timed out after #{@spec[:pool_timeout]} seconds"
|
98
95
|
end
|
@@ -32,11 +32,7 @@ module ActiveCypher
|
|
32
32
|
|
33
33
|
# 5. Gather unique connections for all model classes referenced in this profile
|
34
34
|
model_classes = dsl_context.nodes.map { |node| node[:model_class] }.uniq
|
35
|
-
connections = model_classes.map
|
36
|
-
klass.connection
|
37
|
-
rescue StandardError
|
38
|
-
nil
|
39
|
-
end.compact.uniq
|
35
|
+
connections = model_classes.map(&:connection).compact.uniq
|
40
36
|
|
41
37
|
# 6. Wipe all nodes in each relevant connection
|
42
38
|
connections.each do |conn|
|
@@ -78,11 +74,7 @@ module ActiveCypher
|
|
78
74
|
end
|
79
75
|
|
80
76
|
# Gather unique connections from all model classes
|
81
|
-
connections = model_classes.map
|
82
|
-
klass.connection
|
83
|
-
rescue StandardError
|
84
|
-
nil
|
85
|
-
end.compact.uniq
|
77
|
+
connections = model_classes.map(&:connection).compact.uniq
|
86
78
|
|
87
79
|
# Wipe all nodes in each connection
|
88
80
|
connections.each do |conn|
|
@@ -104,17 +96,13 @@ module ActiveCypher
|
|
104
96
|
next unless klass < ActiveCypher::Base
|
105
97
|
next if klass.respond_to?(:abstract_class?) && klass.abstract_class?
|
106
98
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
}
|
115
|
-
rescue StandardError
|
116
|
-
# Skip if can't get connection
|
117
|
-
end
|
99
|
+
conn = klass.connection
|
100
|
+
# Store connection details for comparison
|
101
|
+
model_connections[klass] = {
|
102
|
+
adapter: conn.class.name,
|
103
|
+
config: conn.instance_variable_get(:@config),
|
104
|
+
object_id: conn.object_id
|
105
|
+
}
|
118
106
|
end
|
119
107
|
|
120
108
|
relationships.each do |rel|
|
@@ -132,7 +132,7 @@ module ActiveCypher
|
|
132
132
|
# @param key [String, Symbol] The key to check
|
133
133
|
# @return [Boolean] True if the key contains sensitive information
|
134
134
|
def sensitive_key?(key)
|
135
|
-
return true if key.to_s.match?(/(^|[
|
135
|
+
return true if key.to_s.match?(/(^|[-_])(?:password|token|secret|credential|key)($|[-_])/i)
|
136
136
|
|
137
137
|
# Check against Rails filter parameters if available
|
138
138
|
if defined?(Rails) && Rails.application
|
@@ -14,11 +14,7 @@ module ActiveCypher
|
|
14
14
|
self.backend ||= begin
|
15
15
|
base = Logger.new($stdout)
|
16
16
|
base.level = ENV.fetch('AC_LOG_LEVEL', 'info').upcase
|
17
|
-
.then
|
18
|
-
Logger.const_get(lvl)
|
19
|
-
rescue StandardError
|
20
|
-
Logger::INFO
|
21
|
-
end
|
17
|
+
.then { |lvl| Logger.const_get(lvl) }
|
22
18
|
ActiveSupport::TaggedLogging.new(base).tap { |l| l.tagged! 'ActiveCypher' }
|
23
19
|
end
|
24
20
|
end
|
@@ -73,11 +73,7 @@ module ActiveCypher
|
|
73
73
|
|
74
74
|
return @connection if defined?(@connection) && @connection
|
75
75
|
|
76
|
-
|
77
|
-
from_class.constantize.connection
|
78
|
-
rescue StandardError
|
79
|
-
nil
|
80
|
-
end
|
76
|
+
from_class.constantize.connection
|
81
77
|
end
|
82
78
|
|
83
79
|
# --------------------------------------------------------------
|
@@ -301,9 +297,14 @@ module ActiveCypher
|
|
301
297
|
_run(:update) { update_relationship }
|
302
298
|
end
|
303
299
|
end
|
304
|
-
rescue
|
305
|
-
|
306
|
-
|
300
|
+
rescue ActiveCypher::RecordNotSaved, RuntimeError => e
|
301
|
+
# Only catch specific validation errors, let other errors propagate
|
302
|
+
if e.message.include?('must be persisted') || e.message.include?('creation returned no id')
|
303
|
+
log_error "Failed to save #{self.class}: #{e.message}"
|
304
|
+
false
|
305
|
+
else
|
306
|
+
raise
|
307
|
+
end
|
307
308
|
end
|
308
309
|
|
309
310
|
# Bang version of save - raises exception if save fails
|
@@ -396,9 +397,6 @@ module ActiveCypher
|
|
396
397
|
@new_record = false
|
397
398
|
changes_applied
|
398
399
|
true
|
399
|
-
rescue StandardError => e
|
400
|
-
log_error "Failed to save #{self.class}: #{e.class} – #{e.message}"
|
401
|
-
false
|
402
400
|
end
|
403
401
|
|
404
402
|
def update_relationship
|
@@ -9,20 +9,14 @@ module Cyrel
|
|
9
9
|
|
10
10
|
def initialize(ast_node)
|
11
11
|
@ast_node = ast_node
|
12
|
-
@ast_node_hash = ast_node.hash
|
13
12
|
super()
|
14
13
|
end
|
15
14
|
|
16
15
|
def render(query)
|
17
|
-
#
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
# Create a compiler that delegates parameter registration to the query
|
22
|
-
compiler = QueryIntegratedCompiler.new(query)
|
23
|
-
compiler.compile(ast_node)
|
24
|
-
compiler.output.string
|
25
|
-
end
|
16
|
+
# Create a compiler that delegates parameter registration to the query
|
17
|
+
compiler = QueryIntegratedCompiler.new(query)
|
18
|
+
compiler.compile(ast_node)
|
19
|
+
compiler.output.string
|
26
20
|
end
|
27
21
|
|
28
22
|
# Ruby 3.0+ pattern matching support
|
data/lib/cyrel/ast/compiler.rb
CHANGED
@@ -11,13 +11,8 @@ module Cyrel
|
|
11
11
|
|
12
12
|
def initialize
|
13
13
|
@output = StringIO.new
|
14
|
-
|
15
|
-
|
16
|
-
@param_counter = Concurrent::AtomicFixnum.new(0)
|
17
|
-
else
|
18
|
-
@parameters = {}
|
19
|
-
@param_counter = 0
|
20
|
-
end
|
14
|
+
@parameters = {}
|
15
|
+
@param_counter = 0
|
21
16
|
@first_clause = true
|
22
17
|
@loop_variables = Set.new # Track loop variables that shouldn't be parameterized
|
23
18
|
end
|
@@ -573,17 +568,9 @@ module Cyrel
|
|
573
568
|
existing_key = @parameters.key(value)
|
574
569
|
return existing_key if existing_key
|
575
570
|
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
counter = @param_counter.increment
|
580
|
-
key = :"p#{counter}"
|
581
|
-
else
|
582
|
-
# Non-concurrent version
|
583
|
-
|
584
|
-
@param_counter += 1
|
585
|
-
key = :"p#{@param_counter}"
|
586
|
-
end
|
571
|
+
# Parameter registration
|
572
|
+
@param_counter += 1
|
573
|
+
key = :"p#{@param_counter}"
|
587
574
|
@parameters[key] = value
|
588
575
|
key
|
589
576
|
end
|
data/lib/cyrel/logging.rb
CHANGED
data/lib/cyrel/query.rb
CHANGED
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.11.
|
4
|
+
version: 0.11.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Abdelkader Boudih
|
@@ -154,7 +154,6 @@ files:
|
|
154
154
|
- lib/active_cypher/model/core.rb
|
155
155
|
- lib/active_cypher/model/countable.rb
|
156
156
|
- lib/active_cypher/model/destruction.rb
|
157
|
-
- lib/active_cypher/model/inspectable.rb
|
158
157
|
- lib/active_cypher/model/labelling.rb
|
159
158
|
- lib/active_cypher/model/persistence.rb
|
160
159
|
- lib/active_cypher/model/querying.rb
|
@@ -192,7 +191,6 @@ files:
|
|
192
191
|
- lib/cyrel/ast/remove_node.rb
|
193
192
|
- lib/cyrel/ast/return_node.rb
|
194
193
|
- lib/cyrel/ast/set_node.rb
|
195
|
-
- lib/cyrel/ast/simple_cache.rb
|
196
194
|
- lib/cyrel/ast/skip_node.rb
|
197
195
|
- lib/cyrel/ast/union_node.rb
|
198
196
|
- lib/cyrel/ast/unwind_node.rb
|
@@ -265,7 +263,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
265
263
|
- !ruby/object:Gem::Version
|
266
264
|
version: '0'
|
267
265
|
requirements: []
|
268
|
-
rubygems_version: 3.6.
|
266
|
+
rubygems_version: 3.6.9
|
269
267
|
specification_version: 4
|
270
268
|
summary: OpenCypher Adapter ala ActiveRecord
|
271
269
|
test_files: []
|
@@ -1,28 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ActiveCypher
|
4
|
-
module Model
|
5
|
-
# @!parse
|
6
|
-
# # Adds a custom inspect method for pretty-printing a compact, single-line summary of the object.
|
7
|
-
# # Because nothing says "debuggable" like a string that pretends your object is more interesting than it is.
|
8
|
-
module Inspectable
|
9
|
-
# Custom object inspection method for pretty-printing a compact,
|
10
|
-
# single-line summary of the object. Output examples:
|
11
|
-
#
|
12
|
-
# #<UserNode id="26" name="Alice" age=34> => persisted object
|
13
|
-
# #<UserNode (new) name="Bob"> => object not yet saved
|
14
|
-
#
|
15
|
-
def inspect
|
16
|
-
# Put 'internal_id' first like it's the main character (even if it's nil)
|
17
|
-
ordered = attributes.dup
|
18
|
-
ordered = ordered.slice('internal_id').merge(ordered.except('internal_id'))
|
19
|
-
|
20
|
-
# Turn each attr into "key: value" because we humans fear raw hashes
|
21
|
-
parts = ordered.map { |k, v| "#{k}: #{v.inspect}" }
|
22
|
-
|
23
|
-
# Wrap it all up in a fake-sane object string, so you can pretend your data is organized.
|
24
|
-
"#<#{self.class} #{parts.join(', ')}>"
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
@@ -1,50 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'singleton'
|
4
|
-
|
5
|
-
module Cyrel
|
6
|
-
module AST
|
7
|
-
# Simple thread-safe compilation cache
|
8
|
-
# Because compiling the same query twice is like watching reruns
|
9
|
-
class SimpleCache
|
10
|
-
include Singleton
|
11
|
-
|
12
|
-
def initialize
|
13
|
-
@cache = {}
|
14
|
-
@mutex = Mutex.new
|
15
|
-
@max_size = 1000
|
16
|
-
end
|
17
|
-
|
18
|
-
def fetch(key)
|
19
|
-
@mutex.synchronize do
|
20
|
-
if @cache.key?(key)
|
21
|
-
@cache[key]
|
22
|
-
elsif block_given?
|
23
|
-
value = yield
|
24
|
-
store(key, value)
|
25
|
-
value
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def clear!
|
31
|
-
@mutex.synchronize { @cache.clear }
|
32
|
-
end
|
33
|
-
|
34
|
-
def size
|
35
|
-
@mutex.synchronize { @cache.size }
|
36
|
-
end
|
37
|
-
|
38
|
-
private
|
39
|
-
|
40
|
-
def store(key, value)
|
41
|
-
# Simple LRU: remove oldest entries when cache is full
|
42
|
-
if @cache.size >= @max_size
|
43
|
-
# Remove half of the oldest entries
|
44
|
-
(@max_size / 2).times { @cache.shift }
|
45
|
-
end
|
46
|
-
@cache[key] = value
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|