neo4j-ruby-driver 6.0.3.alpha.0-java → 6.2.1.beta.1-java
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/README.md +113 -411
- data/lib/neo4j/driver/auth_token_manager.rb +29 -0
- data/lib/neo4j/driver/client_certificate_manager.rb +31 -0
- data/lib/neo4j/driver/exceptions/neo4j_exception.rb +16 -7
- data/lib/neo4j/driver/exceptions/protocol_exception.rb +0 -5
- data/lib/neo4j/driver/exceptions/security_retryable_exception.rb +14 -0
- data/lib/neo4j/driver/exceptions/transaction_terminated_exception.rb +13 -0
- data/lib/neo4j/driver/ext/auth_token.rb +17 -0
- data/{jruby → lib}/neo4j/driver/ext/auth_tokens.rb +5 -1
- data/lib/neo4j/driver/ext/bookmark_managers.rb +20 -0
- data/lib/neo4j/driver/ext/client_certificates.rb +21 -0
- data/{jruby → lib}/neo4j/driver/ext/config_converter.rb +34 -6
- data/lib/neo4j/driver/ext/exception_mapper.rb +177 -0
- data/lib/neo4j/driver/ext/graph_database.rb +25 -0
- data/lib/neo4j/driver/ext/internal/clock_adapter.rb +33 -0
- data/lib/neo4j/driver/ext/internal/cluster/routing_table_registry_impl.rb +28 -0
- data/lib/neo4j/driver/ext/internal/driver_factory.rb +54 -0
- data/lib/neo4j/driver/ext/internal/internal_notification_severity.rb +14 -0
- data/lib/neo4j/driver/ext/internal/reflection.rb +22 -0
- data/lib/neo4j/driver/ext/internal/summary/internal_gql_notification.rb +22 -0
- data/lib/neo4j/driver/ext/internal/summary/internal_gql_status_object.rb +18 -0
- data/{jruby → lib}/neo4j/driver/ext/internal/summary/internal_plan.rb +3 -5
- data/lib/neo4j/driver/ext/internal/summary/internal_query_profile.rb +36 -0
- data/lib/neo4j/driver/ext/internal/summary/internal_result_summary.rb +44 -0
- data/lib/neo4j/driver/ext/internal_driver.rb +101 -0
- data/lib/neo4j/driver/ext/logger.rb +54 -0
- data/lib/neo4j/driver/ext/map_converter.rb +13 -0
- data/lib/neo4j/driver/ext/neo_converter.rb +71 -0
- data/lib/neo4j/driver/ext/plain_map_converter.rb +11 -0
- data/{jruby → lib}/neo4j/driver/ext/ruby_converter.rb +19 -5
- data/lib/neo4j/driver/ext/unsupported_type.rb +14 -0
- data/lib/neo4j/driver/internal/deprecator.rb +9 -5
- data/lib/neo4j/driver/internal/duration_normalizer.rb +14 -41
- data/lib/neo4j/driver/internal/internal_auth_token_manager.rb +37 -0
- data/lib/neo4j/driver/internal/validator.rb +10 -0
- data/lib/neo4j/driver/summary/query_type.rb +5 -1
- data/lib/neo4j/driver/types/duration.rb +76 -0
- data/lib/neo4j/driver/types/local_date_time.rb +52 -8
- data/lib/neo4j/driver/types/local_time.rb +42 -7
- data/lib/neo4j/driver/types/offset_time.rb +71 -7
- data/lib/neo4j/driver/types/point.rb +38 -18
- data/lib/neo4j/driver/types/temporal_value.rb +43 -0
- data/lib/neo4j/driver/types/unresolvable_zoned_date_time.rb +38 -0
- data/{jruby → lib}/neo4j/driver/version.rb +1 -1
- data/{jruby → lib}/neo4j/driver.rb +31 -7
- data/lib/neo4j-ruby-driver_jars.rb +14 -0
- data/lib/neo4j-ruby-driver_loader.rb +34 -20
- data/lib/org/neo4j/driver/neo4j-java-driver-all/6.2.1/neo4j-java-driver-all-6.2.1.jar +0 -0
- data/lib/org/neo4j/driver/neo4j-java-driver-observation-metrics/6.2.1/neo4j-java-driver-observation-metrics-6.2.1.jar +0 -0
- data/lib/org/reactivestreams/reactive-streams/1.0.4/reactive-streams-1.0.4.jar +0 -0
- metadata +96 -177
- data/jruby/neo4j/driver/ext/async_converter.rb +0 -55
- data/jruby/neo4j/driver/ext/exception_mapper.rb +0 -92
- data/jruby/neo4j/driver/ext/graph_database.rb +0 -33
- data/jruby/neo4j/driver/ext/internal/async/internal_async_session.rb +0 -23
- data/jruby/neo4j/driver/ext/internal/cluster/routing_table_registry_impl.rb +0 -15
- data/jruby/neo4j/driver/ext/internal/cursor/disposable_async_result_cursor.rb +0 -15
- data/jruby/neo4j/driver/ext/internal/driver_factory.rb +0 -24
- data/jruby/neo4j/driver/ext/internal/summary/internal_result_summary.rb +0 -34
- data/jruby/neo4j/driver/ext/internal_driver.rb +0 -52
- data/jruby/neo4j/driver/ext/logger.rb +0 -60
- data/jruby/neo4j/driver/ext/map_converter.rb +0 -14
- data/jruby/neo4j/driver/ext/neo_converter.rb +0 -59
- data/lib/neo4j/driver/internal/bolt_server_address.rb +0 -97
- data/lib/neo4j/driver/synchronizable.rb +0 -23
- data/lib/neo4j/driver/types/time.rb +0 -45
- /data/{jruby → lib}/neo4j/driver/ext/exception_checkable.rb +0 -0
- /data/{jruby → lib}/neo4j/driver/ext/internal/abstract_query_runner.rb +0 -0
- /data/{jruby → lib}/neo4j/driver/ext/internal/eager_result_value.rb +0 -0
- /data/{jruby → lib}/neo4j/driver/ext/internal/metrics/internal_connection_pool_metrics.rb +0 -0
- /data/{jruby → lib}/neo4j/driver/ext/internal/summary/internal_notification.rb +0 -0
- /data/{jruby → lib}/neo4j/driver/ext/internal_entity.rb +0 -0
- /data/{jruby → lib}/neo4j/driver/ext/internal_keys.rb +0 -0
- /data/{jruby → lib}/neo4j/driver/ext/internal_node.rb +0 -0
- /data/{jruby → lib}/neo4j/driver/ext/internal_record.rb +0 -0
- /data/{jruby → lib}/neo4j/driver/ext/internal_relationship.rb +0 -0
- /data/{jruby → lib}/neo4j/driver/ext/internal_result.rb +0 -0
- /data/{jruby → lib}/neo4j/driver/ext/internal_session.rb +0 -0
- /data/{jruby → lib}/neo4j/driver/ext/internal_transaction.rb +0 -0
- /data/{jruby → lib}/neo4j/driver/ext/query.rb +0 -0
- /data/{jruby → lib}/neo4j/driver/ext/run_override.rb +0 -0
- /data/{jruby → lib}/neo4j/driver/ext/start_end_naming.rb +0 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Neo4j
|
|
4
|
+
module Driver
|
|
5
|
+
module Ext
|
|
6
|
+
module Internal
|
|
7
|
+
module Summary
|
|
8
|
+
# QueryProfile (driver 6.2) reports each profile stat as an
|
|
9
|
+
# Optional, so an absent stat is distinguishable from a real 0 —
|
|
10
|
+
# unlike the deprecated ProfiledPlan, whose long getters collapse
|
|
11
|
+
# absent to 0. Map empty -> nil (the backend omits nil) and
|
|
12
|
+
# present -> the value; this is what makes
|
|
13
|
+
# Feature:API:Summary:Profile:OptionalStats work. operator_type /
|
|
14
|
+
# args / identifiers / children come from the InternalPlan
|
|
15
|
+
# superclass unchanged.
|
|
16
|
+
module InternalQueryProfile
|
|
17
|
+
def db_hits = long(dbHits)
|
|
18
|
+
def records = long(rows)
|
|
19
|
+
def page_cache_hits = long(pageCacheHits)
|
|
20
|
+
def page_cache_misses = long(pageCacheMisses)
|
|
21
|
+
def page_cache_hit_ratio = double(pageCacheHitRatio)
|
|
22
|
+
def time = super.or_else(nil)&.to_nanos
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
# OptionalLong / OptionalDouble -> Integer / Float, or nil when
|
|
27
|
+
# empty. or_else(nil) can't be used: their orElse takes a
|
|
28
|
+
# primitive default, not nil.
|
|
29
|
+
def long(optional) = optional.present? ? optional.as_long : nil
|
|
30
|
+
def double(optional) = optional.present? ? optional.as_double : nil
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Neo4j
|
|
4
|
+
module Driver
|
|
5
|
+
module Ext
|
|
6
|
+
module Internal
|
|
7
|
+
module Summary
|
|
8
|
+
module InternalResultSummary
|
|
9
|
+
%i[result_available_after result_consumed_after].each do |method|
|
|
10
|
+
define_method(method) do
|
|
11
|
+
super(Java::JavaUtilConcurrent::TimeUnit::MILLISECONDS).then { |val| val unless val == -1 }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Ruby-ify the 6.2 queryProfile() Optional -> the QueryProfile or
|
|
16
|
+
# nil. The presence-aware profile (Optional stats) the backend
|
|
17
|
+
# serializes; `profile` stays the deprecated ProfiledPlan.
|
|
18
|
+
def query_profile = super.or_else(nil)
|
|
19
|
+
|
|
20
|
+
# Driver::Summary::QueryType is a Ruby module in lib/shared/.
|
|
21
|
+
# include_package only fills missing constants on a module via
|
|
22
|
+
# const_missing — Java never overrides Ruby constants that
|
|
23
|
+
# already exist, regardless of load order. So our Ruby module
|
|
24
|
+
# wins on both flavours.
|
|
25
|
+
#
|
|
26
|
+
# The case selector still needs the Java enum. Fully-qualifying
|
|
27
|
+
# Java::OrgNeo4jDriverSummary::QueryType keeps readers from
|
|
28
|
+
# wondering whether `QueryType` here means the Ruby module or
|
|
29
|
+
# the Java enum, and avoids a java_import line whose only job
|
|
30
|
+
# would be to disambiguate this one method.
|
|
31
|
+
def query_type
|
|
32
|
+
case super
|
|
33
|
+
when Java::OrgNeo4jDriverSummary::QueryType::READ_ONLY then Driver::Summary::QueryType::READ_ONLY
|
|
34
|
+
when Java::OrgNeo4jDriverSummary::QueryType::READ_WRITE then Driver::Summary::QueryType::READ_WRITE
|
|
35
|
+
when Java::OrgNeo4jDriverSummary::QueryType::WRITE_ONLY then Driver::Summary::QueryType::WRITE_ONLY
|
|
36
|
+
when Java::OrgNeo4jDriverSummary::QueryType::SCHEMA_WRITE then Driver::Summary::QueryType::SCHEMA_WRITE
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Neo4j
|
|
4
|
+
module Driver
|
|
5
|
+
module Ext
|
|
6
|
+
module InternalDriver
|
|
7
|
+
extend AutoCloseable
|
|
8
|
+
include ConfigConverter
|
|
9
|
+
include ExceptionCheckable
|
|
10
|
+
|
|
11
|
+
auto_closeable :session
|
|
12
|
+
|
|
13
|
+
# See lib/mri/.../driver.rb. auth_token is part of `config` in the
|
|
14
|
+
# published surface, but Java's builder takes it as a separate
|
|
15
|
+
# setter — pull it out before handing the rest to QueryConfig.
|
|
16
|
+
def execute_query(query, parameters = {}, config = {})
|
|
17
|
+
auth_token = config.delete(:auth_token)
|
|
18
|
+
check do
|
|
19
|
+
executable_query(query)
|
|
20
|
+
.with_auth_token(auth_token)
|
|
21
|
+
.with_config(to_java_config(Neo4j::Driver::QueryConfig, **config))
|
|
22
|
+
.with_parameters(to_neo(parameters))
|
|
23
|
+
.execute
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def session(auth_token: nil, **session_config)
|
|
28
|
+
# auth_token is a per-session override (Feature:API:Session:AuthConfig);
|
|
29
|
+
# Java exposes it via Driver.session(Class<T>, SessionConfig, AuthToken).
|
|
30
|
+
# Passing null defers to the driver default — same as the
|
|
31
|
+
# 2-arg overload, so we can always take the long path.
|
|
32
|
+
java_method(:session,
|
|
33
|
+
[java.lang.Class, org.neo4j.driver.SessionConfig, org.neo4j.driver.AuthToken])
|
|
34
|
+
.call(org.neo4j.driver.Session.java_class,
|
|
35
|
+
to_java_config(Neo4j::Driver::SessionConfig, **session_config),
|
|
36
|
+
auth_token)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def verify_connectivity
|
|
40
|
+
check { super }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def verify_authentication(auth_token)
|
|
44
|
+
check { super }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def supports_session_auth?
|
|
48
|
+
check { supports_session_auth }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Routing table for `database` (testkit GetRoutingTable). Returns
|
|
52
|
+
# Java's RoutingTable (routers/writers/readers + expirationTimestamp);
|
|
53
|
+
# the path to it is the driver's business, not testkit's.
|
|
54
|
+
def routing_table(database)
|
|
55
|
+
routing_table_registry.routing_table_handler(database).routing_table
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Force a routing-table refresh (testkit ForcedRoutingTableUpdate).
|
|
59
|
+
# Drive the registry's ensureRoutingTable — a fresh/aged table is
|
|
60
|
+
# stale, so this triggers a real ROUTE and populates the handler.
|
|
61
|
+
# ImmutableObservation is an empty marker interface, so a bare impl
|
|
62
|
+
# is enough.
|
|
63
|
+
def routing_table_refresh(database, bookmarks)
|
|
64
|
+
db_name = Neo4j::Driver::Internal::DatabaseName.database(database)
|
|
65
|
+
params = Java::OrgNeo4jDriverInternalShadedBoltConnection::RoutedBoltConnectionParameters
|
|
66
|
+
.builder
|
|
67
|
+
.with_database_name(db_name)
|
|
68
|
+
.with_bookmarks(java.util.HashSet.new(Array(bookmarks)))
|
|
69
|
+
.build
|
|
70
|
+
routing_table_registry
|
|
71
|
+
.ensure_routing_table(java.util.concurrent.CompletableFuture.completed_future(db_name),
|
|
72
|
+
params, NOOP_OBSERVATION)
|
|
73
|
+
.to_completable_future.get
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
NOOP_OBSERVATION = Class.new do
|
|
77
|
+
include Java::OrgNeo4jDriverInternalShadedBoltConnectionObservation::ImmutableObservation
|
|
78
|
+
end.new
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
# Reflect from the session factory down to the RoutedBoltConnectionSource
|
|
83
|
+
# that owns the routing-table registry. The number of delegating
|
|
84
|
+
# wrappers varies by driver version (6.1.x adds a
|
|
85
|
+
# ProviderClosingBoltConnectionSource), so unwrap `delegate` until the
|
|
86
|
+
# source with a `registry` field.
|
|
87
|
+
def routing_table_registry
|
|
88
|
+
source = Internal::Reflection.field(session_factory, 'connectionSource')
|
|
89
|
+
until Internal::Reflection.field?(source, 'registry')
|
|
90
|
+
unless Internal::Reflection.field?(source, 'delegate')
|
|
91
|
+
raise KeyError, "No routing-table registry in the connection-source chain (#{source.java_class.simple_name})"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
source = Internal::Reflection.field(source, 'delegate')
|
|
95
|
+
end
|
|
96
|
+
Internal::Reflection.field(source, 'registry')
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Neo4j
|
|
4
|
+
module Driver
|
|
5
|
+
module Ext
|
|
6
|
+
class Logger
|
|
7
|
+
include org.neo4j.driver.Logger
|
|
8
|
+
include org.neo4j.driver.Logging
|
|
9
|
+
extend Forwardable
|
|
10
|
+
|
|
11
|
+
delegate debug?: :@logger
|
|
12
|
+
|
|
13
|
+
def initialize(logger)
|
|
14
|
+
@logger = logger
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def getLog(_name)
|
|
18
|
+
self
|
|
19
|
+
end
|
|
20
|
+
alias get_log getLog
|
|
21
|
+
|
|
22
|
+
def error(*args) = log(::Logger::ERROR, *args)
|
|
23
|
+
|
|
24
|
+
def info(*args) = log(::Logger::INFO, *args)
|
|
25
|
+
|
|
26
|
+
def warn(*args) = log(::Logger::WARN, *args)
|
|
27
|
+
|
|
28
|
+
def debug(*args) = log(::Logger::DEBUG, *args)
|
|
29
|
+
alias trace debug
|
|
30
|
+
|
|
31
|
+
def trace_enabled?
|
|
32
|
+
debug?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def debug_enabled
|
|
36
|
+
debug?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def log(level, *args)
|
|
42
|
+
@logger.add(level) { format(*args) }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def format(message, *args)
|
|
46
|
+
return message.to_s if args.empty? && !message.is_a?(java.lang.Throwable)
|
|
47
|
+
args.unshift(message)
|
|
48
|
+
args.unshift('%s%n%s') if args.last.is_a?(java.lang.Throwable)
|
|
49
|
+
java.lang.String.format(*args)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Neo4j
|
|
4
|
+
module Driver
|
|
5
|
+
module Ext
|
|
6
|
+
module NeoConverter
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def to_neo(object, skip_unknown: false)
|
|
10
|
+
case object
|
|
11
|
+
when Hash
|
|
12
|
+
object.map { |key, value| [key.to_s, to_neo(value)] }.to_h
|
|
13
|
+
when Types::Path
|
|
14
|
+
Exceptions::ClientException.unable_to_convert(object)
|
|
15
|
+
when Enumerable
|
|
16
|
+
object.map(&method(:to_neo))
|
|
17
|
+
when Types::UUID # native java.util.UUID; Values.value accepts it
|
|
18
|
+
object
|
|
19
|
+
when String
|
|
20
|
+
object.encoding == Encoding::BINARY ? object.to_java_bytes : object
|
|
21
|
+
when Types::Duration
|
|
22
|
+
Java::OrgNeo4jDriverInternal::InternalIsoDuration.new(object.months, object.days, object.seconds,
|
|
23
|
+
object.nanoseconds)
|
|
24
|
+
when Types::Point
|
|
25
|
+
Java::OrgNeo4jDriver::Values.point(object.srid, *object.coordinates)
|
|
26
|
+
when Types::OffsetTime
|
|
27
|
+
Java::JavaTime::OffsetTime.of(Java::JavaTime::LocalTime.of_nano_of_day(object.nanoseconds),
|
|
28
|
+
Java::JavaTime::ZoneOffset.of_total_seconds(object.tz_offset_seconds))
|
|
29
|
+
when Types::LocalTime
|
|
30
|
+
Java::JavaTime::LocalTime.of_nano_of_day(object.nanoseconds)
|
|
31
|
+
when Types::LocalDateTime
|
|
32
|
+
Java::JavaTime::LocalDateTime.of_epoch_second(object.epoch_seconds, object.nanoseconds,
|
|
33
|
+
Java::JavaTime::ZoneOffset::UTC)
|
|
34
|
+
when defined?(ActiveSupport::TimeWithZone) && ActiveSupport::TimeWithZone
|
|
35
|
+
to_zoned_date_time(object, object.time_zone.tzinfo.identifier)
|
|
36
|
+
when Time, DateTime
|
|
37
|
+
to_zoned_date_time(object, zone_identifier_for(object))
|
|
38
|
+
when Date
|
|
39
|
+
Java::JavaTime::LocalDate.of(object.year, object.month, object.day)
|
|
40
|
+
when Symbol
|
|
41
|
+
object.to_s
|
|
42
|
+
when nil, true, false, Integer, Float
|
|
43
|
+
object
|
|
44
|
+
else
|
|
45
|
+
if skip_unknown
|
|
46
|
+
object
|
|
47
|
+
else
|
|
48
|
+
raise Exceptions::ClientException.unable_to_convert(object)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def to_zoned_date_time(object, zone)
|
|
54
|
+
# Build from the instant, not the wall-clock fields: during a DST
|
|
55
|
+
# fall-back the local time is ambiguous and ZonedDateTime.of(...)
|
|
56
|
+
# would silently pick the earlier offset, dropping the object's
|
|
57
|
+
# actual offset. ofInstant keeps the real instant (and is identical
|
|
58
|
+
# for non-ambiguous times). Use to_i/nsec (the UTC instant) rather
|
|
59
|
+
# than to_time, which triggers an ActiveSupport localtime path.
|
|
60
|
+
instant = Java::JavaTime::Instant.of_epoch_second(object.to_i, object.nsec)
|
|
61
|
+
Java::JavaTime::ZonedDateTime.of_instant(instant, Java::JavaTime::ZoneId.of(zone))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def zone_identifier_for(time)
|
|
65
|
+
zone = time.zone
|
|
66
|
+
zone.respond_to?(:identifier) ? zone.identifier : time.formatted_offset
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -16,20 +16,25 @@ module Neo4j
|
|
|
16
16
|
date = as_local_date
|
|
17
17
|
Date.new(date.year, date.month_value, date.day_of_month)
|
|
18
18
|
when Java::OrgNeo4jDriverInternalTypes::TypeConstructor::DURATION
|
|
19
|
-
|
|
19
|
+
Types::Duration.new(*%i[months days seconds nanoseconds].map(&as_object.method(:send)))
|
|
20
20
|
when Java::OrgNeo4jDriverInternalTypes::TypeConstructor::POINT
|
|
21
21
|
point = as_point
|
|
22
22
|
Types::Point.new(srid: point.srid, x: point.x, y: point.y, z: nullable(point.z))
|
|
23
23
|
when Java::OrgNeo4jDriverInternalTypes::TypeConstructor::BYTES
|
|
24
24
|
String.from_java_bytes(as_byte_array).force_encoding(Encoding::BINARY)
|
|
25
25
|
when Java::OrgNeo4jDriverInternalTypes::TypeConstructor::TIME
|
|
26
|
-
Types::OffsetTime.
|
|
26
|
+
Types::OffsetTime.new(as_offset_time.to_local_time.to_nano_of_day, as_offset_time.offset.total_seconds)
|
|
27
27
|
when Java::OrgNeo4jDriverInternalTypes::TypeConstructor::LOCAL_TIME
|
|
28
|
-
Types::LocalTime.
|
|
28
|
+
Types::LocalTime.new(as_local_time.to_nano_of_day)
|
|
29
29
|
when Java::OrgNeo4jDriverInternalTypes::TypeConstructor::LOCAL_DATE_TIME
|
|
30
|
-
Types::LocalDateTime.
|
|
30
|
+
Types::LocalDateTime.new(as_local_date_time.to_epoch_second(Java::JavaTime::ZoneOffset::UTC),
|
|
31
|
+
as_local_date_time.nano)
|
|
31
32
|
when Java::OrgNeo4jDriverInternalTypes::TypeConstructor::DATE_TIME
|
|
32
33
|
to_time
|
|
34
|
+
when Java::OrgNeo4jDriverInternalTypes::TypeConstructor::UUID
|
|
35
|
+
# Bolt 6.1 UUID -> native java.util.UUID (Types::UUID on JRuby).
|
|
36
|
+
# asObject would raise Uncoercible (UUID is not a string).
|
|
37
|
+
as_uuid
|
|
33
38
|
else
|
|
34
39
|
as_object
|
|
35
40
|
end
|
|
@@ -44,8 +49,17 @@ module Neo4j
|
|
|
44
49
|
Time.parse(time.to_string)
|
|
45
50
|
else
|
|
46
51
|
instant = time.to_instant
|
|
47
|
-
Time.at(instant.epoch_second, instant.nano, :nsec
|
|
52
|
+
Time.at(instant.epoch_second, instant.nano, :nsec, in: TZInfo::Timezone.get(zone_id))
|
|
48
53
|
end
|
|
54
|
+
rescue Java::JavaTime::DateTimeException => e
|
|
55
|
+
# The server sent a zoned datetime whose zone the driver can't
|
|
56
|
+
# resolve (e.g. an unknown tz id); the Java value defers the
|
|
57
|
+
# failure to access time. Surface it as a driver exception
|
|
58
|
+
# (preserving the zone id in the message) instead of leaking the
|
|
59
|
+
# raw Java exception. cause: nil so the managed-tx reverse_check
|
|
60
|
+
# doesn't unwrap to (and re-raise) the raw Java DateTimeException,
|
|
61
|
+
# which the retry logic can't map back to a driver exception.
|
|
62
|
+
raise Exceptions::ClientException.new(e.message), cause: nil
|
|
49
63
|
end
|
|
50
64
|
|
|
51
65
|
def nullable(double)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Neo4j
|
|
4
|
+
module Driver
|
|
5
|
+
module Ext
|
|
6
|
+
# Java's UnsupportedType#message returns an Optional<String>; unwrap it to
|
|
7
|
+
# a nil-or-String so the public API matches MRI's UnsupportedType, which
|
|
8
|
+
# stores a plain nilable message.
|
|
9
|
+
module UnsupportedType
|
|
10
|
+
def message = super.or_else(nil)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Neo4j
|
|
2
4
|
module Driver
|
|
3
5
|
module Internal
|
|
4
6
|
module Deprecator
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
class << self
|
|
8
|
+
def deprecator
|
|
9
|
+
self
|
|
10
|
+
end
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
def behavior=(value)
|
|
13
|
+
# No-op for now
|
|
14
|
+
end
|
|
11
15
|
end
|
|
12
16
|
end
|
|
13
17
|
end
|
|
@@ -4,47 +4,20 @@ module Neo4j
|
|
|
4
4
|
module Driver
|
|
5
5
|
module Internal
|
|
6
6
|
module DurationNormalizer
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
duration&.in_milliseconds&.round
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def create(months, days, seconds, nanoseconds)
|
|
25
|
-
{ months:, days:, seconds: seconds + (nanoseconds.zero? ? 0 : nanoseconds * BigDecimal('1e-9')) }
|
|
26
|
-
.sum { |key, value| ActiveSupport::Duration.send(key, value) }
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
private
|
|
30
|
-
|
|
31
|
-
def divmod(number, factor)
|
|
32
|
-
number_i, remainder = number.divmod(1)
|
|
33
|
-
[number_i.to_i, remainder * factor]
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def months(parts)
|
|
37
|
-
parts[:years] * 12 + parts[:months]
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def days(parts)
|
|
41
|
-
parts[:weeks] * 7 + parts[:days]
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def seconds(parts)
|
|
45
|
-
parts[:hours] * ActiveSupport::Duration::SECONDS_PER_HOUR +
|
|
46
|
-
parts[:minutes] * ActiveSupport::Duration::SECONDS_PER_MINUTE + parts[:seconds]
|
|
47
|
-
end
|
|
7
|
+
# Convert timeout from seconds (or ActiveSupport::Duration) to
|
|
8
|
+
# milliseconds for the Bolt protocol. A negative timeout is
|
|
9
|
+
# invalid — reject it client-side (matches Java, which raises
|
|
10
|
+
# rather than sending a negative tx_timeout to the server).
|
|
11
|
+
def timeout_to_milliseconds(timeout)
|
|
12
|
+
return if timeout.nil?
|
|
13
|
+
|
|
14
|
+
# Check the sign before rounding: a tiny negative timeout
|
|
15
|
+
# (e.g. -0.4ms) would otherwise round to 0 and slip through.
|
|
16
|
+
ms = timeout.to_f * 1000
|
|
17
|
+
raise Exceptions::ClientException,
|
|
18
|
+
"Transaction timeout must not be negative, but was #{ms}ms" if ms.negative?
|
|
19
|
+
|
|
20
|
+
ms.round
|
|
48
21
|
end
|
|
49
22
|
end
|
|
50
23
|
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Neo4j
|
|
4
|
+
module Driver
|
|
5
|
+
module Internal
|
|
6
|
+
# Auth-token manager: anything responding to
|
|
7
|
+
#
|
|
8
|
+
# get_token -> AuthToken
|
|
9
|
+
# handle_security_exception(token, exception) -> Boolean
|
|
10
|
+
#
|
|
11
|
+
# is a manager. Pass an instance to `GraphDatabase.driver` via the
|
|
12
|
+
# `:auth_token_manager` kw arg and the driver will call back into
|
|
13
|
+
# it when it needs a fresh token or when the server rejects the
|
|
14
|
+
# current one.
|
|
15
|
+
#
|
|
16
|
+
# This concrete class is the Proc-based shortcut — supply two
|
|
17
|
+
# callables and you're done. Clients that need richer behaviour
|
|
18
|
+
# subclass and override the methods; the JRuby flavour's class
|
|
19
|
+
# additionally implements `org.neo4j.driver.AuthTokenManager`, so
|
|
20
|
+
# subclasses there automatically slot into the Java driver.
|
|
21
|
+
class InternalAuthTokenManager
|
|
22
|
+
def initialize(get_token:, handle_security_exception:)
|
|
23
|
+
@get_token = get_token
|
|
24
|
+
@handle_security_exception = handle_security_exception
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def get_token
|
|
28
|
+
@get_token.call
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def handle_security_exception(token, exception)
|
|
32
|
+
@handle_security_exception.call(token, exception)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -19,6 +19,16 @@ module Neo4j
|
|
|
19
19
|
obj
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
# Matches Java's driver-side query-text validation (the JRuby
|
|
23
|
+
# flavour gets this for free from the Java driver). A Query
|
|
24
|
+
# object carries its own text, so only bare strings are checked.
|
|
25
|
+
def self.require_query_text!(query)
|
|
26
|
+
raise ArgumentError, 'Cypher query text should not be null' if query.nil?
|
|
27
|
+
raise ArgumentError, 'Cypher query text should not be an empty string' if query.is_a?(String) && query.strip.empty?
|
|
28
|
+
|
|
29
|
+
query
|
|
30
|
+
end
|
|
31
|
+
|
|
22
32
|
def self.require_non_nil_credentials!(username, password)
|
|
23
33
|
require_non_nil! username, "Username"
|
|
24
34
|
require_non_nil! password, "Password"
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Neo4j
|
|
2
4
|
module Driver
|
|
3
5
|
module Summary
|
|
6
|
+
# Mirrors org.neo4j.driver.summary.QueryType — coarse classification
|
|
7
|
+
# of what a query did (read, write, both, or schema mutation).
|
|
4
8
|
module QueryType
|
|
5
9
|
READ_ONLY = 'r'
|
|
6
|
-
READ_WRITE = 'rw'
|
|
7
10
|
WRITE_ONLY = 'w'
|
|
11
|
+
READ_WRITE = 'rw'
|
|
8
12
|
SCHEMA_WRITE = 's'
|
|
9
13
|
end
|
|
10
14
|
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Neo4j
|
|
4
|
+
module Driver
|
|
5
|
+
module Types
|
|
6
|
+
# Bolt Duration. Stores months, days, seconds, nanoseconds as
|
|
7
|
+
# distinct ints — Neo4j keeps these separate because months are
|
|
8
|
+
# variable-length (no fixed conversion to seconds).
|
|
9
|
+
class Duration < TemporalValue
|
|
10
|
+
attr_reader :months, :days, :seconds, :nanoseconds
|
|
11
|
+
|
|
12
|
+
def self.significant_fields = %i[months days seconds nanoseconds]
|
|
13
|
+
|
|
14
|
+
def initialize(months, days, seconds, nanoseconds)
|
|
15
|
+
@months = months.to_i
|
|
16
|
+
@days = days.to_i
|
|
17
|
+
@seconds = seconds.to_i
|
|
18
|
+
@nanoseconds = nanoseconds.to_i
|
|
19
|
+
normalize!
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Parse ISO 8601 duration: "P1Y2M3DT4H5M6.789S", "P3M", "PT2H30M"
|
|
23
|
+
def self.parse(string)
|
|
24
|
+
unless string =~ /^P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)?$/
|
|
25
|
+
raise ArgumentError, "Invalid ISO 8601 duration format: #{string}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
years = ($1 || 0).to_i
|
|
29
|
+
months = ($2 || 0).to_i
|
|
30
|
+
days = ($3 || 0).to_i
|
|
31
|
+
hours = ($4 || 0).to_i
|
|
32
|
+
minutes = ($5 || 0).to_i
|
|
33
|
+
seconds_with_fraction = ($6 || 0).to_f
|
|
34
|
+
|
|
35
|
+
total_months = years * 12 + months
|
|
36
|
+
whole_seconds = seconds_with_fraction.to_i
|
|
37
|
+
fractional_seconds = seconds_with_fraction - whole_seconds
|
|
38
|
+
nanos = (fractional_seconds * NANOS_PER_SECOND).round
|
|
39
|
+
total_seconds = hours * 3600 + minutes * 60 + whole_seconds
|
|
40
|
+
|
|
41
|
+
new(total_months, days, total_seconds, nanos)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def parts
|
|
45
|
+
{ months: @months, days: @days, seconds: @seconds, nanoseconds: @nanoseconds }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# ISO 8601 form. Nanoseconds get a 9-digit zero-padded suffix on
|
|
49
|
+
# seconds so 1s + 5ns prints as "PT1.000000005S", not "PT1.5S".
|
|
50
|
+
def to_s
|
|
51
|
+
if @nanoseconds.zero?
|
|
52
|
+
format('P%dM%dDT%dS', @months, @days, @seconds)
|
|
53
|
+
else
|
|
54
|
+
format('P%dM%dDT%d.%09dS', @months, @days, @seconds, @nanoseconds)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
# Normalize nanoseconds overflow/underflow into seconds:
|
|
61
|
+
# -1ns becomes -1s + 999999999ns; 1.5e9 ns becomes 1s + 5e8 ns.
|
|
62
|
+
def normalize!
|
|
63
|
+
if @nanoseconds < 0
|
|
64
|
+
sec_adjust = (@nanoseconds.abs / NANOS_PER_SECOND.to_f).ceil
|
|
65
|
+
@seconds -= sec_adjust
|
|
66
|
+
@nanoseconds += sec_adjust * NANOS_PER_SECOND
|
|
67
|
+
elsif @nanoseconds >= NANOS_PER_SECOND
|
|
68
|
+
sec_adjust = @nanoseconds / NANOS_PER_SECOND
|
|
69
|
+
@seconds += sec_adjust
|
|
70
|
+
@nanoseconds %= NANOS_PER_SECOND
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|