Skip to content

Commit

Permalink
bolt 5.0 support
Browse files Browse the repository at this point in the history
  • Loading branch information
klobuczek committed Jan 13, 2025
1 parent 24c402a commit c09502e
Show file tree
Hide file tree
Showing 29 changed files with 260 additions and 49 deletions.
1 change: 1 addition & 0 deletions History.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
=== 5.0.7.alpha.2 / 2025-01-13
=== 4.4.19 / 2024-12-16
=== 4.4.6 / 2024-12-16
=== 4.4.16 / 2023-05-03
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ class BoltProtocolUtil
DEFAULT_MAX_OUTBOUND_CHUNK_SIZE_BYTES = 2 ** 15 - 1
HANDSHAKE = [
BOLT_MAGIC_PREAMBLE,
Messaging::V5::BoltProtocolV5::VERSION.to_int,
Messaging::V44::BoltProtocolV44::VERSION.to_int_range(Messaging::V42::BoltProtocolV42::VERSION),
Messaging::V41::BoltProtocolV41::VERSION.to_int,
Messaging::V4::BoltProtocolV4::VERSION.to_int,
Messaging::V3::BoltProtocolV3::VERSION.to_int]
HANDSHAKE_BUF = HANDSHAKE.pack('N*').freeze

Expand Down
3 changes: 1 addition & 2 deletions ruby/neo4j/driver/internal/async/pool/channel.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,11 @@ def initialize(address, connector, logger)
@stream.write(Connection::BoltProtocolUtil.handshake_buf)
@stream.flush
Connection::HandshakeHandler.new(logger).decode(self)
stream_reader = Connection::StreamReader.new(@stream)
stream_writer = Outbound::ChunkAwareByteBufOutput.new(@stream)
@message_dispatcher = Inbound::InboundMessageDispatcher.new(self, logger)
@attributes[:message_dispatcher] = @message_dispatcher
@outbound_handler = Outbound::OutboundMessageHandler.new(stream_writer, message_format, logger)
@common_message_reader = Messaging::Common::CommonMessageReader.new(stream_reader)
@common_message_reader = message_format.new_reader(@stream)
connector.initialize_channel(self, protocol)
end

Expand Down
5 changes: 3 additions & 2 deletions ruby/neo4j/driver/internal/internal_entity.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
module Neo4j::Driver
module Internal
class InternalEntity
attr_reader :id, :properties
attr_reader :id, :element_id, :properties
delegate :hash, to: :id
delegate :[], :size, :key?, :keys, :values, :to_h, to: :properties

def initialize(id, properties)
def initialize(id, element_id, **properties)
@id = id
@element_id = element_id || id.to_s
@properties = properties
end

Expand Down
4 changes: 2 additions & 2 deletions ruby/neo4j/driver/internal/internal_node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ module Internal
class InternalNode < InternalEntity
attr_reader :labels

def initialize(id, *labels, **properties)
super(id, properties)
def initialize(id, element_id, *labels, **properties)
super(id, element_id, **properties)
@labels = labels
end

Expand Down
13 changes: 7 additions & 6 deletions ruby/neo4j/driver/internal/internal_relationship.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@
module Neo4j::Driver
module Internal
class InternalRelationship < InternalEntity
attr_accessor :start_node_id, :end_node_id
attr_accessor :start_node_id, :end_node_id, :start_element_id, :end_element_id
attr_reader :type

def initialize(id, start_node_id, end_node_id, type, **properties)
super(id, properties)
@start_node_id = start_node_id
@end_node_id = end_node_id
def initialize(id, element_id, start_node_id, start_element_id, end_node_id, end_element_id, type, **properties)
super(id, element_id, **properties)
set_start_and_end_node_ids(start_node_id, start_element_id, end_node_id, end_element_id)
@type = type.to_sym
end

def start_and_end_node_ids=(start_node_id, end_node_id)
def set_start_and_end_node_ids(start_node_id, start_element_id, end_node_id, end_element_id)
@start_node_id = start_node_id
@start_element_id = start_element_id || start_node_id.to_s
@end_node_id = end_node_id
@end_element_id = end_element_id || end_node_id.to_s
end

def to_s
Expand Down
2 changes: 2 additions & 0 deletions ruby/neo4j/driver/internal/messaging/bolt_protocol.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ def self.for_version(version)
V43::BoltProtocolV43::INSTANCE
when V44::BoltProtocolV44::VERSION
V44::BoltProtocolV44::INSTANCE
when V5::BoltProtocolV5::VERSION
V5::BoltProtocolV5::INSTANCE
else
raise Exceptions::ClientException, "Unknown protocol version: #{version}"
end
Expand Down
2 changes: 2 additions & 0 deletions ruby/neo4j/driver/internal/messaging/common/common_value.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ module CommonValue
LOCAL_DATE_TIME = 'd'
LOCAL_DATE_TIME_STRUCT_SIZE = 2
DATE_TIME_WITH_ZONE_OFFSET = 'F'
DATE_TIME_WITH_ZONE_OFFSET_UTC = 'I'
DATE_TIME_WITH_ZONE_ID = 'f'
DATE_TIME_WITH_ZONE_ID_UTC = 'i'
DATE_TIME_STRUCT_SIZE = 3
DURATION = 'E'
DURATION_TIME_STRUCT_SIZE = 4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module Messaging
module Common
module CommonValuePacker
include CommonValue
attr_writer :date_time_utc_enabled

def pack(value)
case value
Expand Down Expand Up @@ -61,18 +62,20 @@ def pack_local_date_time(local_date_time)
end

def pack_date_time_with_zone_id(time)
pack_struct_header(DATE_TIME_STRUCT_SIZE, DATE_TIME_WITH_ZONE_ID)
pack_struct_header(DATE_TIME_STRUCT_SIZE,
@date_time_utc_enabled ? DATE_TIME_WITH_ZONE_ID_UTC : DATE_TIME_WITH_ZONE_ID)
pack_date_time(time)
pack_string(time.time_zone.tzinfo.identifier)
end

def pack_date_time(time)
pack_integer(time.to_i + time.utc_offset)
pack_integer(time.to_i + (@date_time_utc_enabled ? 0 : time.utc_offset))
pack_integer(time.nsec)
end

def pack_date_time_with_zone_offset(time)
pack_struct_header(DATE_TIME_STRUCT_SIZE, DATE_TIME_WITH_ZONE_OFFSET)
pack_struct_header(DATE_TIME_STRUCT_SIZE,
@date_time_utc_enabled ? DATE_TIME_WITH_ZONE_OFFSET_UTC : DATE_TIME_WITH_ZONE_OFFSET)
pack_date_time(time)
pack_utc_offset(time)
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module CommonValueUnpacker
UNBOUND_RELATIONSHIP = 'r'
PATH = 'P'
NODE_FIELDS = 3
RELATIONSHIP_FIELDS = 5

def unpack_map(size)
size.times.to_h { [unpack.to_sym, unpack] }
Expand Down Expand Up @@ -91,9 +92,15 @@ def unpack_struct(size, type)
when DATE_TIME_WITH_ZONE_OFFSET
ensure_correct_struct_size(:DATE_TIME_WITH_ZONE_OFFSET, DATE_TIME_STRUCT_SIZE, size)
unpack_date_time_with_zone_offset
when DATE_TIME_WITH_ZONE_OFFSET_UTC
ensure_correct_struct_size(:DATE_TIME_WITH_ZONE_OFFSET_UTC, DATE_TIME_STRUCT_SIZE, size)
unpack_date_time_with_zone_offset(true)
when DATE_TIME_WITH_ZONE_ID
ensure_correct_struct_size(:DATE_TIME_WITH_ZONE_ID, DATE_TIME_STRUCT_SIZE, size)
unpack_date_time_with_zone_id
when DATE_TIME_WITH_ZONE_ID_UTC
ensure_correct_struct_size(:DATE_TIME_WITH_ZONE_ID_UTC, DATE_TIME_STRUCT_SIZE, size)
unpack_date_time_with_zone_id(true)
when DURATION
ensure_correct_struct_size(:DURATION, DURATION_TIME_STRUCT_SIZE, size)
unpack_duration
Expand All @@ -104,13 +111,13 @@ def unpack_struct(size, type)
ensure_correct_struct_size(:POINT, POINT_3D_STRUCT_SIZE, size)
unpack_point3_d
when NODE
ensure_correct_struct_size(:NODE, NODE_FIELDS, size)
ensure_correct_struct_size(:NODE, node_fields, size)
adapted = unpack_node
when RELATIONSHIP
ensure_correct_struct_size(:RELATIONSHIP, 5, size)
ensure_correct_struct_size(:RELATIONSHIP, relationship_fields, size)
unpack_relationship
when UNBOUND_RELATIONSHIP
ensure_correct_struct_size(:RELATIONSHIP, 3, size)
ensure_correct_struct_size(:RELATIONSHIP, unbound_relationship_fields, size)
unpack_unbound_relationship
when PATH
ensure_correct_struct_size(:PATH, 3, size)
Expand All @@ -123,15 +130,26 @@ def unpack_struct(size, type)
private

def unpack_relationship
InternalRelationship.new(*4.times.map { unpack }, **unpack)
id = unpack
start_node_id = unpack
end_node_id = unpack
type = unpack
properties = unpack
InternalRelationship.new(id, unpack_element_id, start_node_id, unpack_element_id, end_node_id, unpack_element_id, type, **properties)
end

def unpack_unbound_relationship
InternalRelationship.new(unpack, nil, nil, unpack, **unpack)
id = unpack
type = unpack
properties = unpack
InternalRelationship.new(id, unpack_element_id, *[nil] * 4, type, **properties)
end

def unpack_node
InternalNode.new(unpack, *unpack.map(&:to_sym), **unpack)
id = unpack
labels = unpack.map(&:to_sym)
properties = unpack
InternalNode.new(id, unpack_element_id, *labels, **properties)
end

def unpack_path
Expand Down Expand Up @@ -168,7 +186,13 @@ def ensure_correct_struct_size(type_constructor, expected, actual)
end
end

private
def node_fields = NODE_FIELDS

def relationship_fields = RELATIONSHIP_FIELDS

def unbound_relationship_fields = 3

def unpack_element_id = nil

def ensure_correct_struct_signature(struct_name, expected, actual)
if expected != actual
Expand Down Expand Up @@ -198,19 +222,20 @@ def unpack_local_date_time
Types::LocalDateTime.new(Time.at(unpack, unpack, :nsec).utc)
end

def unpack_date_time_with_zone_offset
def unpack_date_time_with_zone_offset(utc = false)
# Time.at(unpack, unpack, :nsec, in: unpack)
sec = unpack
nsec = unpack
offset = unpack
time = Time.at(sec, nsec, :nsec).utc
time += offset if utc
Time.new(time.year, time.month, time.mday, time.hour, time.min, time.sec + Rational(nsec, 1_000_000_000),
offset)
end

def unpack_date_time_with_zone_id
def unpack_date_time_with_zone_id(utc = false)
time = Time.at(unpack, unpack, :nsec).in_time_zone(TZInfo::Timezone.get(unpack))
time - time.utc_offset
utc ? time : time - time.utc_offset
end

def unpack_duration
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module Neo4j::Driver
module Internal
module Messaging
module Encode
class LogoffMessageEncoder
def encode(message, packer)
Util::Preconditions.check_argument(message, Request::LogoffMessage)
packer.pack_struct_header(0, message.class::SIGNATURE)
end
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module Neo4j::Driver
module Internal
module Messaging
module Encode
class LogonMessageEncoder
def encode(message, packer)
Util::Preconditions.check_argument(message, Request::LogonMessage)
packer.pack_struct_header(1, message.class::SIGNATURE)
packer.pack(message.metadata)
end
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ def initialize(user_agent, auth_token, routing_context)
end

def to_s
metadata_copy = metadata.merge(Security::InternalAuthToken::CREDENTIALS_KEY => '******')
"HELLO #{metadata_copy}"
"HELLO #{safe_metadata}"
end

private
Expand Down
13 changes: 13 additions & 0 deletions ruby/neo4j/driver/internal/messaging/request/logoff_message.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module Neo4j::Driver
module Internal
module Messaging
module Request
class LogoffMessage < MessageWithMetadata
SIGNATURE = 0x6B

def to_s = 'LOGOFF'
end
end
end
end
end
13 changes: 13 additions & 0 deletions ruby/neo4j/driver/internal/messaging/request/logon_message.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module Neo4j::Driver
module Internal
module Messaging
module Request
class LogonMessage < MessageWithMetadata
SIGNATURE = 0x6A

def to_s = "LOGON #{safe_metadata}"
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ module Internal
module Messaging
module Request
class MessageWithMetadata < Struct.new(:metadata)
protected

def safe_metadata = replace(metadata, Security::InternalAuthToken::CREDENTIALS_KEY, '******')

private

def replace(hash, key, value) = hash.key?(key) ? hash.merge(key => value) : hash
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions ruby/neo4j/driver/internal/messaging/v3/bolt_protocol_v3.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ def initialize_channel(channel, user_agent, auth_token, routing_context)
end

def prepare_to_close_channel(channel)
message_dispatcher = Connection::ChannelAttributes.message_dispatcher(channel)
message_dispatcher = channel.message_dispatcher

message = Request::GoodbyeMessage::GOODBYE
message_dispatcher.enqueue(Handlers::NoOpResponseHandler::INSTANCE)
channel.write_and_flush(message, channel.void_promise)
channel.write_and_flush(message)

message_dispatcher.prepare_to_close_channel
end
Expand Down
6 changes: 3 additions & 3 deletions ruby/neo4j/driver/internal/messaging/v3/message_format_v3.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ def new_writer(output)
MessageWriterV3.new(output)
end

def new_reader(input)
Common::CommonMessageReader.new(input)
end
def new_reader(input) = Common::CommonMessageReader.new(new_value_unpacker(input))

def new_value_unpacker(input) = Async::Connection::StreamReader.new(input)
end
end
end
Expand Down
6 changes: 1 addition & 5 deletions ruby/neo4j/driver/internal/messaging/v4/message_format_v4.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@ module Neo4j::Driver
module Internal
module Messaging
module V4
class MessageFormatV4
class MessageFormatV4 < V3::MessageFormatV3
def new_writer(output)
MessageWriterV4.new(output)
end

def new_reader(input)
Common::CommonMessageReader.new(input)
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,10 @@ module Internal
module Messaging
module V43
# Bolt message format v4.3
class MessageFormatV43
class MessageFormatV43 < V4::MessageFormatV4
def new_writer(output)
MessageWriterV43.new(output)
end

def new_reader(input)
Common::CommonMessageReader.new(input)
end
end
end
end
Expand Down
Loading

0 comments on commit c09502e

Please sign in to comment.