fix(sql-file): import mysql dumps with binary literals

This commit is contained in:
zipg
2026-09-01 00:35:01 +08:00
committed by GitHub
parent 351a8b699b
commit 49926f25f1
4 changed files with 794 additions and 166 deletions
+1 -1
View File
@@ -2056,7 +2056,7 @@ pub(crate) fn supports_connection_level_database_bootstrap_target(
.is_some_and(|profile| is_mysql_compatible_import_profile(&profile) && profile != "manticoresearch")
}
fn is_mysql_compatible_import_target(db_type: &DatabaseType, driver_profile: Option<&str>) -> bool {
pub(crate) fn is_mysql_compatible_import_target(db_type: &DatabaseType, driver_profile: Option<&str>) -> bool {
matches!(
db_type,
DatabaseType::Mysql
+473 -20
View File
@@ -431,7 +431,11 @@ pub async fn execute_sql_file_paths(
let mut splitter = StreamingSqlFileSplitter::new(database_type, options);
let mut pending_statements = Vec::with_capacity(SQL_FILE_STATEMENT_BATCH_SIZE);
let mut decoder = match SqlFileStreamDecoder::open(file_path).await {
let normalize_mysql_binary_literals = import_target.as_ref().is_some_and(|target| {
crate::sql::is_mysql_compatible_import_target(&target.db_type, target.driver_profile.as_deref())
});
let mut decoder = match SqlFileStreamDecoder::open_for_target(file_path, normalize_mysql_binary_literals).await
{
Ok(decoder) => decoder,
Err(error) => {
emit(sql_file_execution_error_progress(&request.execution_id, started_at, &progress, error.clone()));
@@ -539,7 +543,9 @@ pub async fn read_sql_file_preview(file_path: &Path, max_chars: usize) -> Result
struct SqlFileStreamDecoder {
reader: SqlFileByteReader,
decoder: encoding_rs::Decoder,
mysql_binary_normalizer: Option<MysqlDumpBinaryLiteralNormalizer>,
pending_bytes: Vec<u8>,
pending_decoded_bytes: Vec<u8>,
reached_eof: bool,
}
@@ -600,23 +606,46 @@ fn is_gzip_sql_file_path(path: &Path) -> bool {
}
impl SqlFileStreamDecoder {
#[cfg(test)]
async fn open(file_path: &Path) -> Result<Self, String> {
Self::open_with_detection_limit(file_path, None).await
Self::open_with_options(file_path, None, false).await
}
async fn open_with_detection_limit(file_path: &Path, detection_limit: Option<usize>) -> Result<Self, String> {
let (encoding, bom_len) = detect_sql_file_encoding(file_path, detection_limit).await?;
Self::open_with_options(file_path, detection_limit, false).await
}
async fn open_for_target(file_path: &Path, normalize_mysql_binary_literals: bool) -> Result<Self, String> {
Self::open_with_options(file_path, None, normalize_mysql_binary_literals).await
}
async fn open_with_options(
file_path: &Path,
detection_limit: Option<usize>,
normalize_mysql_binary_literals: bool,
) -> Result<Self, String> {
let (encoding, bom_len, detected_mysql_binary_literals) =
detect_sql_file_encoding(file_path, detection_limit).await?;
let mut reader = SqlFileByteReader::open(file_path).await?;
let mut prefix = [0u8; 3];
let prefix_len = reader.read(&mut prefix).await?;
let prefix = &prefix[..prefix_len];
let mut pending_bytes = prefix[bom_len..].to_vec();
pending_bytes.reserve(SQL_FILE_READ_CHUNK_BYTES);
Ok(Self { reader, decoder: encoding.new_decoder_without_bom_handling(), pending_bytes, reached_eof: false })
Ok(Self {
reader,
decoder: encoding.new_decoder_without_bom_handling(),
mysql_binary_normalizer: (encoding == encoding_rs::UTF_8
&& (normalize_mysql_binary_literals || detected_mysql_binary_literals))
.then(MysqlDumpBinaryLiteralNormalizer::default),
pending_bytes,
pending_decoded_bytes: Vec::new(),
reached_eof: false,
})
}
async fn next_chunk(&mut self) -> Result<Option<String>, String> {
if self.reached_eof && self.pending_bytes.is_empty() {
if self.reached_eof && self.pending_bytes.is_empty() && self.pending_decoded_bytes.is_empty() {
return Ok(None);
}
while !self.reached_eof && self.pending_bytes.len() < SQL_FILE_READ_CHUNK_BYTES {
@@ -629,14 +658,22 @@ impl SqlFileStreamDecoder {
self.pending_bytes.extend_from_slice(&buffer[..read]);
}
let normalized = if let Some(normalizer) = self.mysql_binary_normalizer.as_mut() {
let input = normalizer.normalize(&self.pending_bytes, self.reached_eof)?;
self.pending_bytes.clear();
input
} else {
std::mem::take(&mut self.pending_bytes)
};
let mut input = std::mem::take(&mut self.pending_decoded_bytes);
input.extend_from_slice(&normalized);
let mut output = String::with_capacity(
self.decoder
.max_utf8_buffer_length_without_replacement(self.pending_bytes.len())
.unwrap_or(self.pending_bytes.len()),
self.decoder.max_utf8_buffer_length_without_replacement(input.len()).unwrap_or(input.len()),
);
let (result, read) =
self.decoder.decode_to_string_without_replacement(&self.pending_bytes, &mut output, self.reached_eof);
self.pending_bytes.drain(..read);
let (result, read) = self.decoder.decode_to_string_without_replacement(&input, &mut output, self.reached_eof);
if read < input.len() {
self.pending_decoded_bytes.extend_from_slice(&input[read..]);
}
match result {
encoding_rs::DecoderResult::InputEmpty => Ok((!output.is_empty()).then_some(output)),
encoding_rs::DecoderResult::OutputFull => Ok(Some(output)),
@@ -645,29 +682,256 @@ impl SqlFileStreamDecoder {
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
enum MysqlDumpBinaryLiteralState {
#[default]
Normal,
SingleQuoted,
DoubleQuoted,
BacktickQuoted,
LineComment,
BlockComment,
BinaryQuoted,
}
#[derive(Debug, Default)]
struct MysqlDumpBinaryLiteralNormalizer {
state: MysqlDumpBinaryLiteralState,
pending: Vec<u8>,
previous_source_byte: Option<u8>,
quoted_escape_pending: bool,
}
impl MysqlDumpBinaryLiteralNormalizer {
fn normalize(&mut self, input: &[u8], eof: bool) -> Result<Vec<u8>, String> {
self.pending.extend_from_slice(input);
let mut output = Vec::with_capacity(self.pending.len());
let mut index = 0;
while index < self.pending.len() {
let byte = self.pending[index];
match self.state {
MysqlDumpBinaryLiteralState::Normal => {
if byte == b'_' {
if self.pending.len() - index < b"_binary".len() {
if !eof {
break;
}
} else if self.pending[index..index + 7].eq_ignore_ascii_case(b"_binary")
&& !is_sql_identifier_byte(self.source_byte_before(index))
{
let mut quote_index = index + 7;
while quote_index < self.pending.len() && self.pending[quote_index].is_ascii_whitespace() {
quote_index += 1;
}
if quote_index == self.pending.len() && !eof {
break;
}
if self.pending.get(quote_index) == Some(&b'\'') {
output.extend_from_slice(b"X'");
self.state = MysqlDumpBinaryLiteralState::BinaryQuoted;
self.previous_source_byte = Some(b'\'');
index = quote_index + 1;
continue;
}
}
}
if byte == b'-' && self.pending.get(index + 1) == Some(&b'-') {
// MySQL requires whitespace or a control character after `--`
// to open a line comment: `5--1` is subtraction, matching the
// splitter's dash_dash_starts_line_comment rule.
let byte_after_dashes = self.pending.get(index + 2).copied();
if byte_after_dashes.is_none() && !eof {
break;
}
output.extend_from_slice(b"--");
self.previous_source_byte = Some(b'-');
index += 2;
if byte_after_dashes.is_none_or(|byte| byte.is_ascii_whitespace() || byte.is_ascii_control()) {
self.state = MysqlDumpBinaryLiteralState::LineComment;
}
} else if byte == b'-' && self.pending.get(index + 1).is_none() && !eof {
break;
} else if byte == b'#' {
output.push(byte);
self.state = MysqlDumpBinaryLiteralState::LineComment;
self.previous_source_byte = Some(byte);
index += 1;
} else if byte == b'/' && self.pending.get(index + 1) == Some(&b'*') {
output.extend_from_slice(b"/*");
self.state = MysqlDumpBinaryLiteralState::BlockComment;
self.previous_source_byte = Some(b'*');
index += 2;
} else if byte == b'/' && self.pending.get(index + 1).is_none() && !eof {
break;
} else {
output.push(byte);
self.previous_source_byte = Some(byte);
self.state = match byte {
b'\'' => MysqlDumpBinaryLiteralState::SingleQuoted,
b'"' => MysqlDumpBinaryLiteralState::DoubleQuoted,
b'`' => MysqlDumpBinaryLiteralState::BacktickQuoted,
_ => MysqlDumpBinaryLiteralState::Normal,
};
index += 1;
}
}
MysqlDumpBinaryLiteralState::SingleQuoted
| MysqlDumpBinaryLiteralState::DoubleQuoted
| MysqlDumpBinaryLiteralState::BacktickQuoted => {
let quote = match self.state {
MysqlDumpBinaryLiteralState::SingleQuoted => b'\'',
MysqlDumpBinaryLiteralState::DoubleQuoted => b'"',
MysqlDumpBinaryLiteralState::BacktickQuoted => b'`',
_ => unreachable!(),
};
if byte == quote && self.pending.get(index + 1).is_none() && !eof {
break;
}
output.push(byte);
self.previous_source_byte = Some(byte);
index += 1;
if self.quoted_escape_pending {
self.quoted_escape_pending = false;
} else if byte == b'\\' {
if let Some(escaped) = self.pending.get(index) {
output.push(*escaped);
self.previous_source_byte = Some(*escaped);
index += 1;
} else if !eof {
self.quoted_escape_pending = true;
}
} else if byte == quote {
if self.pending.get(index) == Some(&quote) {
output.push(quote);
self.previous_source_byte = Some(quote);
index += 1;
} else {
self.state = MysqlDumpBinaryLiteralState::Normal;
}
}
}
MysqlDumpBinaryLiteralState::LineComment => {
output.push(byte);
self.previous_source_byte = Some(byte);
index += 1;
if byte == b'\n' {
self.state = MysqlDumpBinaryLiteralState::Normal;
}
}
MysqlDumpBinaryLiteralState::BlockComment => {
if byte == b'*' && self.pending.get(index + 1).is_none() && !eof {
break;
}
output.push(byte);
self.previous_source_byte = Some(byte);
index += 1;
if byte == b'*' && self.pending.get(index) == Some(&b'/') {
output.push(b'/');
self.previous_source_byte = Some(b'/');
index += 1;
self.state = MysqlDumpBinaryLiteralState::Normal;
}
}
MysqlDumpBinaryLiteralState::BinaryQuoted => {
if byte == b'\\' {
let Some(escaped) = self.pending.get(index + 1).copied() else {
if !eof {
break;
}
return Err("Unterminated MySQL binary literal".to_string());
};
let value = match escaped {
b'0' => 0x00,
b'b' => 0x08,
b'n' => b'\n',
b'r' => b'\r',
b't' => b'\t',
b'Z' => 0x1A,
b'\\' => b'\\',
b'\'' => b'\'',
b'"' => b'"',
other => other,
};
append_hex(&mut output, value);
self.previous_source_byte = Some(escaped);
index += 2;
} else if byte == b'\'' && self.pending.get(index + 1).is_none() && !eof {
break;
} else if byte == b'\'' {
if self.pending.get(index + 1) == Some(&b'\'') {
append_hex(&mut output, b'\'');
self.previous_source_byte = Some(b'\'');
index += 2;
} else {
output.push(b'\'');
self.previous_source_byte = Some(b'\'');
self.state = MysqlDumpBinaryLiteralState::Normal;
index += 1;
}
} else {
append_hex(&mut output, byte);
self.previous_source_byte = Some(byte);
index += 1;
}
}
}
}
self.pending.drain(..index);
if eof {
if self.state == MysqlDumpBinaryLiteralState::BinaryQuoted {
return Err("Unterminated MySQL binary literal".to_string());
}
if !self.pending.is_empty() {
output.extend_from_slice(&self.pending);
self.pending.clear();
}
}
Ok(output)
}
fn source_byte_before(&self, index: usize) -> Option<u8> {
if index > 0 {
self.pending.get(index - 1).copied()
} else {
self.previous_source_byte
}
}
}
fn is_sql_identifier_byte(byte: Option<u8>) -> bool {
byte.is_some_and(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'$'))
}
fn append_hex(output: &mut Vec<u8>, byte: u8) {
const HEX: &[u8; 16] = b"0123456789ABCDEF";
output.push(HEX[(byte >> 4) as usize]);
output.push(HEX[(byte & 0x0F) as usize]);
}
async fn detect_sql_file_encoding(
file_path: &Path,
detection_limit: Option<usize>,
) -> Result<(&'static encoding_rs::Encoding, usize), String> {
) -> Result<(&'static encoding_rs::Encoding, usize, bool), String> {
let mut file = SqlFileByteReader::open(file_path).await?;
let mut prefix = [0u8; 3];
let prefix_len = file.read(&mut prefix).await.map_err(|error| error.to_string())?;
let prefix = &prefix[..prefix_len];
if prefix.starts_with(&[0xEF, 0xBB, 0xBF]) {
return Ok((encoding_rs::UTF_8, 3));
}
if prefix.starts_with(&[0xFF, 0xFE]) {
return Ok((encoding_rs::UTF_16LE, 2));
return Ok((encoding_rs::UTF_16LE, 2, false));
}
if prefix.starts_with(&[0xFE, 0xFF]) {
return Ok((encoding_rs::UTF_16BE, 2));
return Ok((encoding_rs::UTF_16BE, 2, false));
}
let bom_len = usize::from(prefix.starts_with(&[0xEF, 0xBB, 0xBF])) * 3;
// SQL dumps often begin with ASCII comments even when the remaining file
// is GBK. Validate the entire stream as UTF-8 with bounded buffers before
// falling back to the legacy GBK behavior.
let mut decoder = encoding_rs::UTF_8.new_decoder_without_bom_handling();
let mut input = prefix.to_vec();
let mut input = prefix[bom_len..].to_vec();
let mut inspected_bytes = prefix.len();
let mut reached_eof = false;
loop {
@@ -690,15 +954,70 @@ async fn detect_sql_file_encoding(
let (result, read) = decoder.decode_to_string_without_replacement(&input, &mut output, reached_eof);
input.drain(..read);
match result {
encoding_rs::DecoderResult::Malformed(_, _) => return Ok((encoding_rs::GBK, 0)),
encoding_rs::DecoderResult::Malformed(_, _) => {
if validate_utf8_with_mysql_binary_literals(file_path, detection_limit).await? {
return Ok((encoding_rs::UTF_8, bom_len, true));
}
return Ok(if bom_len > 0 {
(encoding_rs::UTF_8, bom_len, false)
} else {
(encoding_rs::GBK, 0, false)
});
}
encoding_rs::DecoderResult::InputEmpty if reached_eof || reached_detection_limit => {
return Ok((encoding_rs::UTF_8, 0));
return Ok((encoding_rs::UTF_8, bom_len, false));
}
encoding_rs::DecoderResult::InputEmpty | encoding_rs::DecoderResult::OutputFull => {}
}
}
}
async fn validate_utf8_with_mysql_binary_literals(
file_path: &Path,
detection_limit: Option<usize>,
) -> Result<bool, String> {
let mut file = SqlFileByteReader::open(file_path).await?;
let mut normalizer = MysqlDumpBinaryLiteralNormalizer::default();
let mut decoder = encoding_rs::UTF_8.new_decoder_without_bom_handling();
let mut input = Vec::new();
let mut inspected_bytes = 0;
let mut reached_eof = false;
loop {
let reached_detection_limit = detection_limit.is_some_and(|limit| inspected_bytes >= limit);
if !reached_eof && !reached_detection_limit {
let remaining =
detection_limit.map(|limit| limit.saturating_sub(inspected_bytes)).unwrap_or(SQL_FILE_READ_CHUNK_BYTES);
let mut buffer = vec![0u8; SQL_FILE_READ_CHUNK_BYTES.min(remaining.max(1))];
let read = file.read(&mut buffer).await?;
if read == 0 {
reached_eof = true;
} else {
inspected_bytes += read;
input.extend_from_slice(&buffer[..read]);
}
}
let normalized = match normalizer.normalize(&input, reached_eof) {
Ok(normalized) => normalized,
Err(_) => return Ok(false),
};
input.clear();
let mut output = String::with_capacity(
decoder
.max_utf8_buffer_length_without_replacement(normalized.len())
.unwrap_or(normalized.len().saturating_mul(3)),
);
let (result, _) = decoder.decode_to_string_without_replacement(&normalized, &mut output, reached_eof);
if !matches!(result, encoding_rs::DecoderResult::InputEmpty) {
return Ok(false);
}
if reached_eof || reached_detection_limit {
return Ok(true);
}
}
}
enum StreamingSqlFileSplitter {
Statements(SqlStatementSplitter),
SqlServerBatches(SqlServerBatchSplitter),
@@ -1794,6 +2113,140 @@ mod tests {
assert_eq!(decoded, "SELECT 'compressed';\n");
}
#[tokio::test]
async fn mysql_target_decoder_preserves_utf16le_bom_files() {
let mut bytes = vec![0xFF, 0xFE];
for unit in "SELECT _binary 'abc', '中文';".encode_utf16() {
bytes.extend_from_slice(&unit.to_le_bytes());
}
let path = temporary_sql_file(&bytes).await;
let mut decoder = SqlFileStreamDecoder::open_for_target(&path, true).await.unwrap();
let mut decoded = String::new();
while let Some(chunk) = decoder.next_chunk().await.unwrap() {
decoded.push_str(&chunk);
}
tokio::fs::remove_file(path).await.unwrap();
assert_eq!(decoded, "SELECT _binary 'abc', '中文';");
}
#[tokio::test]
async fn mysql_target_decoder_preserves_gbk_files() {
let sql = "INSERT INTO t VALUES ('中文');";
let (encoded, _, _) = encoding_rs::GBK.encode(sql);
let path = temporary_sql_file(encoded.as_ref()).await;
let mut decoder = SqlFileStreamDecoder::open_for_target(&path, true).await.unwrap();
let mut decoded = String::new();
while let Some(chunk) = decoder.next_chunk().await.unwrap() {
decoded.push_str(&chunk);
}
tokio::fs::remove_file(path).await.unwrap();
assert_eq!(decoded, sql);
}
#[test]
fn mysql_binary_literal_normalizer_preserves_bytes_and_mysql_escapes() {
let mut source = b"INSERT INTO t VALUES (_binary '".to_vec();
source.extend_from_slice(&[0xAC, b'\\', 0xED, b'\\', b'0', b'\\', b'n', b'\\', b'\'', b'\\', b'\\']);
source.extend_from_slice(b"'); -- _binary '\\xFF'\nSELECT 'not _binary x';");
let mut normalizer = MysqlDumpBinaryLiteralNormalizer::default();
let mut normalized = Vec::new();
for chunk in source.chunks(3) {
normalized.extend(normalizer.normalize(chunk, false).unwrap());
}
normalized.extend(normalizer.normalize(&[], true).unwrap());
assert_eq!(
String::from_utf8(normalized).unwrap(),
"INSERT INTO t VALUES (X'ACED000A275C'); -- _binary '\\xFF'\nSELECT 'not _binary x';"
);
}
#[tokio::test]
async fn streaming_decoder_recovers_utf8_mysql_dump_with_raw_binary_bytes() {
let mut bytes = b"-- MySQL dump\nINSERT INTO t VALUES (_binary '".to_vec();
bytes.extend_from_slice(&[0xAC, b'\\', 0xED, b'\\', b'0', 0x05]);
bytes.extend_from_slice(b"', '\xE4\xB8\xAD\xE6\x96\x87');\n");
let path = temporary_sql_file(&bytes).await;
let mut decoder = SqlFileStreamDecoder::open(&path).await.unwrap();
let mut decoded = String::new();
while let Some(chunk) = decoder.next_chunk().await.unwrap() {
decoded.push_str(&chunk);
}
tokio::fs::remove_file(path).await.unwrap();
assert_eq!(decoded, "-- MySQL dump\nINSERT INTO t VALUES (X'ACED0005', '中文');\n");
}
#[tokio::test]
async fn streaming_decoder_recovers_bom_utf8_mysql_dump_with_raw_binary_bytes() {
let mut bytes = vec![0xEF, 0xBB, 0xBF];
bytes.extend_from_slice(b"INSERT INTO t VALUES (_binary '");
bytes.extend_from_slice(&[0xAC, b'\\', 0xED, b'\\', b'0', 0x05]);
bytes.extend_from_slice(b"');\n");
let path = temporary_sql_file(&bytes).await;
let mut decoder = SqlFileStreamDecoder::open(&path).await.unwrap();
let mut decoded = String::new();
while let Some(chunk) = decoder.next_chunk().await.unwrap() {
decoded.push_str(&chunk);
}
tokio::fs::remove_file(path).await.unwrap();
assert_eq!(decoded, "INSERT INTO t VALUES (X'ACED0005');\n");
}
#[tokio::test]
async fn streaming_decoder_recovers_gzip_mysql_dump_with_raw_binary_bytes() {
// Encoding detection and binary-literal validation must run on the
// decompressed bytes, not on the raw gzip stream.
let mut bytes = b"-- MySQL dump\nINSERT INTO t VALUES (_binary '".to_vec();
bytes.extend_from_slice(&[0xAC, b'\\', 0xED, b'\\', b'0', 0x05]);
bytes.extend_from_slice(b"', '\xE4\xB8\xAD\xE6\x96\x87');\n");
let path = temporary_gzip_sql_file(&bytes).await;
let mut decoder = SqlFileStreamDecoder::open(&path).await.unwrap();
let mut decoded = String::new();
while let Some(chunk) = decoder.next_chunk().await.unwrap() {
decoded.push_str(&chunk);
}
tokio::fs::remove_file(path).await.unwrap();
assert_eq!(decoded, "-- MySQL dump\nINSERT INTO t VALUES (X'ACED0005', '中文');\n");
}
#[test]
fn mysql_binary_literal_normalizer_does_not_rewrite_comments_or_strings() {
let source = b"-- _binary '\xAC'\nSELECT '_binary \'x\'', `col_binary`; /* _binary '\xAC' */";
let mut normalizer = MysqlDumpBinaryLiteralNormalizer::default();
let normalized = normalizer.normalize(source, true).unwrap();
assert_eq!(normalized, source);
}
#[test]
fn mysql_binary_literal_normalizer_rejects_unterminated_literal() {
let mut normalizer = MysqlDumpBinaryLiteralNormalizer::default();
let error = normalizer.normalize(b"SELECT _binary 'abc", true).unwrap_err();
assert_eq!(error, "Unterminated MySQL binary literal");
}
#[test]
fn mysql_binary_literal_normalizer_treats_dash_dash_digit_as_subtraction() {
// MySQL treats `--` as a comment opener only when followed by whitespace
// or a control character, so `5--1` is subtraction and the `_binary`
// literal later on the same line must still be normalized.
let mut source = b"SELECT 5--1, _binary '".to_vec();
source.extend_from_slice(&[0xAC, 0x05]);
source.extend_from_slice(b"';\n");
let mut normalizer = MysqlDumpBinaryLiteralNormalizer::default();
let normalized = normalizer.normalize(&source, true).unwrap();
assert_eq!(normalized, b"SELECT 5--1, X'AC05';\n".to_vec());
}
#[test]
fn non_sqlserver_sql_file_keeps_statement_splitting_behavior() {
assert_eq!(
+58
View File
@@ -1217,3 +1217,61 @@ INSERT INTO install_check (id) VALUES (1), (2);
assert_eq!(verify.columns, vec!["count"]);
assert_eq!(verify.rows, vec![vec![serde_json::json!("2")]]);
}
#[tokio::test]
#[ignore = "requires DBX_LIVE_SQL_FILE_MYSQL_* env vars pointing at a writable MySQL connection without a required default database"]
async fn live_sql_file_import_preserves_raw_mysql_binary_literal_bytes() {
let suffix = uuid::Uuid::new_v4().simple().to_string();
let config = live_mysql_sql_file_config(&format!("sql-file-binary-{suffix}"));
let (state, db_path) = app_state_with_config(config.clone()).await;
let database_name = std::env::var("DBX_LIVE_SQL_FILE_MYSQL_DATABASE").unwrap_or_else(|_| "dbx_test".to_string());
let table_name = format!("binary_dump_{suffix}");
let mut script = format!(
"USE `{database_name}`;\nCREATE TABLE `{table_name}` (id INT PRIMARY KEY, value LONGBLOB);\nINSERT INTO `{table_name}` VALUES (1, _binary '"
)
.into_bytes();
script.extend_from_slice(&[0xAC, b'\\', 0xED, b'\\', b'0', 0x05]);
script.extend_from_slice(b"');\n");
let request = SqlFileRequest {
execution_id: format!("exec-{suffix}"),
connection_id: config.id.clone(),
database: String::new(),
file_path: std::env::temp_dir().join(format!("mysql-binary-dump-{suffix}.sql")).to_string_lossy().into_owned(),
continue_on_error: false,
};
tokio::fs::write(&request.file_path, script).await.unwrap();
let result = execute_sql_file_path(
&state,
&request,
std::path::Path::new(&request.file_path),
CancellationToken::new(),
Instant::now(),
|_| {},
)
.await;
let verify = execute_sql_statement(
&state,
&config.id,
&database_name,
&format!("SELECT HEX(value) AS value_hex FROM `{table_name}` WHERE id = 1"),
None,
None,
)
.await;
let _ = execute_sql_statement(
&state,
&config.id,
&database_name,
&format!("DROP TABLE IF EXISTS `{table_name}`"),
None,
None,
)
.await;
let _ = std::fs::remove_file(db_path);
let _ = std::fs::remove_file(&request.file_path);
result.expect("binary SQL file import should succeed");
let verify = verify.expect("verify imported binary bytes");
assert_eq!(verify.rows, vec![vec![serde_json::json!("ACED0005")]]);
}
+262 -145
View File
@@ -1,70 +1,70 @@
{
"repository": "t8y2/dbx",
"generatedAt": "2026-08-23T18:05:28.346Z",
"stars": 16308,
"generatedAt": "2026-08-30T20:24:13.247Z",
"stars": 17473,
"contributors": [
{
"login": "t8y2",
"avatarUrl": "https://avatars.githubusercontent.com/u/77960507?v=4",
"profileUrl": "https://github.com/t8y2",
"commits": 3178,
"mergedPullRequests": 17,
"commits": 3310,
"mergedPullRequests": 23,
"firstContributionAt": "2026-04-30T08:14:51Z",
"latestContributionAt": "2026-08-18T04:05:42Z"
"latestContributionAt": "2026-08-30T05:30:01Z"
},
{
"login": "zipg",
"avatarUrl": "https://avatars.githubusercontent.com/u/4047349?v=4",
"profileUrl": "https://github.com/zipg",
"commits": 590,
"mergedPullRequests": 590,
"commits": 644,
"mergedPullRequests": 644,
"firstContributionAt": "2026-06-22T06:10:59Z",
"latestContributionAt": "2026-08-22T17:39:23Z"
"latestContributionAt": "2026-08-29T19:05:11Z"
},
{
"login": "eryajf",
"avatarUrl": "https://avatars.githubusercontent.com/u/33259379?v=4",
"profileUrl": "https://github.com/eryajf",
"commits": 166,
"mergedPullRequests": 165,
"commits": 191,
"mergedPullRequests": 190,
"firstContributionAt": "2026-06-08T11:45:45Z",
"latestContributionAt": "2026-08-22T18:53:52Z"
},
{
"login": "Abeautifulsnow",
"avatarUrl": "https://avatars.githubusercontent.com/u/28704977?v=4",
"profileUrl": "https://github.com/Abeautifulsnow",
"commits": 97,
"mergedPullRequests": 85,
"firstContributionAt": "2026-05-06T03:16:57Z",
"latestContributionAt": "2026-08-21T02:36:18Z"
"latestContributionAt": "2026-08-30T04:26:07Z"
},
{
"login": "q396921921",
"avatarUrl": "https://avatars.githubusercontent.com/u/37297228?v=4",
"profileUrl": "https://github.com/q396921921",
"commits": 88,
"mergedPullRequests": 88,
"commits": 104,
"mergedPullRequests": 104,
"firstContributionAt": "2026-07-20T13:41:21Z",
"latestContributionAt": "2026-08-23T04:25:45Z"
"latestContributionAt": "2026-08-27T14:25:09Z"
},
{
"login": "Abeautifulsnow",
"avatarUrl": "https://avatars.githubusercontent.com/u/28704977?v=4",
"profileUrl": "https://github.com/Abeautifulsnow",
"commits": 104,
"mergedPullRequests": 92,
"firstContributionAt": "2026-05-06T03:16:57Z",
"latestContributionAt": "2026-08-30T04:26:55Z"
},
{
"login": "CN-Scars",
"avatarUrl": "https://avatars.githubusercontent.com/u/57054419?v=4",
"profileUrl": "https://github.com/CN-Scars",
"commits": 68,
"mergedPullRequests": 68,
"commits": 74,
"mergedPullRequests": 74,
"firstContributionAt": "2026-05-30T10:02:05Z",
"latestContributionAt": "2026-08-22T07:41:21Z"
"latestContributionAt": "2026-08-28T08:51:34Z"
},
{
"login": "onenewcode",
"avatarUrl": "https://avatars.githubusercontent.com/u/94808747?v=4",
"profileUrl": "https://github.com/onenewcode",
"commits": 62,
"mergedPullRequests": 62,
"commits": 66,
"mergedPullRequests": 66,
"firstContributionAt": "2026-06-30T01:53:31Z",
"latestContributionAt": "2026-08-22T04:58:28Z"
"latestContributionAt": "2026-08-27T02:55:20Z"
},
{
"login": "serenez",
@@ -75,14 +75,23 @@
"firstContributionAt": "2026-04-30T04:27:54Z",
"latestContributionAt": "2026-07-20T15:31:27Z"
},
{
"login": "0verme",
"avatarUrl": "https://avatars.githubusercontent.com/u/24986278?v=4",
"profileUrl": "https://github.com/0verme",
"commits": 47,
"mergedPullRequests": 47,
"firstContributionAt": "2026-08-15T15:33:44Z",
"latestContributionAt": "2026-08-29T16:47:45Z"
},
{
"login": "onceMisery",
"avatarUrl": "https://avatars.githubusercontent.com/u/37446017?v=4",
"profileUrl": "https://github.com/onceMisery",
"commits": 45,
"mergedPullRequests": 43,
"commits": 46,
"mergedPullRequests": 44,
"firstContributionAt": "2026-06-10T14:19:43Z",
"latestContributionAt": "2026-08-23T04:23:46Z"
"latestContributionAt": "2026-08-24T10:35:18Z"
},
{
"login": "Illuminated2020",
@@ -97,10 +106,10 @@
"login": "mapan0424",
"avatarUrl": "https://avatars.githubusercontent.com/u/45232878?v=4",
"profileUrl": "https://github.com/mapan0424",
"commits": 34,
"mergedPullRequests": 34,
"commits": 36,
"mergedPullRequests": 36,
"firstContributionAt": "2026-07-17T05:42:50Z",
"latestContributionAt": "2026-08-20T07:14:20Z"
"latestContributionAt": "2026-08-25T07:15:42Z"
},
{
"login": "xddcode",
@@ -111,15 +120,6 @@
"firstContributionAt": "2026-07-10T06:34:09Z",
"latestContributionAt": "2026-08-20T02:23:08Z"
},
{
"login": "0verme",
"avatarUrl": "https://avatars.githubusercontent.com/u/24986278?v=4",
"profileUrl": "https://github.com/0verme",
"commits": 29,
"mergedPullRequests": 29,
"firstContributionAt": "2026-08-15T15:33:44Z",
"latestContributionAt": "2026-08-23T10:08:18Z"
},
{
"login": "gggaiitx",
"avatarUrl": "https://avatars.githubusercontent.com/u/62124152?v=4",
@@ -192,6 +192,15 @@
"firstContributionAt": "2026-06-17T08:11:49Z",
"latestContributionAt": "2026-06-26T10:48:46Z"
},
{
"login": "amwps290",
"avatarUrl": "https://avatars.githubusercontent.com/u/18211479?v=4",
"profileUrl": "https://github.com/amwps290",
"commits": 15,
"mergedPullRequests": 15,
"firstContributionAt": "2026-07-21T05:32:24Z",
"latestContributionAt": "2026-08-30T01:59:24Z"
},
{
"login": "jischeng",
"avatarUrl": "https://avatars.githubusercontent.com/u/49861575?v=4",
@@ -220,13 +229,22 @@
"latestContributionAt": "2026-07-21T16:25:09Z"
},
{
"login": "amwps290",
"avatarUrl": "https://avatars.githubusercontent.com/u/18211479?v=4",
"profileUrl": "https://github.com/amwps290",
"login": "mr-dadong",
"avatarUrl": "https://avatars.githubusercontent.com/u/102886401?v=4",
"profileUrl": "https://github.com/mr-dadong",
"commits": 14,
"mergedPullRequests": 14,
"firstContributionAt": "2026-08-17T07:04:42Z",
"latestContributionAt": "2026-08-28T03:11:35Z"
},
{
"login": "DASungta",
"avatarUrl": "https://avatars.githubusercontent.com/u/8896967?v=4",
"profileUrl": "https://github.com/DASungta",
"commits": 13,
"mergedPullRequests": 13,
"firstContributionAt": "2026-07-21T05:32:24Z",
"latestContributionAt": "2026-08-20T08:04:22Z"
"firstContributionAt": "2026-07-21T04:15:46Z",
"latestContributionAt": "2026-08-24T05:29:24Z"
},
{
"login": "SpencerZhang",
@@ -238,22 +256,13 @@
"latestContributionAt": "2026-07-20T08:54:52Z"
},
{
"login": "DASungta",
"avatarUrl": "https://avatars.githubusercontent.com/u/8896967?v=4",
"profileUrl": "https://github.com/DASungta",
"login": "GIWTO",
"avatarUrl": "https://avatars.githubusercontent.com/u/40267153?v=4",
"profileUrl": "https://github.com/GIWTO",
"commits": 12,
"mergedPullRequests": 12,
"firstContributionAt": "2026-07-21T04:15:46Z",
"latestContributionAt": "2026-08-23T04:24:47Z"
},
{
"login": "mr-dadong",
"avatarUrl": "https://avatars.githubusercontent.com/u/102886401?v=4",
"profileUrl": "https://github.com/mr-dadong",
"commits": 12,
"mergedPullRequests": 12,
"firstContributionAt": "2026-08-17T07:04:42Z",
"latestContributionAt": "2026-08-20T14:11:49Z"
"firstContributionAt": "2026-07-25T02:38:05Z",
"latestContributionAt": "2026-08-26T13:38:10Z"
},
{
"login": "dienaso",
@@ -318,15 +327,6 @@
"firstContributionAt": "2026-06-16T09:58:42Z",
"latestContributionAt": "2026-07-02T06:03:20Z"
},
{
"login": "GIWTO",
"avatarUrl": "https://avatars.githubusercontent.com/u/40267153?v=4",
"profileUrl": "https://github.com/GIWTO",
"commits": 10,
"mergedPullRequests": 10,
"firstContributionAt": "2026-07-25T02:38:05Z",
"latestContributionAt": "2026-08-19T12:23:17Z"
},
{
"login": "aiLi0617",
"avatarUrl": "https://avatars.githubusercontent.com/u/76681595?v=4",
@@ -363,6 +363,24 @@
"firstContributionAt": "2026-07-18T17:32:18Z",
"latestContributionAt": "2026-07-29T16:27:55Z"
},
{
"login": "difagume",
"avatarUrl": "https://avatars.githubusercontent.com/u/5271339?v=4",
"profileUrl": "https://github.com/difagume",
"commits": 7,
"mergedPullRequests": 7,
"firstContributionAt": "2026-07-31T04:15:42Z",
"latestContributionAt": "2026-08-29T20:17:33Z"
},
{
"login": "hanxuanyu",
"avatarUrl": "https://avatars.githubusercontent.com/u/21254318?v=4",
"profileUrl": "https://github.com/hanxuanyu",
"commits": 7,
"mergedPullRequests": 7,
"firstContributionAt": "2026-08-08T05:23:28Z",
"latestContributionAt": "2026-08-27T04:21:29Z"
},
{
"login": "Jinmoli",
"avatarUrl": "https://avatars.githubusercontent.com/u/49105556?v=4",
@@ -399,15 +417,6 @@
"firstContributionAt": "2026-06-11T15:15:39Z",
"latestContributionAt": "2026-07-28T19:47:20Z"
},
{
"login": "hanxuanyu",
"avatarUrl": "https://avatars.githubusercontent.com/u/21254318?v=4",
"profileUrl": "https://github.com/hanxuanyu",
"commits": 6,
"mergedPullRequests": 6,
"firstContributionAt": "2026-08-08T05:23:28Z",
"latestContributionAt": "2026-08-18T16:30:40Z"
},
{
"login": "jeeinn",
"avatarUrl": "https://avatars.githubusercontent.com/u/13930054?v=4",
@@ -435,6 +444,24 @@
"firstContributionAt": "2026-08-02T17:09:52Z",
"latestContributionAt": "2026-08-08T03:04:17Z"
},
{
"login": "sxhjlzl",
"avatarUrl": "https://avatars.githubusercontent.com/u/12149913?v=4",
"profileUrl": "https://github.com/sxhjlzl",
"commits": 6,
"mergedPullRequests": 6,
"firstContributionAt": "2026-08-19T03:31:47Z",
"latestContributionAt": "2026-08-26T05:52:42Z"
},
{
"login": "wzlstudy",
"avatarUrl": "https://avatars.githubusercontent.com/u/54768887?v=4",
"profileUrl": "https://github.com/wzlstudy",
"commits": 6,
"mergedPullRequests": 6,
"firstContributionAt": "2026-08-01T01:44:32Z",
"latestContributionAt": "2026-08-29T17:34:00Z"
},
{
"login": "ZionLin2016",
"avatarUrl": "https://avatars.githubusercontent.com/u/24632648?v=4",
@@ -453,15 +480,6 @@
"firstContributionAt": "2026-07-24T07:06:09Z",
"latestContributionAt": "2026-08-07T05:43:30Z"
},
{
"login": "difagume",
"avatarUrl": "https://avatars.githubusercontent.com/u/5271339?v=4",
"profileUrl": "https://github.com/difagume",
"commits": 5,
"mergedPullRequests": 5,
"firstContributionAt": "2026-07-31T04:15:42Z",
"latestContributionAt": "2026-08-15T06:41:54Z"
},
{
"login": "fraluc06",
"avatarUrl": "https://avatars.githubusercontent.com/u/67560467?v=4",
@@ -489,6 +507,24 @@
"firstContributionAt": "2026-07-15T03:09:27Z",
"latestContributionAt": "2026-08-18T15:17:24Z"
},
{
"login": "Lan-zk",
"avatarUrl": "https://avatars.githubusercontent.com/u/68137514?v=4",
"profileUrl": "https://github.com/Lan-zk",
"commits": 5,
"mergedPullRequests": 5,
"firstContributionAt": "2026-08-13T05:20:22Z",
"latestContributionAt": "2026-08-29T19:57:03Z"
},
{
"login": "lizzzzz777",
"avatarUrl": "https://avatars.githubusercontent.com/u/72645313?v=4",
"profileUrl": "https://github.com/lizzzzz777",
"commits": 5,
"mergedPullRequests": 5,
"firstContributionAt": "2026-08-14T01:26:28Z",
"latestContributionAt": "2026-08-25T06:46:25Z"
},
{
"login": "wbean",
"avatarUrl": "https://avatars.githubusercontent.com/u/2233455?v=4",
@@ -561,15 +597,6 @@
"firstContributionAt": "2026-06-08T17:32:08Z",
"latestContributionAt": "2026-08-06T04:13:57Z"
},
{
"login": "Lan-zk",
"avatarUrl": "https://avatars.githubusercontent.com/u/68137514?v=4",
"profileUrl": "https://github.com/Lan-zk",
"commits": 4,
"mergedPullRequests": 4,
"firstContributionAt": "2026-08-13T05:20:22Z",
"latestContributionAt": "2026-08-19T18:26:56Z"
},
{
"login": "soddygo",
"avatarUrl": "https://avatars.githubusercontent.com/u/13834644?v=4",
@@ -588,15 +615,6 @@
"firstContributionAt": "2026-07-22T04:43:40Z",
"latestContributionAt": "2026-07-31T05:54:26Z"
},
{
"login": "sxhjlzl",
"avatarUrl": "https://avatars.githubusercontent.com/u/12149913?v=4",
"profileUrl": "https://github.com/sxhjlzl",
"commits": 4,
"mergedPullRequests": 4,
"firstContributionAt": "2026-08-19T03:31:47Z",
"latestContributionAt": "2026-08-22T17:40:51Z"
},
{
"login": "wuxiemian",
"avatarUrl": "https://avatars.githubusercontent.com/u/29685283?v=4",
@@ -607,13 +625,13 @@
"latestContributionAt": "2026-07-12T13:30:37Z"
},
{
"login": "wzlstudy",
"avatarUrl": "https://avatars.githubusercontent.com/u/54768887?v=4",
"profileUrl": "https://github.com/wzlstudy",
"login": "xiaou61",
"avatarUrl": "https://avatars.githubusercontent.com/u/113765024?v=4",
"profileUrl": "https://github.com/xiaou61",
"commits": 4,
"mergedPullRequests": 4,
"firstContributionAt": "2026-08-01T01:44:32Z",
"latestContributionAt": "2026-08-18T07:36:37Z"
"firstContributionAt": "2026-08-14T09:19:51Z",
"latestContributionAt": "2026-08-28T04:36:37Z"
},
{
"login": "xzxlove",
@@ -723,6 +741,15 @@
"firstContributionAt": "2026-06-27T03:48:16Z",
"latestContributionAt": "2026-06-28T06:52:25Z"
},
{
"login": "klinge1011",
"avatarUrl": "https://avatars.githubusercontent.com/u/34301334?v=4",
"profileUrl": "https://github.com/klinge1011",
"commits": 3,
"mergedPullRequests": 3,
"firstContributionAt": "2026-08-21T12:23:37Z",
"latestContributionAt": "2026-08-25T14:35:40Z"
},
{
"login": "lazyanubis",
"avatarUrl": "https://avatars.githubusercontent.com/u/46840787?v=4",
@@ -733,13 +760,22 @@
"latestContributionAt": "2026-08-07T05:45:00Z"
},
{
"login": "lizzzzz777",
"avatarUrl": "https://avatars.githubusercontent.com/u/72645313?v=4",
"profileUrl": "https://github.com/lizzzzz777",
"login": "Mo-Moore",
"avatarUrl": "https://avatars.githubusercontent.com/u/33098900?v=4",
"profileUrl": "https://github.com/Mo-Moore",
"commits": 3,
"mergedPullRequests": 3,
"firstContributionAt": "2026-08-14T01:26:28Z",
"latestContributionAt": "2026-08-18T02:51:50Z"
"firstContributionAt": "2026-08-27T05:54:45Z",
"latestContributionAt": "2026-08-28T06:27:56Z"
},
{
"login": "ordinary-s",
"avatarUrl": "https://avatars.githubusercontent.com/u/59008530?v=4",
"profileUrl": "https://github.com/ordinary-s",
"commits": 3,
"mergedPullRequests": 3,
"firstContributionAt": "2026-08-18T11:36:48Z",
"latestContributionAt": "2026-08-26T14:00:04Z"
},
{
"login": "polemp",
@@ -1011,6 +1047,15 @@
"firstContributionAt": "2026-07-13T10:58:52Z",
"latestContributionAt": "2026-08-09T15:33:24Z"
},
{
"login": "StaravenX",
"avatarUrl": "https://avatars.githubusercontent.com/u/231610333?v=4",
"profileUrl": "https://github.com/StaravenX",
"commits": 2,
"mergedPullRequests": 2,
"firstContributionAt": "2026-08-26T11:48:56Z",
"latestContributionAt": "2026-08-28T07:29:07Z"
},
{
"login": "TangTangH",
"avatarUrl": "https://avatars.githubusercontent.com/u/31877158?v=4",
@@ -1083,15 +1128,6 @@
"firstContributionAt": "2026-07-03T06:18:00Z",
"latestContributionAt": "2026-07-03T11:25:02Z"
},
{
"login": "xiaou61",
"avatarUrl": "https://avatars.githubusercontent.com/u/113765024?v=4",
"profileUrl": "https://github.com/xiaou61",
"commits": 2,
"mergedPullRequests": 2,
"firstContributionAt": "2026-08-14T09:19:51Z",
"latestContributionAt": "2026-08-14T14:46:33Z"
},
{
"login": "yangqinlong",
"avatarUrl": "https://avatars.githubusercontent.com/u/7039386?v=4",
@@ -1191,6 +1227,15 @@
"firstContributionAt": "2026-05-10T12:02:47Z",
"latestContributionAt": "2026-05-10T12:53:40Z"
},
{
"login": "cdredfox",
"avatarUrl": "https://avatars.githubusercontent.com/u/315326?v=4",
"profileUrl": "https://github.com/cdredfox",
"commits": 1,
"mergedPullRequests": 1,
"firstContributionAt": "2026-08-27T05:57:53Z",
"latestContributionAt": "2026-08-27T09:16:58Z"
},
{
"login": "CGerAJ",
"avatarUrl": "https://avatars.githubusercontent.com/u/20548301?v=4",
@@ -1209,6 +1254,15 @@
"firstContributionAt": "2026-08-06T04:32:47Z",
"latestContributionAt": "2026-08-10T02:54:27Z"
},
{
"login": "Cherrs",
"avatarUrl": "https://avatars.githubusercontent.com/u/6745593?v=4",
"profileUrl": "https://github.com/Cherrs",
"commits": 1,
"mergedPullRequests": 1,
"firstContributionAt": "2026-08-25T09:42:47Z",
"latestContributionAt": "2026-08-26T05:21:51Z"
},
{
"login": "cl1107",
"avatarUrl": "https://avatars.githubusercontent.com/u/21985353?v=4",
@@ -1245,6 +1299,15 @@
"firstContributionAt": "2026-07-02T07:45:55Z",
"latestContributionAt": "2026-07-02T08:37:21Z"
},
{
"login": "debugzhao",
"avatarUrl": "https://avatars.githubusercontent.com/u/32617253?v=4",
"profileUrl": "https://github.com/debugzhao",
"commits": 1,
"mergedPullRequests": 1,
"firstContributionAt": "2026-08-28T14:35:35Z",
"latestContributionAt": "2026-08-30T04:25:55Z"
},
{
"login": "Dhevenddra",
"avatarUrl": "https://avatars.githubusercontent.com/u/121691114?v=4",
@@ -1344,6 +1407,15 @@
"firstContributionAt": "2026-07-05T11:00:04Z",
"latestContributionAt": "2026-07-05T11:38:51Z"
},
{
"login": "fanchenggang",
"avatarUrl": "https://avatars.githubusercontent.com/u/20358122?v=4",
"profileUrl": "https://github.com/fanchenggang",
"commits": 1,
"mergedPullRequests": 1,
"firstContributionAt": "2026-08-27T07:09:23Z",
"latestContributionAt": "2026-08-27T09:37:55Z"
},
{
"login": "fuuulstack",
"avatarUrl": "https://avatars.githubusercontent.com/u/38742225?v=4",
@@ -1353,6 +1425,15 @@
"firstContributionAt": "2026-07-23T06:23:37Z",
"latestContributionAt": "2026-08-03T07:50:44Z"
},
{
"login": "gaoruiqiang2017",
"avatarUrl": "https://avatars.githubusercontent.com/u/26794805?v=4",
"profileUrl": "https://github.com/gaoruiqiang2017",
"commits": 1,
"mergedPullRequests": 1,
"firstContributionAt": "2026-08-25T11:28:46Z",
"latestContributionAt": "2026-08-25T14:15:41Z"
},
{
"login": "gdzhu8023",
"avatarUrl": "https://avatars.githubusercontent.com/u/21006156?v=4",
@@ -1371,6 +1452,15 @@
"firstContributionAt": "2026-06-01T08:54:31Z",
"latestContributionAt": "2026-06-01T10:04:58Z"
},
{
"login": "Hanwn",
"avatarUrl": "https://avatars.githubusercontent.com/u/30523763?v=4",
"profileUrl": "https://github.com/Hanwn",
"commits": 1,
"mergedPullRequests": 1,
"firstContributionAt": "2026-08-29T01:18:22Z",
"latestContributionAt": "2026-08-29T17:11:09Z"
},
{
"login": "heyrmi",
"avatarUrl": "https://avatars.githubusercontent.com/u/42892759?v=4",
@@ -1470,15 +1560,6 @@
"firstContributionAt": "2026-07-23T09:40:04Z",
"latestContributionAt": "2026-07-28T17:29:20Z"
},
{
"login": "klinge1011",
"avatarUrl": "https://avatars.githubusercontent.com/u/34301334?v=4",
"profileUrl": "https://github.com/klinge1011",
"commits": 1,
"mergedPullRequests": 1,
"firstContributionAt": "2026-08-21T12:23:37Z",
"latestContributionAt": "2026-08-22T07:41:17Z"
},
{
"login": "koco-co",
"avatarUrl": "https://avatars.githubusercontent.com/u/227195474?v=4",
@@ -1506,6 +1587,15 @@
"firstContributionAt": "2026-08-04T15:56:03Z",
"latestContributionAt": "2026-08-05T01:49:14Z"
},
{
"login": "LDQONJ",
"avatarUrl": "https://avatars.githubusercontent.com/u/128981031?v=4",
"profileUrl": "https://github.com/LDQONJ",
"commits": 1,
"mergedPullRequests": 1,
"firstContributionAt": "2026-08-26T02:32:37Z",
"latestContributionAt": "2026-08-26T06:43:53Z"
},
{
"login": "leic4u",
"avatarUrl": "https://avatars.githubusercontent.com/u/32786903?v=4",
@@ -1578,6 +1668,15 @@
"firstContributionAt": "2026-07-14T11:25:31Z",
"latestContributionAt": "2026-07-28T20:32:27Z"
},
{
"login": "mmorella-dev",
"avatarUrl": "https://avatars.githubusercontent.com/u/4561733?v=4",
"profileUrl": "https://github.com/mmorella-dev",
"commits": 1,
"mergedPullRequests": 1,
"firstContributionAt": "2026-08-28T19:54:04Z",
"latestContributionAt": "2026-08-29T20:36:35Z"
},
{
"login": "monellin",
"avatarUrl": "https://avatars.githubusercontent.com/u/17845081?v=4",
@@ -1596,15 +1695,6 @@
"firstContributionAt": "2026-08-15T16:59:21Z",
"latestContributionAt": "2026-08-16T03:21:22Z"
},
{
"login": "octo-patch",
"avatarUrl": "https://avatars.githubusercontent.com/u/266937838?v=4",
"profileUrl": "https://github.com/octo-patch",
"commits": 1,
"mergedPullRequests": 1,
"firstContributionAt": "2026-07-30T10:58:25Z",
"latestContributionAt": "2026-07-30T15:05:13Z"
},
{
"login": "OctoBored",
"avatarUrl": "https://avatars.githubusercontent.com/u/212877535?v=4",
@@ -1659,6 +1749,15 @@
"firstContributionAt": "2026-08-03T06:58:44Z",
"latestContributionAt": "2026-08-04T06:23:45Z"
},
{
"login": "s1oopX",
"avatarUrl": "https://avatars.githubusercontent.com/u/185045939?v=4",
"profileUrl": "https://github.com/s1oopX",
"commits": 1,
"mergedPullRequests": 1,
"firstContributionAt": "2026-08-29T11:49:11Z",
"latestContributionAt": "2026-08-29T17:02:01Z"
},
{
"login": "shnno13",
"avatarUrl": "https://avatars.githubusercontent.com/u/14847218?v=4",
@@ -1812,6 +1911,15 @@
"firstContributionAt": "2026-08-17T06:30:17Z",
"latestContributionAt": "2026-08-18T04:47:53Z"
},
{
"login": "wzlove",
"avatarUrl": "https://avatars.githubusercontent.com/u/35476126?v=4",
"profileUrl": "https://github.com/wzlove",
"commits": 1,
"mergedPullRequests": 1,
"firstContributionAt": "2026-08-25T11:23:05Z",
"latestContributionAt": "2026-08-25T14:34:04Z"
},
{
"login": "xcstatus",
"avatarUrl": "https://avatars.githubusercontent.com/u/40375067?v=4",
@@ -1973,6 +2081,15 @@
"mergedPullRequests": 1,
"firstContributionAt": "2026-06-23T05:56:48Z",
"latestContributionAt": "2026-06-23T17:44:46Z"
},
{
"login": "octo-patch",
"avatarUrl": "https://avatars.githubusercontent.com/u/266937838?v=4",
"profileUrl": "https://github.com/octo-patch",
"commits": 0,
"mergedPullRequests": 1,
"firstContributionAt": "2026-07-30T10:58:25Z",
"latestContributionAt": "2026-07-30T15:05:13Z"
}
]
}