Skip to content

Commit 6080b0b

Browse files
check optional metadata in handle_rows_event instead of decoding
1 parent 139ba39 commit 6080b0b

3 files changed

Lines changed: 37 additions & 37 deletions

File tree

src/mysql-util/src/decoding.rs

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,26 +33,6 @@ pub fn pack_mysql_row(
3333
) -> Result<Row, MySqlError> {
3434
let mut packer = row_container.packer();
3535

36-
// If a column name begins with '@', then the binlog does not have full row metadata,
37-
// meaning that full column names are not available and we need to rely on the order
38-
// of the columns in the upstream table matching the order of the columns in the row.
39-
// This is a fallback for MySQL servers that do not have `binlog_row_metadata` set to
40-
// `FULL`. If the first column name does not begin with '@', then we can assume that
41-
// full metadata is available and we can match columns by name.
42-
let fallback_names = row
43-
.columns_ref()
44-
.first()
45-
.is_some_and(|col| col.name_ref().starts_with(b"@"));
46-
47-
if binlog_full_metadata && fallback_names {
48-
// This should never happen, but if it does, it's a sign that something is very wrong with the MySQL server's binlog configuration. We want to error rather than silently producing incorrect results.
49-
return Err(MySqlError::ValueDecodeError {
50-
column_name: "<unknown>".to_string(),
51-
qualified_table_name: format!("{}.{}", table_desc.schema_name, table_desc.name),
52-
error: "Table 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".to_string(),
53-
});
54-
}
55-
5636
// For each column in `table_desc` (in descriptor order), resolve its wire
5737
// index. Non-fallback rows are matched by name so a reordered upstream
5838
// still decodes correctly; fallback rows have no names and are matched

src/storage/src/source/mysql/replication/events.rs

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
// by the Apache License, Version 2.0.
99

1010
use maplit::btreemap;
11+
use mysql_async::binlog::events::OptionalMetaExtractor;
1112
use mysql_common::binlog::events::{QueryEvent, RowsEventData};
1213
use mz_mysql_util::{MySqlError, pack_mysql_row};
1314
use mz_ore::iter::IteratorExt;
@@ -262,6 +263,11 @@ pub(super) async fn handle_rows_event(
262263
// Capability for this event.
263264
let gtid_cap = ctx.data_cap_set.delayed(new_gtid);
264265

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+
265271
// Iterate over the rows in this RowsEvent. Each row is a pair of 'before_row', 'after_row',
266272
// to accomodate for updates and deletes (which include a before_row),
267273
// and updates and inserts (which inclued an after row).
@@ -293,23 +299,34 @@ pub(super) async fn handle_rows_event(
293299
for (binlog_row, diff) in updates.into_iter().flatten() {
294300
let row = mysql_async::Row::try_from(binlog_row)?;
295301
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(&gtid_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(&gtid_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+
}
313330
};
314331

315332
let data = (output.output_index, event);

src/storage/src/source/mysql/snapshot.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,9 @@ pub(crate) fn render<'scope>(
417417
let row: MySqlRow = row;
418418
snapshot_staged += 1;
419419
for (output, row_val) in outputs.iter().repeat_clone(row) {
420+
// We don't need to verify if binlog_row_metadata matches the expected when snapshotting as
421+
// the snapshot query always returns rows with full metadata. If the output is configured
422+
// with binlog_full_metadata = false, then we will just ignore the metadata when decoding.
420423
let event = match pack_mysql_row(
421424
&mut final_row,
422425
row_val,

0 commit comments

Comments
 (0)