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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +113 -411
  3. data/lib/neo4j/driver/auth_token_manager.rb +29 -0
  4. data/lib/neo4j/driver/client_certificate_manager.rb +31 -0
  5. data/lib/neo4j/driver/exceptions/neo4j_exception.rb +16 -7
  6. data/lib/neo4j/driver/exceptions/protocol_exception.rb +0 -5
  7. data/lib/neo4j/driver/exceptions/security_retryable_exception.rb +14 -0
  8. data/lib/neo4j/driver/exceptions/transaction_terminated_exception.rb +13 -0
  9. data/lib/neo4j/driver/ext/auth_token.rb +17 -0
  10. data/{jruby → lib}/neo4j/driver/ext/auth_tokens.rb +5 -1
  11. data/lib/neo4j/driver/ext/bookmark_managers.rb +20 -0
  12. data/lib/neo4j/driver/ext/client_certificates.rb +21 -0
  13. data/{jruby → lib}/neo4j/driver/ext/config_converter.rb +34 -6
  14. data/lib/neo4j/driver/ext/exception_mapper.rb +177 -0
  15. data/lib/neo4j/driver/ext/graph_database.rb +25 -0
  16. data/lib/neo4j/driver/ext/internal/clock_adapter.rb +33 -0
  17. data/lib/neo4j/driver/ext/internal/cluster/routing_table_registry_impl.rb +28 -0
  18. data/lib/neo4j/driver/ext/internal/driver_factory.rb +54 -0
  19. data/lib/neo4j/driver/ext/internal/internal_notification_severity.rb +14 -0
  20. data/lib/neo4j/driver/ext/internal/reflection.rb +22 -0
  21. data/lib/neo4j/driver/ext/internal/summary/internal_gql_notification.rb +22 -0
  22. data/lib/neo4j/driver/ext/internal/summary/internal_gql_status_object.rb +18 -0
  23. data/{jruby → lib}/neo4j/driver/ext/internal/summary/internal_plan.rb +3 -5
  24. data/lib/neo4j/driver/ext/internal/summary/internal_query_profile.rb +36 -0
  25. data/lib/neo4j/driver/ext/internal/summary/internal_result_summary.rb +44 -0
  26. data/lib/neo4j/driver/ext/internal_driver.rb +101 -0
  27. data/lib/neo4j/driver/ext/logger.rb +54 -0
  28. data/lib/neo4j/driver/ext/map_converter.rb +13 -0
  29. data/lib/neo4j/driver/ext/neo_converter.rb +71 -0
  30. data/lib/neo4j/driver/ext/plain_map_converter.rb +11 -0
  31. data/{jruby → lib}/neo4j/driver/ext/ruby_converter.rb +19 -5
  32. data/lib/neo4j/driver/ext/unsupported_type.rb +14 -0
  33. data/lib/neo4j/driver/internal/deprecator.rb +9 -5
  34. data/lib/neo4j/driver/internal/duration_normalizer.rb +14 -41
  35. data/lib/neo4j/driver/internal/internal_auth_token_manager.rb +37 -0
  36. data/lib/neo4j/driver/internal/validator.rb +10 -0
  37. data/lib/neo4j/driver/summary/query_type.rb +5 -1
  38. data/lib/neo4j/driver/types/duration.rb +76 -0
  39. data/lib/neo4j/driver/types/local_date_time.rb +52 -8
  40. data/lib/neo4j/driver/types/local_time.rb +42 -7
  41. data/lib/neo4j/driver/types/offset_time.rb +71 -7
  42. data/lib/neo4j/driver/types/point.rb +38 -18
  43. data/lib/neo4j/driver/types/temporal_value.rb +43 -0
  44. data/lib/neo4j/driver/types/unresolvable_zoned_date_time.rb +38 -0
  45. data/{jruby → lib}/neo4j/driver/version.rb +1 -1
  46. data/{jruby → lib}/neo4j/driver.rb +31 -7
  47. data/lib/neo4j-ruby-driver_jars.rb +14 -0
  48. data/lib/neo4j-ruby-driver_loader.rb +34 -20
  49. data/lib/org/neo4j/driver/neo4j-java-driver-all/6.2.1/neo4j-java-driver-all-6.2.1.jar +0 -0
  50. data/lib/org/neo4j/driver/neo4j-java-driver-observation-metrics/6.2.1/neo4j-java-driver-observation-metrics-6.2.1.jar +0 -0
  51. data/lib/org/reactivestreams/reactive-streams/1.0.4/reactive-streams-1.0.4.jar +0 -0
  52. metadata +96 -177
  53. data/jruby/neo4j/driver/ext/async_converter.rb +0 -55
  54. data/jruby/neo4j/driver/ext/exception_mapper.rb +0 -92
  55. data/jruby/neo4j/driver/ext/graph_database.rb +0 -33
  56. data/jruby/neo4j/driver/ext/internal/async/internal_async_session.rb +0 -23
  57. data/jruby/neo4j/driver/ext/internal/cluster/routing_table_registry_impl.rb +0 -15
  58. data/jruby/neo4j/driver/ext/internal/cursor/disposable_async_result_cursor.rb +0 -15
  59. data/jruby/neo4j/driver/ext/internal/driver_factory.rb +0 -24
  60. data/jruby/neo4j/driver/ext/internal/summary/internal_result_summary.rb +0 -34
  61. data/jruby/neo4j/driver/ext/internal_driver.rb +0 -52
  62. data/jruby/neo4j/driver/ext/logger.rb +0 -60
  63. data/jruby/neo4j/driver/ext/map_converter.rb +0 -14
  64. data/jruby/neo4j/driver/ext/neo_converter.rb +0 -59
  65. data/lib/neo4j/driver/internal/bolt_server_address.rb +0 -97
  66. data/lib/neo4j/driver/synchronizable.rb +0 -23
  67. data/lib/neo4j/driver/types/time.rb +0 -45
  68. /data/{jruby → lib}/neo4j/driver/ext/exception_checkable.rb +0 -0
  69. /data/{jruby → lib}/neo4j/driver/ext/internal/abstract_query_runner.rb +0 -0
  70. /data/{jruby → lib}/neo4j/driver/ext/internal/eager_result_value.rb +0 -0
  71. /data/{jruby → lib}/neo4j/driver/ext/internal/metrics/internal_connection_pool_metrics.rb +0 -0
  72. /data/{jruby → lib}/neo4j/driver/ext/internal/summary/internal_notification.rb +0 -0
  73. /data/{jruby → lib}/neo4j/driver/ext/internal_entity.rb +0 -0
  74. /data/{jruby → lib}/neo4j/driver/ext/internal_keys.rb +0 -0
  75. /data/{jruby → lib}/neo4j/driver/ext/internal_node.rb +0 -0
  76. /data/{jruby → lib}/neo4j/driver/ext/internal_record.rb +0 -0
  77. /data/{jruby → lib}/neo4j/driver/ext/internal_relationship.rb +0 -0
  78. /data/{jruby → lib}/neo4j/driver/ext/internal_result.rb +0 -0
  79. /data/{jruby → lib}/neo4j/driver/ext/internal_session.rb +0 -0
  80. /data/{jruby → lib}/neo4j/driver/ext/internal_transaction.rb +0 -0
  81. /data/{jruby → lib}/neo4j/driver/ext/query.rb +0 -0
  82. /data/{jruby → lib}/neo4j/driver/ext/run_override.rb +0 -0
  83. /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,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Neo4j
4
+ module Driver
5
+ module Ext
6
+ module MapConverter
7
+ include PlainMapConverter
8
+
9
+ def to_h = java_method(:asMap, [java.util.function.Function]).call(&:itself).as_ruby_object
10
+ end
11
+ end
12
+ end
13
+ 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
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Neo4j
4
+ module Driver
5
+ module Ext
6
+ module PlainMapConverter
7
+ def as_ruby_object = to_h.transform_values!(&:as_ruby_object).transform_keys!(&:to_sym)
8
+ end
9
+ end
10
+ end
11
+ 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
- Driver::Internal::DurationNormalizer.create(*%i[months days seconds nanoseconds].map(&as_object.method(:send)))
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.parse(as_offset_time.to_string)
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.parse(as_local_time.to_string)
28
+ Types::LocalTime.new(as_local_time.to_nano_of_day)
29
29
  when Java::OrgNeo4jDriverInternalTypes::TypeConstructor::LOCAL_DATE_TIME
30
- Types::LocalDateTime.parse(as_local_date_time.to_string)
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).in_time_zone(TZInfo::Timezone.get(zone_id))
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
- def self.deprecator
6
- @deprecator ||= ActiveSupport::Deprecation.new('6.0', 'neo4j-ruby-driver')
7
- end
7
+ class << self
8
+ def deprecator
9
+ self
10
+ end
8
11
 
9
- def self.log_warning(old_method, new_method, version)
10
- deprecator.deprecation_warning(old_method, new_method)
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
- class << self
8
- def normalize(object)
9
- parts = object.parts.to_h
10
- parts.default = 0
11
- months_i, months_remainder_seconds = divmod(months(parts), ActiveSupport::Duration::SECONDS_PER_MONTH)
12
- months_days, months_remainder_seconds =
13
- months_remainder_seconds.divmod(ActiveSupport::Duration::SECONDS_PER_DAY)
14
- days_i, days_remainder_seconds = divmod(months_days + days(parts), ActiveSupport::Duration::SECONDS_PER_DAY)
15
- seconds_i, nonanoseconds = divmod(months_remainder_seconds + days_remainder_seconds + seconds(parts),
16
- 1_000_000_000)
17
- [months_i, days_i, seconds_i, nonanoseconds.round]
18
- end
19
-
20
- def milliseconds(duration)
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