1
+ # Copyright 2024 JanusGraph-Python Authors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from gremlin_python .structure .io .graphbinaryV1 import (
16
+ _GraphBinaryTypeIO , StringIO , GraphBinaryReader , GraphBinaryWriter , DataType ,
17
+ _make_packer ,
18
+ uint64_pack , uint64_unpack , uint8_pack , uint8_unpack ,
19
+ )
20
+ from janusgraph_python .process .traversal import _JanusGraphP , RelationIdentifier
21
+
22
+ uint16_pack , uint16_unpack = _make_packer ('>H' )
23
+ uint32_pack , uint32_unpack = _make_packer ('>I' )
24
+
25
+ class JanusGraphBinaryReader (GraphBinaryReader ):
26
+ def __init__ (self ):
27
+ # register JanusGraph-specific deserializer for custom type code
28
+ deserializer_map = {
29
+ DataType .custom : JanusGraphBinaryTypeIO
30
+ }
31
+
32
+ GraphBinaryReader .__init__ (self , deserializer_map )
33
+
34
+ class JanusGraphBinaryWriter (GraphBinaryWriter ):
35
+ def __init__ (self ):
36
+ # register JanusGraph-specific RelationIdentifier and text-predicate serializer
37
+ serializer_map = [
38
+ (RelationIdentifier , JanusGraphRelationIdentifierIO ),
39
+ (_JanusGraphP , JanusGraphPSerializer )
40
+ ]
41
+
42
+ GraphBinaryWriter .__init__ (self , serializer_map )
43
+
44
+ class JanusGraphBinaryTypeIO (_GraphBinaryTypeIO ):
45
+ # registry of JanusGraph-specific types with their type_id, type_name and class for deserialization
46
+ io_registry = {}
47
+
48
+ @classmethod
49
+ def register_deserializer (cls , type_class ):
50
+ """
51
+ Method to register a deserializer for a JanusGraph-specific type
52
+ """
53
+ cls .io_registry [type_class .graphbinary_type_id ] = (type_class .graphbinary_type_name , type_class )
54
+
55
+ @classmethod
56
+ def objectify (cls , buff , reader , nullable = True ):
57
+ """
58
+ Method used for deserialization of JanusGraph-specific type
59
+ """
60
+ return cls .is_null (buff , reader , cls ._read_data , nullable )
61
+
62
+ @classmethod
63
+ def _read_data (cls , b , r ):
64
+ """
65
+ Method used for identifying a JanusGraph-specific type and
66
+ find a deserializer class for it
67
+ """
68
+ # check if first byte is custom type code byte
69
+ if uint8_unpack (b .read (1 )) != DataType .custom .value :
70
+ return None
71
+
72
+ # get the custom type name length
73
+ custom_type_name_length = uint16_unpack (b .read (2 ))
74
+ custom_type_name = b .read (custom_type_name_length ).decode ()
75
+
76
+ # read the custom type id
77
+ custom_type_id = uint32_unpack (b .read (4 ))
78
+
79
+ # try to get a deserializer class for the JanusGraph-specific type
80
+ custom_serializer = cls .io_registry .get (custom_type_id )
81
+ if not custom_serializer :
82
+ raise NotImplementedError (f"No deserializer found for JanusGraph type with id: { custom_type_id } " )
83
+
84
+ # check the type name
85
+ if custom_serializer [0 ] != custom_type_name :
86
+ raise NotImplementedError (f"No deserializer found for JanusGraph type with name: { custom_type_name } " )
87
+
88
+ return custom_serializer [1 ].objectify (b , r )
89
+
90
+ @classmethod
91
+ def prefix_bytes_custom_type (cls , writer , to_extend , as_value = False ):
92
+ """
93
+ Helper method to add a specific byte array prefix while serializing
94
+ JanusGraph-specific type as custom type
95
+ """
96
+ if to_extend is None :
97
+ to_extend = bytearray ()
98
+
99
+ # use the custom type code
100
+ if not as_value :
101
+ to_extend += uint8_pack (DataType .custom .value )
102
+
103
+ # add the name of the custom JanusGraph type
104
+ StringIO .dictify (cls .graphbinary_type_name , writer , to_extend , True , False )
105
+
106
+ # add the id of the custom JanusGraph type
107
+ to_extend += uint32_pack (cls .graphbinary_type_id )
108
+
109
+ # use the custom type code
110
+ if not as_value :
111
+ to_extend += uint8_pack (DataType .custom .value )
112
+
113
+ class JanusGraphPSerializer (JanusGraphBinaryTypeIO ):
114
+ graphbinary_type_id = 0x1002
115
+ graphbinary_type_name = "janusgraph.P"
116
+ python_type = _JanusGraphP
117
+
118
+ @classmethod
119
+ def dictify (cls , obj , writer , to_extend , as_value = False , nullable = True ):
120
+ """
121
+ Method to serialize JanusGraph-specific Text predicate
122
+ """
123
+ cls .prefix_bytes_custom_type (writer , to_extend , as_value )
124
+
125
+ # serialize the custom JanusGraph operator
126
+ StringIO .dictify (obj .operator , writer , to_extend , True , False )
127
+
128
+ # serialize the value
129
+ writer .to_dict (obj .value , to_extend )
130
+
131
+ return to_extend
132
+
133
+ class JanusGraphRelationIdentifierIO (JanusGraphBinaryTypeIO ):
134
+ graphbinary_type_id = 0x1001
135
+ graphbinary_type_name = "janusgraph.RelationIdentifier"
136
+ python_type = RelationIdentifier
137
+
138
+ long_marker = 0
139
+ string_marker = 1
140
+
141
+ @classmethod
142
+ def dictify (cls , obj , writer , to_extend , as_value = False , nullable = True ):
143
+ """
144
+ Method to serialize JanusGraph-specific RelationIdentifier
145
+ """
146
+ cls .prefix_bytes_custom_type (writer , to_extend , as_value )
147
+
148
+ # serialize out vertex ID
149
+ if isinstance (obj .out_vertex_id , int ):
150
+ to_extend += uint8_pack (cls .long_marker )
151
+ to_extend += uint64_pack (obj .out_vertex_id )
152
+ else :
153
+ to_extend += uint8_pack (cls .string_marker )
154
+ cls ._write_string (obj .out_vertex_id , writer , to_extend )
155
+
156
+ # serialize edge type ID and relation ID
157
+ to_extend += uint64_pack (obj .type_id )
158
+ to_extend += uint64_pack (obj .relation_id )
159
+
160
+ # serialize in vertex ID
161
+ if obj .in_vertex_id is None :
162
+ to_extend += uint8_pack (cls .long_marker )
163
+ to_extend += uint64_pack (0 )
164
+ elif isinstance (obj .in_vertex_id , int ):
165
+ to_extend += uint8_pack (cls .long_marker )
166
+ to_extend += uint64_pack (obj .in_vertex_id )
167
+ else :
168
+ to_extend += uint8_pack (cls .string_marker )
169
+ cls ._write_string (obj .in_vertex_id , writer , to_extend )
170
+
171
+ return to_extend
172
+
173
+ @classmethod
174
+ def objectify (cls , b , r ):
175
+ """
176
+ Method to deserialize JanusGraph-specific RelationIdentifier
177
+ """
178
+ if uint8_unpack (b .read (1 )) != DataType .custom .value :
179
+ raise Exception ("Unexpected type while deserializing JanusGraph RelationIdentifier" )
180
+
181
+ # read the next byte that shows if the out vertex id is string or long
182
+ out_vertex_id_marker = uint8_unpack (b .read (1 ))
183
+
184
+ # deserialize out vertex ID
185
+ if out_vertex_id_marker == cls .string_marker :
186
+ out_vertex_id = cls ._read_string (b )
187
+ else :
188
+ out_vertex_id = uint64_unpack (b .read (8 ))
189
+
190
+ # deserialize edge type ID and relation ID
191
+ type_id = uint64_unpack (b .read (8 ))
192
+ relation_id = uint64_unpack (b .read (8 ))
193
+
194
+ # deserialize in vertex ID
195
+ in_vertex_id_marker = uint8_unpack (b .read (1 ))
196
+ if in_vertex_id_marker == cls .string_marker :
197
+ in_vertex_id = cls ._read_string (b )
198
+ else :
199
+ in_vertex_id = uint64_unpack (b .read (8 ))
200
+ if in_vertex_id == 0 :
201
+ in_vertex_id = None
202
+
203
+ return RelationIdentifier .from_ids (out_vertex_id , type_id , relation_id , in_vertex_id )
204
+
205
+ @classmethod
206
+ def _read_string (cls , buff ):
207
+ """
208
+ Helper method to read a string represented as byte array.
209
+ The length of the string is not known upfront so the byte
210
+ array needs to be red until a byte occurs that is marked
211
+ with a special end marker
212
+ """
213
+ final_string = ""
214
+ while True :
215
+ c = 0xFF & uint8_unpack (buff .read (1 ))
216
+ final_string += chr (c & 0x7F )
217
+
218
+ # check if the character is marked with end marker
219
+ # if yes that is the end of the string
220
+ if c & 0x80 > 0 :
221
+ break
222
+
223
+ return final_string
224
+
225
+ @classmethod
226
+ def _write_string (cls , string , writer , to_extend ):
227
+ """
228
+ Helper method to create a byte array from a string and
229
+ mark the string's last character with special end marker
230
+ """
231
+ b = bytearray ()
232
+ b .extend (map (ord , string ))
233
+
234
+ # add end marker to the last character
235
+ b [- 1 ] |= 0x80
236
+
237
+ to_extend += b
238
+
239
+ # register the JanusGraph-specific RelationIdentifier as deserializer
240
+ JanusGraphBinaryTypeIO .register_deserializer (JanusGraphRelationIdentifierIO )
0 commit comments