Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
19e3d38
feat: add table export functionality with CSV, JSON, and SQL formats
datlechin Dec 28, 2025
d88070a
Update TablePro/Core/Services/ExportService.swift
datlechin Dec 28, 2025
94d73c0
Update TablePro/Core/Services/ExportService.swift
datlechin Dec 28, 2025
9901b82
Update TablePro/Views/Export/ExportCSVOptionsView.swift
datlechin Dec 28, 2025
9d61999
Update TablePro/Core/Services/ExportService.swift
datlechin Dec 28, 2025
85cb44a
fix: address code review feedback
datlechin Dec 28, 2025
376d0c9
refactor: unify sidebar context menus into single function
datlechin Dec 28, 2025
fc68f11
fix: address all code review issues
datlechin Dec 28, 2025
a457b5e
Update TablePro/Core/Services/ExportService.swift
datlechin Dec 28, 2025
baed5e7
Update TablePro/Core/Services/ExportService.swift
datlechin Dec 28, 2025
94eb180
Update TablePro/Core/Services/ExportService.swift
datlechin Dec 28, 2025
fe05cfa
Update TablePro/Views/Export/ExportSuccessView.swift
datlechin Dec 29, 2025
d1db29e
Update TablePro/Core/Services/ExportService.swift
datlechin Dec 29, 2025
8b65d67
Update TablePro/Core/Services/ExportService.swift
datlechin Dec 29, 2025
764e8fd
wip
datlechin Dec 29, 2025
ed798d9
Update TablePro/Core/Services/ExportService.swift
datlechin Dec 29, 2025
de48444
Update TablePro/Views/Sidebar/SidebarView.swift
datlechin Dec 29, 2025
80ad0a2
Update TablePro/Views/Export/ExportDialog.swift
datlechin Dec 29, 2025
5e29e6f
wip
datlechin Dec 29, 2025
a411d58
Update TablePro/Core/Database/MySQLDriver.swift
datlechin Dec 29, 2025
ccb171c
Update TablePro/Views/Export/ExportDialog.swift
datlechin Dec 29, 2025
dcd96aa
wip
datlechin Dec 29, 2025
dc1518b
Update TablePro/Core/Services/ExportService.swift
datlechin Dec 29, 2025
8683a5a
Update TablePro/Core/Services/ExportService.swift
datlechin Dec 29, 2025
dcb444b
Update TablePro/Core/Services/ExportService.swift
datlechin Dec 29, 2025
549bb55
wip
datlechin Dec 29, 2025
dcee9e8
Update TablePro/Core/Services/ExportService.swift
datlechin Dec 29, 2025
8e27851
Update TablePro/Views/Export/ExportDialog.swift
datlechin Dec 29, 2025
1bc3152
Update TablePro/Core/Services/ExportService.swift
datlechin Dec 29, 2025
dc609eb
Update TablePro/Core/Services/ExportService.swift
datlechin Dec 29, 2025
0af9b4e
Update TablePro/Views/Export/ExportDialog.swift
datlechin Dec 29, 2025
6973621
Update TablePro/Core/Services/ExportService.swift
datlechin Dec 29, 2025
dab537b
Update TablePro/Core/Services/ExportService.swift
datlechin Dec 29, 2025
c856ceb
wip
datlechin Dec 29, 2025
2f53566
wip
datlechin Dec 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions TablePro.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@
AUTOMATION_APPLE_EVENTS = NO;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 9;
CURRENT_PROJECT_VERSION = 10;
DEVELOPMENT_TEAM = D7HJ5TFYCU;
ENABLE_APP_SANDBOX = NO;
ENABLE_HARDENED_RUNTIME = YES;
Expand Down Expand Up @@ -286,7 +286,7 @@
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/Libs";
MACOSX_DEPLOYMENT_TARGET = 14.6;
MARKETING_VERSION = 0.1.9;
MARKETING_VERSION = 0.1.10;
OTHER_LDFLAGS = (
"-force_load",
"$(PROJECT_DIR)/Libs/libmariadb.a",
Expand Down Expand Up @@ -331,7 +331,7 @@
AUTOMATION_APPLE_EVENTS = NO;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 9;
CURRENT_PROJECT_VERSION = 10;
DEVELOPMENT_TEAM = D7HJ5TFYCU;
ENABLE_APP_SANDBOX = NO;
ENABLE_HARDENED_RUNTIME = YES;
Expand Down Expand Up @@ -364,7 +364,7 @@
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/Libs";
MACOSX_DEPLOYMENT_TARGET = 14.6;
MARKETING_VERSION = 0.1.9;
MARKETING_VERSION = 0.1.10;
OTHER_LDFLAGS = (
"-force_load",
"$(PROJECT_DIR)/Libs/libmariadb.a",
Expand Down
10 changes: 2 additions & 8 deletions TablePro/Core/ChangeTracking/SQLStatementGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -397,14 +397,8 @@ struct SQLStatementGenerator {
}

/// Escape characters that can break SQL strings
/// Delegates to shared SQLEscaping utility for consistent escaping across the codebase
private func escapeSQLString(_ str: String) -> String {
var result = str
result = result.replacingOccurrences(of: "\\", with: "\\\\") // Backslash first
result = result.replacingOccurrences(of: "'", with: "''") // Single quote
result = result.replacingOccurrences(of: "\n", with: "\\n") // Newline
result = result.replacingOccurrences(of: "\r", with: "\\r") // Carriage return
result = result.replacingOccurrences(of: "\t", with: "\\t") // Tab
result = result.replacingOccurrences(of: "\0", with: "\\0") // Null byte
return result
SQLEscaping.escapeStringLiteral(str)
}
}
11 changes: 10 additions & 1 deletion TablePro/Core/Database/MySQLDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,16 @@ final class MySQLDriver: DatabaseDriver {
}

func fetchTableDDL(table: String) async throws -> String {
let query = "SHOW CREATE TABLE `\(table)`"
// The `table` argument must be a valid MySQL/MariaDB table identifier, optionally
// schema-qualified, and is interpolated verbatim into the query. Examples:
// - "users"
// - "`mydb`.`users`"
// - "`users`"
//
// This method does not add any quoting or escaping around `table`. It is the
// caller's responsibility to provide a correctly formatted and safely quoted
// identifier when needed.
let query = "SHOW CREATE TABLE \(table)"
Comment thread
datlechin marked this conversation as resolved.
let result = try await execute(query: query)

// SHOW CREATE TABLE returns 2 columns: Table name and Create Table statement
Expand Down
62 changes: 62 additions & 0 deletions TablePro/Core/Database/SQLEscaping.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// SQLEscaping.swift
// TablePro
//
// Shared utilities for SQL string escaping to prevent SQL injection.
// Used across ExportService, SQLStatementGenerator, and other SQL-generating code.
//

import Foundation

/// Centralized SQL escaping utilities to prevent SQL injection vulnerabilities
enum SQLEscaping {

/// Escape a string value for use in SQL string literals (VALUES, WHERE clauses, etc.)
///
/// Handles the following special characters:
/// - Backslashes (must be escaped first to avoid double-escaping)
/// - Single quotes (SQL standard: doubled)
/// - Control characters: null, backspace, tab, newline, form feed, carriage return
/// - MySQL EOF marker (\x1A) which can cause parsing issues
///
/// Example:
/// ```swift
/// let safe = SQLEscaping.escapeStringLiteral("O'Brien\\test")
/// // Result: "O''Brien\\\\test"
/// let sql = "INSERT INTO users (name) VALUES ('\(safe)')"
/// ```
///
/// - Parameter str: The raw string to escape
/// - Returns: The escaped string safe for use in SQL string literals
static func escapeStringLiteral(_ str: String) -> String {
var result = str
// IMPORTANT: Escape backslashes FIRST to avoid double-escaping
result = result.replacingOccurrences(of: "\\", with: "\\\\")
// Single quote: SQL standard escaping (double the quote)
result = result.replacingOccurrences(of: "'", with: "''")
// Common control characters
result = result.replacingOccurrences(of: "\n", with: "\\n")
result = result.replacingOccurrences(of: "\r", with: "\\r")
result = result.replacingOccurrences(of: "\t", with: "\\t")
result = result.replacingOccurrences(of: "\0", with: "\\0")
// Additional control characters that can cause issues
result = result.replacingOccurrences(of: "\u{08}", with: "\\b") // Backspace
result = result.replacingOccurrences(of: "\u{0C}", with: "\\f") // Form feed
result = result.replacingOccurrences(of: "\u{1A}", with: "\\Z") // MySQL EOF marker (Ctrl+Z)
return result
}
Comment thread
datlechin marked this conversation as resolved.

/// Escape wildcards in LIKE patterns while preserving intentional wildcards
///
/// This is useful when building LIKE clauses where the search term should be treated literally.
///
/// - Parameter value: The value to escape
/// - Returns: The escaped value with %, _, and \ escaped
static func escapeLikeWildcards(_ value: String) -> String {
var result = value
result = result.replacingOccurrences(of: "\\", with: "\\\\")
result = result.replacingOccurrences(of: "%", with: "\\%")
result = result.replacingOccurrences(of: "_", with: "\\_")
return result
}
}
10 changes: 2 additions & 8 deletions TablePro/Core/Services/CreateTableService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -545,15 +545,9 @@ struct CreateTableService {
}

/// Escape characters that can break SQL strings
/// Delegates to shared SQLEscaping utility for consistent escaping across the codebase
private func escapeSQLString(_ str: String) -> String {
var result = str
result = result.replacingOccurrences(of: "\\", with: "\\\\") // Backslash first
result = result.replacingOccurrences(of: "'", with: "''") // Single quote (SQL standard)
result = result.replacingOccurrences(of: "\n", with: "\\n") // Newline
result = result.replacingOccurrences(of: "\r", with: "\\r") // Carriage return
result = result.replacingOccurrences(of: "\t", with: "\\t") // Tab
result = result.replacingOccurrences(of: "\0", with: "\\0") // Null byte
return result
SQLEscaping.escapeStringLiteral(str)
}
}

Expand Down
Loading