|
8 | 8 | // by the Apache License, Version 2.0. |
9 | 9 |
|
10 | 10 | use maplit::btreemap; |
| 11 | +use mysql_async::binlog::events::OptionalMetaExtractor; |
11 | 12 | use mysql_common::binlog::events::{QueryEvent, RowsEventData}; |
12 | 13 | use mz_mysql_util::{MySqlError, pack_mysql_row}; |
13 | 14 | use mz_ore::iter::IteratorExt; |
@@ -262,6 +263,11 @@ pub(super) async fn handle_rows_event( |
262 | 263 | // Capability for this event. |
263 | 264 | let gtid_cap = ctx.data_cap_set.delayed(new_gtid); |
264 | 265 |
|
| 266 | + // We can check here if the binlog has full row metadata by looking at the column name optional |
| 267 | + // metadata, which is only present if full metadata is enabled. |
| 268 | + let optional_metadata = OptionalMetaExtractor::new(table_map_event.iter_optional_meta())?; |
| 269 | + let has_full_metadata = optional_metadata.iter_column_name().next().is_some(); |
| 270 | + |
265 | 271 | // Iterate over the rows in this RowsEvent. Each row is a pair of 'before_row', 'after_row', |
266 | 272 | // to accomodate for updates and deletes (which include a before_row), |
267 | 273 | // and updates and inserts (which inclued an after row). |
@@ -293,23 +299,34 @@ pub(super) async fn handle_rows_event( |
293 | 299 | for (binlog_row, diff) in updates.into_iter().flatten() { |
294 | 300 | let row = mysql_async::Row::try_from(binlog_row)?; |
295 | 301 | for (output, row_val) in outputs.iter().repeat_clone(row) { |
296 | | - let event = match pack_mysql_row( |
297 | | - &mut final_row, |
298 | | - row_val, |
299 | | - &output.desc, |
300 | | - Some(>id_str), |
301 | | - output.binlog_full_metadata, |
302 | | - ) { |
303 | | - Ok(row) => Ok(SourceMessage { |
304 | | - key: Row::default(), |
305 | | - value: row, |
306 | | - metadata: Row::default(), |
307 | | - }), |
308 | | - // Produce a DefiniteError in the stream for any rows that fail to decode |
309 | | - Err(err @ MySqlError::ValueDecodeError { .. }) => Err(DataflowError::from( |
310 | | - DefiniteError::ValueDecodeError(err.to_string()), |
311 | | - )), |
312 | | - Err(err) => Err(err)?, |
| 302 | + let event = if !has_full_metadata && output.binlog_full_metadata { |
| 303 | + tracing::warn!(%id, "timely-{worker_id} missing full metadata for {table:?} \ |
| 304 | + - this can lead to incorrect decoding of some data types. This metadata is only available on MySQL 8.0+ with binlog_version=2, and must be enabled with the binlog_row_metadata configuration option."); |
| 305 | + Err(DataflowError::from(DefiniteError::ValueDecodeError( |
| 306 | + format!( |
| 307 | + "Table {0} was created with binlog_row_metadata=FULL but binlog_row_metadata has since been set to a different value, meaning we cannot reliably decode the columns", |
| 308 | + output.table_name |
| 309 | + ), |
| 310 | + ))) |
| 311 | + } else { |
| 312 | + match pack_mysql_row( |
| 313 | + &mut final_row, |
| 314 | + row_val, |
| 315 | + &output.desc, |
| 316 | + Some(>id_str), |
| 317 | + output.binlog_full_metadata, |
| 318 | + ) { |
| 319 | + Ok(row) => Ok(SourceMessage { |
| 320 | + key: Row::default(), |
| 321 | + value: row, |
| 322 | + metadata: Row::default(), |
| 323 | + }), |
| 324 | + // Produce a DefiniteError in the stream for any rows that fail to decode |
| 325 | + Err(err @ MySqlError::ValueDecodeError { .. }) => Err(DataflowError::from( |
| 326 | + DefiniteError::ValueDecodeError(err.to_string()), |
| 327 | + )), |
| 328 | + Err(err) => Err(err)?, |
| 329 | + } |
313 | 330 | }; |
314 | 331 |
|
315 | 332 | let data = (output.output_index, event); |
|
0 commit comments