1mod tunnel;
13use std::time::Duration;
14
15use aws_rds::RdsTokenError;
16pub use tunnel::{
17 Config, DEFAULT_CONNECT_TIMEOUT, DEFAULT_SNAPSHOT_LOCK_WAIT_TIMEOUT,
18 DEFAULT_SNAPSHOT_MAX_EXECUTION_TIME, DEFAULT_SNAPSHOT_WAIT_TIMEOUT, DEFAULT_TCP_KEEPALIVE,
19 MySqlConn, TimeoutConfig, TunnelConfig,
20};
21
22mod desc;
23pub use desc::{
24 MySqlColumnDesc, MySqlKeyDesc, MySqlTableDesc, ProtoMySqlColumnDesc, ProtoMySqlKeyDesc,
25 ProtoMySqlTableDesc,
26};
27
28pub mod schema_change;
29pub use schema_change::{SchemaChange, SchemaChangeError};
30
31mod replication;
32pub use replication::{
33 ensure_full_row_binlog_format, ensure_gtid_consistency, ensure_replication_commit_order,
34 query_sys_var,
35};
36
37pub mod schemas;
38pub use schemas::{
39 MySqlTableSchema, QualifiedTableRef, SYSTEM_SCHEMAS, SchemaRequest, schema_info,
40};
41
42pub mod privileges;
43pub use privileges::validate_source_privileges;
44
45pub mod decoding;
46pub use decoding::pack_mysql_row;
47
48pub mod probe;
49pub use probe::{KeyProber, MAX_KEY_LENGTH};
50
51pub mod partition;
52pub use partition::{PartitionParams, partition_table};
53
54mod aws_rds;
55
56#[derive(Debug, Clone)]
57pub struct UnsupportedDataType {
58 pub column_type: String,
59 pub qualified_table_name: String,
60 pub column_name: String,
61 pub intended_type: Option<String>,
62}
63
64impl std::fmt::Display for UnsupportedDataType {
65 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
66 match &self.intended_type {
67 Some(intended_type) => write!(
68 f,
69 "'{}.{}' of type '{}' represented as: '{}'",
70 self.qualified_table_name, self.column_name, self.column_type, intended_type
71 ),
72 None => write!(
73 f,
74 "'{}.{}' of type '{}'",
75 self.qualified_table_name, self.column_name, self.column_type
76 ),
77 }
78 }
79}
80
81#[derive(Debug, Clone)]
82pub struct MissingPrivilege {
83 pub privilege: String,
84 pub qualified_table_name: String,
85}
86
87impl std::fmt::Display for MissingPrivilege {
88 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
89 write!(
90 f,
91 "Missing privilege '{}' for '{}'",
92 self.privilege, self.qualified_table_name
93 )
94 }
95}
96
97#[derive(Debug, thiserror::Error)]
98pub enum MySqlError {
99 #[error("error validating privileges: {}", itertools::join(.0, ", "))]
100 MissingPrivileges(Vec<MissingPrivilege>),
101 #[error("error creating mysql connection with config: {0}")]
102 InvalidClientConfig(String),
103 #[error("error setting up ssh: {0}")]
104 Ssh(#[source] anyhow::Error),
105 #[error("error decoding value for '{qualified_table_name}' column '{column_name}': {error}")]
106 ValueDecodeError {
107 column_name: String,
108 qualified_table_name: String,
109 error: String,
110 },
111 #[error("non-UTF-8 key value in '{qualified_table_name}' column '{column_name}': {error}")]
112 NonUtf8KeyValue {
113 qualified_table_name: String,
114 column_name: String,
115 error: String,
116 },
117 #[error(
118 "missing row estimate in '{qualified_table_name}' for key range ({lower_bound}, {upper_bound})"
119 )]
120 MissingRowEstimate {
121 qualified_table_name: String,
122 lower_bound: String,
124 upper_bound: String,
126 },
127 #[error("unsupported data types: {}", itertools::join(.columns, ", "))]
128 UnsupportedDataTypes { columns: Vec<UnsupportedDataType> },
129 #[error(
130 "duplicated column names in table '{qualified_table_name}': {}",
131 itertools::join(.columns, ", ")
132 )]
133 DuplicatedColumnNames {
134 qualified_table_name: String,
135 columns: Vec<String>,
136 },
137 #[error("invalid mysql system setting '{setting}'. Expected '{expected}'. Got '{actual}'.")]
138 InvalidSystemSetting {
139 setting: String,
140 expected: String,
141 actual: String,
142 },
143 #[error(transparent)]
145 Generic(#[from] anyhow::Error),
146 #[error(transparent)]
148 MySql(#[from] mysql_async::Error),
149 #[error("connection attempt timed out after {0:?}")]
150 ConnectionTimeout(Duration),
151 #[error("connection attempt failed, the MySQL client panicked: {0}")]
154 ConnectionPanicked(String),
155 #[error(transparent)]
157 AwsTokenError(#[from] RdsTokenError),
158}
159
160pub fn quote_identifier(identifier: &str) -> String {
162 let mut escaped = identifier.replace("`", "``");
163 escaped.insert(0, '`');
164 escaped.push('`');
165 escaped
166}
167
168pub const ER_SOURCE_FATAL_ERROR_READING_BINLOG_CODE: u16 = 1236;
172
173pub const ER_NO_SUCH_TABLE: u16 = 1146;
175
176#[cfg(test)]
177mod tests {
178
179 use super::quote_identifier;
180 #[mz_ore::test]
181 fn test_identifier_quoting() {
182 let expected = vec!["`a`", "`naughty``sql`", "```;naughty;sql;```"];
183 let input = ["a", "naughty`sql", "`;naughty;sql;`"]
184 .iter()
185 .map(|raw_str| quote_identifier(raw_str))
186 .collect::<Vec<_>>();
187 assert_eq!(expected, input);
188 }
189}