1010from .constants import FIELD_TYPE
1111from .constants import BINLOG
1212from .constants import CHARSET
13+ from .constants import NONE_SOURCE
1314from .column import Column
1415from .table import Table
1516from .bitmap import BitCount , BitGet
@@ -23,6 +24,7 @@ def __init__(self, from_packet, event_size, table_map, ctl_connection, **kwargs)
2324 self .__ignored_tables = kwargs ["ignored_tables" ]
2425 self .__only_schemas = kwargs ["only_schemas" ]
2526 self .__ignored_schemas = kwargs ["ignored_schemas" ]
27+ self .__none_sources = {}
2628
2729 # Header
2830 self .table_id = self ._read_table_id ()
@@ -176,10 +178,15 @@ def __read_values_name(
176178 unsigned ,
177179 i ,
178180 ):
181+ name = self .table_map [self .table_id ].columns [i ].name
179182 if BitGet (cols_bitmap , i ) == 0 :
183+ # This block is only executed when binlog_row_image = MINIMAL.
184+ # When binlog_row_image = FULL, this block does not execute.
185+ self .__none_sources [name ] = NONE_SOURCE .COLS_BITMAP
180186 return None
181187
182188 if self ._is_null (null_bitmap , null_bitmap_index ):
189+ self .__none_sources [name ] = NONE_SOURCE .NULL
183190 return None
184191
185192 if column .type == FIELD_TYPE .TINY :
@@ -223,17 +230,26 @@ def __read_values_name(
223230 elif column .type == FIELD_TYPE .BLOB :
224231 return self .__read_string (column .length_size , column )
225232 elif column .type == FIELD_TYPE .DATETIME :
226- return self .__read_datetime ()
233+ ret = self .__read_datetime ()
234+ if ret is None :
235+ self .__none_sources [name ] = NONE_SOURCE .OUT_OF_DATETIME_RANGE
236+ return ret
227237 elif column .type == FIELD_TYPE .TIME :
228238 return self .__read_time ()
229239 elif column .type == FIELD_TYPE .DATE :
230- return self .__read_date ()
240+ ret = self .__read_date ()
241+ if ret is None :
242+ self .__none_sources [name ] = NONE_SOURCE .OUT_OF_DATE_RANGE
243+ return ret
231244 elif column .type == FIELD_TYPE .TIMESTAMP :
232245 return datetime .datetime .utcfromtimestamp (self .packet .read_uint32 ())
233246
234247 # For new date format:
235248 elif column .type == FIELD_TYPE .DATETIME2 :
236- return self .__read_datetime2 (column )
249+ ret = self .__read_datetime2 (column )
250+ if ret is None :
251+ self .__none_sources [name ] = NONE_SOURCE .OUT_OF_DATETIME2_RANGE
252+ return ret
237253 elif column .type == FIELD_TYPE .TIME2 :
238254 return self .__read_time2 (column )
239255 elif column .type == FIELD_TYPE .TIMESTAMP2 :
@@ -257,11 +273,16 @@ def __read_values_name(
257273 elif column .type == FIELD_TYPE .SET :
258274 bit_mask = self .packet .read_uint_by_size (column .size )
259275 if column .set_values :
260- return {
276+ ret = {
261277 val
262278 for idx , val in enumerate (column .set_values )
263279 if bit_mask & (1 << idx )
264- } or None
280+ }
281+ if not ret :
282+ self .__none_sources [column .name ] = NONE_SOURCE .EMPTY_SET
283+ return None
284+ return ret
285+ self .__none_sources [column .name ] = NONE_SOURCE .EMPTY_SET
265286 return None
266287 elif column .type == FIELD_TYPE .BIT :
267288 return self .__read_bit (column )
@@ -515,6 +536,16 @@ def _json_column_count(self):
515536 count += 1
516537 return count
517538
539+ def _get_none_sources (self , column_data ):
540+ result = {}
541+ for column_name , value in column_data .items ():
542+ if (column_name is None ) or (value is not None ):
543+ continue
544+
545+ source = self .__none_sources .get (column_name , "null" )
546+ result [column_name ] = source
547+ return result
548+
518549 def _dump (self ):
519550 super ()._dump ()
520551 print (f"Table: { self .schema } .{ self .table } " )
@@ -557,6 +588,8 @@ def _fetch_one_row(self):
557588 row = {}
558589
559590 row ["values" ] = self ._read_column_data (self .columns_present_bitmap )
591+ row ["none_sources" ] = self ._get_none_sources (row ["values" ])
592+
560593 return row
561594
562595 def _dump (self ):
@@ -565,7 +598,13 @@ def _dump(self):
565598 for row in self .rows :
566599 print ("--" )
567600 for key in row ["values" ]:
568- print (f"* { key } : { row ['values' ][key ]} " )
601+ none_source = (
602+ row ["none_sources" ][key ] if key in row ["none_sources" ] else ""
603+ )
604+ if none_source :
605+ print (f"* { key } : { row ['values' ][key ]} ({ none_source } )" )
606+ else :
607+ print (f"* { key } : { row ['values' ][key ]} " )
569608
570609
571610class WriteRowsEvent (RowsEvent ):
@@ -585,6 +624,8 @@ def _fetch_one_row(self):
585624 row = {}
586625
587626 row ["values" ] = self ._read_column_data (self .columns_present_bitmap )
627+ row ["none_sources" ] = self ._get_none_sources (row ["values" ])
628+
588629 return row
589630
590631 def _dump (self ):
@@ -593,7 +634,13 @@ def _dump(self):
593634 for row in self .rows :
594635 print ("--" )
595636 for key in row ["values" ]:
596- print (f"* { key } : { row ['values' ][key ]} " )
637+ none_source = (
638+ row ["none_sources" ][key ] if key in row ["none_sources" ] else ""
639+ )
640+ if none_source :
641+ print (f"* { key } : row['values'][key] ({ none_source } )" )
642+ else :
643+ print (f"* { key } : { row ['values' ][key ]} " )
597644
598645
599646class UpdateRowsEvent (RowsEvent ):
@@ -623,8 +670,9 @@ def _fetch_one_row(self):
623670 row = {}
624671
625672 row ["before_values" ] = self ._read_column_data (self .columns_present_bitmap )
626-
673+ row [ "before_none_sources" ] = self . _get_none_sources ( row [ "before_values" ])
627674 row ["after_values" ] = self ._read_column_data (self .columns_present_bitmap2 )
675+ row ["after_none_sources" ] = self ._get_none_sources (row ["after_values" ])
628676 return row
629677
630678 def _dump (self ):
@@ -633,7 +681,23 @@ def _dump(self):
633681 for row in self .rows :
634682 print ("--" )
635683 for key in row ["before_values" ]:
636- print (f"*{ key } :{ row ['before_values' ][key ]} =>{ row ['after_values' ][key ]} " )
684+ if key in row ["before_none_sources" ]:
685+ before_value_info = "%s(%s)" % (
686+ row ["before_values" ][key ],
687+ row ["before_none_sources" ][key ],
688+ )
689+ else :
690+ before_value_info = row ["before_values" ][key ]
691+
692+ if key in row ["after_none_sources" ]:
693+ after_value_info = "%s(%s)" % (
694+ row ["after_values" ][key ],
695+ row ["after_none_sources" ][key ],
696+ )
697+ else :
698+ after_value_info = row ["after_values" ][key ]
699+
700+ print (f"*{ key } :{ before_value_info } =>{ after_value_info } " )
637701
638702
639703class OptionalMetaData :
0 commit comments