-
Notifications
You must be signed in to change notification settings - Fork 108
Adjust Column Value
The Column Value Adjustment feature enables developers to dynamically transform column values during MySQL database exports without modifying the source data. This powerful capability allows for real-time data processing, formatting, masking, and custom transformations as part of the export workflow.
Originally contributed by Tyler Pflueger (tpflueger) via pull request #113, the feature has evolved significantly since its introduction in v2.3.9, with the implementation being completely rewritten in v2.6 for improved performance and usability.
Data Privacy & Security:
- Mask sensitive information such as personally identifiable information (PII), emails, or confidential data before sharing exports with third parties or development teams.
Export-Time Formatting:
- Apply consistent formatting to data values, normalize inconsistent entries, or convert data types to meet specific requirements without altering the original database.
Testing & Development:
- Generate realistic but anonymized datasets for testing environments while maintaining referential integrity and data relationships.
Migration & Integration:
- Transform data during export to match target system requirements, including value mapping, unit conversions, and schema adaptations.
To demonstrate the effect of column value adjustments, here's a comparison of SQL output:
INSERT INTO `users` (`id`, `name`, `email`) VALUES
(1, 'Jennifer Wilson', 'jennifer.wilson@innovatetech.com'),
(2, 'Sarah Mitchell', 'sarah.mitchell@marssen.com'),
(3, 'Michael Chen', 'michael.chen@wendysam.com');
INSERT INTO `users` (`id`, `name`, `email`) VALUES
(1, 'Jen************son', 'jen*****@innovatetech.com'),
(2, 'Sar************ell', 'sar*****@marssen.com'),
(3, 'Mic************hen', 'mic*****@wendysam.com');
As demonstrated above, the adjustment functions have:
- Masked names: Showing first 3 characters + 12 asterisks + last 3 characters
- Masked emails: Showing first 3 characters of local part + asterisks + full domain
This transformation occurs during the export process without modifying the original database, ensuring data privacy while maintaining the structural integrity of the exported SQL statements.
Column value adjustments are configured through the ExportInformations.AddTableColumnValueAdjustment()
method:
// Configure the backup instance
var backup = new MySqlBackup(command);
// Add column adjustment for specific table and column
backup.ExportInfo.AddTableColumnValueAdjustment("tableName", "columnName", adjustmentFunction);
For developers new to C# or those who prefer explicit method declarations, you can define adjustment functions as separate methods:
// Define adjustment methods explicitly
public object MaskEmail(object value)
{
if (value == null) return null;
string email = value.ToString();
if (string.IsNullOrEmpty(email) || !email.Contains("@"))
return "***@***.***";
var parts = email.Split('@');
var maskedLocal = parts[0].Length >= 3
? parts[0].Substring(0, 3) + "*****"
: "*****";
return $"{maskedLocal}@{parts[1]}";
}
public object MaskName(object value)
{
if (value == null) return null;
string name = value.ToString();
if (string.IsNullOrEmpty(name)) return "";
return name.Length >= 6
? $"{name.Substring(0, 3)}************{name.Substring(name.Length - 3)}"
: new string('*', name.Length);
}
// Add the adjustment rules using method references
backup.ExportInfo.AddTableColumnValueAdjustment("users", "email", MaskEmail);
backup.ExportInfo.AddTableColumnValueAdjustment("users", "name", MaskName);
For experienced developers who prefer concise code, you can use inline lambda expressions:
// Mask email addresses
backup.ExportInfo.AddTableColumnValueAdjustment("users", "email", value => {
if (value == null) return null;
string email = value.ToString();
if (string.IsNullOrEmpty(email) || !email.Contains("@"))
return "***@***.***";
var parts = email.Split('@');
var maskedLocal = parts[0].Length >= 3
? parts[0].Substring(0, 3) + "*****"
: "*****";
return $"{maskedLocal}@{parts[1]}";
});
// Mask personal names
backup.ExportInfo.AddTableColumnValueAdjustment("users", "name", value => {
if (value == null) return null;
string name = value.ToString();
if (string.IsNullOrEmpty(name)) return "";
return name.Length >= 6
? $"{name.Substring(0, 3)}************{name.Substring(name.Length - 3)}"
: new string('*', name.Length);
});
public class DatabaseBackupExample
{
public void ExportWithAdjustments()
{
string constr = "server=localhost;user=root;pwd=1234;database=test1;convertzerodatetime=true;";
string filePath = @"C:\backup.sql";
using (var conn = new MySqlConnection(constr))
using (var cmd = conn.CreateCommand())
using (var mb = new MySqlBackup(cmd))
{
conn.Open();
// Add column adjustment rules using method references
mb.ExportInfo.AddTableColumnValueAdjustment("users", "email", MaskEmail);
mb.ExportInfo.AddTableColumnValueAdjustment("users", "name", MaskName);
mb.ExportToFile(filePath);
}
}
// Define adjustment methods explicitly
public object MaskEmail(object value)
{
if (value == null) return null;
string email = value.ToString();
if (string.IsNullOrEmpty(email) || !email.Contains("@"))
return "***@***.***";
var parts = email.Split('@');
var maskedLocal = parts[0].Length >= 3
? parts[0].Substring(0, 3) + "*****"
: "*****";
return $"{maskedLocal}@{parts[1]}";
}
public object MaskName(object value)
{
if (value == null) return null;
string name = value.ToString();
if (string.IsNullOrEmpty(name)) return "";
return name.Length >= 6
? $"{name.Substring(0, 3)}************{name.Substring(name.Length - 3)}"
: new string('*', name.Length);
}
}
public class DatabaseBackupExample
{
public void ExportWithAdjustments()
{
string constr = "server=localhost;user=root;pwd=1234;database=test1;convertzerodatetime=true;";
string filePath = @"C:\backup.sql";
using (var conn = new MySqlConnection(constr))
using (var cmd = conn.CreateCommand())
using (var mb = new MySqlBackup(cmd))
{
conn.Open();
// Add column adjustment rules using inline lambda expressions
mb.ExportInfo.AddTableColumnValueAdjustment("users", "email", value => {
if (value == null) return null;
string email = value.ToString();
if (string.IsNullOrEmpty(email) || !email.Contains("@"))
return "***@***.***";
var parts = email.Split('@');
var maskedLocal = parts[0].Length >= 3
? parts[0].Substring(0, 3) + "*****"
: "*****";
return $"{maskedLocal}@{parts[1]}";
});
mb.ExportInfo.AddTableColumnValueAdjustment("users", "name", value => {
if (value == null) return null;
string name = value.ToString();
if (string.IsNullOrEmpty(name)) return "";
return name.Length >= 6
? $"{name.Substring(0, 3)}************{name.Substring(name.Length - 3)}"
: new string('*', name.Length);
});
mb.ExportToFile(filePath);
}
}
}
Both implementations produce identical results. The choice between them depends on your coding style and project requirements:
- External Method Declaration: Better for complex logic, reusability across multiple exports, and easier unit testing
- Inline Lambda Expressions: More concise for simple transformations and when the logic is specific to a single export operation
The column value adjustments will be applied automatically during the export process, transforming the specified columns while leaving all other data unchanged.
// Configure multiple adjustments for different tables and columns
var adjustments = new Dictionary<(string table, string column), Func<object, object>>
{
{ ("employees", "salary"), value => Math.Round(Convert.ToDecimal(value ?? 0), 2) },
{ ("products", "price"), value => Convert.ToDecimal(value ?? 0) * 1.1m }, // 10% increase
{ ("orders", "status"), value => MapStatusCode(Convert.ToInt32(value ?? 0)) },
{ ("customers", "phone"), value => MaskPhoneNumber(value?.ToString()) },
{ ("inventory", "quantity"), value => value ?? 0 } // Default null values
};
foreach (var adjustment in adjustments)
{
backup.ExportInfo.AddTableColumnValueAdjustment(
adjustment.Key.table,
adjustment.Key.column,
adjustment.Value
);
}
Transform sensitive data to comply with regulations such as GDPR, HIPAA, or internal security policies:
// Mask Social Security Numbers
backup.ExportInfo.AddTableColumnValueAdjustment("employees", "ssn", value =>
value == null ? null : "XXX-XX-XXXX");
// Anonymize customer data
backup.ExportInfo.AddTableColumnValueAdjustment("customers", "customer_id", value =>
$"ANON_{new Random().Next(100000, 999999)}");
Ensure consistent data formatting across exports:
// Standardize date formats
backup.ExportInfo.AddTableColumnValueAdjustment("orders", "order_date", value =>
value is DateTime date ? date.ToString("yyyy-MM-dd") : value);
// Normalize phone numbers
backup.ExportInfo.AddTableColumnValueAdjustment("contacts", "phone", value => {
if (value == null) return null;
string phone = Regex.Replace(value.ToString(), @"[^\d]", "");
return phone.Length == 10 ? $"({phone.Substring(0,3)}) {phone.Substring(3,3)}-{phone.Substring(6,4)}" : value;
});
Create realistic test datasets while protecting sensitive information:
// Generate test-friendly user data
backup.ExportInfo.AddTableColumnValueAdjustment("users", "username", value =>
$"testuser_{new Random().Next(1000, 9999)}");
backup.ExportInfo.AddTableColumnValueAdjustment("users", "email", value =>
$"test.user{new Random().Next(100, 999)}@example.com");
Apply business rules or calculations during export:
// Apply discount calculations
backup.ExportInfo.AddTableColumnValueAdjustment("products", "price", value => {
decimal price = Convert.ToDecimal(value ?? 0);
return price > 1000 ? price * 0.9m : price; // 10% discount for high-value items
});
// Convert units
backup.ExportInfo.AddTableColumnValueAdjustment("measurements", "weight_kg", value =>
Convert.ToDouble(value ?? 0) * 2.20462); // Convert kg to pounds
The Column Value Adjustment feature is optimized for production use:
- Selective Processing: Adjustments are applied only to specified table-column combinations
- Efficient Lookup: Uses dictionary-based lookups for O(1) performance
- Memory Optimization: Processes data in streams to minimize memory usage
- Parallel Processing: Supports parallel export processing when enabled
For optimal performance when working with large datasets:
- Minimize Complex Logic: Keep adjustment functions simple and fast
- Use Efficient Data Types: Prefer primitive operations over complex string manipulations
- Cache External Lookups: Pre-compute lookup tables for reference data
-
Enable Parallel Processing: Use
ExportInfo.EnableParallelProcessing = true
for large exports
The feature includes robust error handling mechanisms:
// Implement safe adjustment functions with error handling
backup.ExportInfo.AddTableColumnValueAdjustment("products", "price", value => {
try {
return Math.Round(Convert.ToDecimal(value ?? 0), 2);
}
catch (Exception) {
return 0.00m; // Default fallback value
}
});
The API has been simplified and enhanced:
Previous Implementation (v2.3.9):
backup.ExportInfo.AdjustColumnValue = (columnInfo) => {
if (columnInfo.TableName == "users" && columnInfo.ColumnName == "email") {
return MaskEmail(columnInfo.Value);
}
return columnInfo.Value;
};
New Implementation (v2.6.0):
backup.ExportInfo.AddTableColumnValueAdjustment("users", "email", MaskEmail);
- Simplified Configuration: Direct table-column mapping eliminates complex conditional logic
- Better Performance: Optimized lookup mechanism reduces processing overhead
- Improved Maintainability: Clear separation of concerns for different table-column combinations
- Enhanced Readability: More intuitive and self-documenting code structure
- Document Transformations: Clearly document what each adjustment function does and why it's needed
- Test Thoroughly: Validate adjustment functions with representative data samples
- Handle Null Values: Always check for null values in adjustment functions
- Maintain Data Integrity: Ensure transformations don't break referential integrity
- Consider Reversibility: For some use cases, consider whether transformations can be reversed if needed
The Column Value Adjustment feature provides a powerful, flexible, and efficient way to transform data during MySQL database exports. By enabling real-time data processing without modifying source databases, it supports a wide range of use cases from data privacy compliance to testing data generation, making it an essential tool for modern database management workflows.