Skip to content

feat: copy rows as INSERT/UPDATE SQL statements#271

Merged
datlechin merged 4 commits intomainfrom
feat/copy-as-sql-statements
Mar 11, 2026
Merged

feat: copy rows as INSERT/UPDATE SQL statements#271
datlechin merged 4 commits intomainfrom
feat/copy-as-sql-statements

Conversation

@datlechin
Copy link
Copy Markdown
Collaborator

@datlechin datlechin commented Mar 11, 2026

Summary

  • Add "Copy as" submenu to the data grid context menu with "INSERT Statement(s)" and "UPDATE Statement(s)" options
  • New SQLRowToStatementConverter generates fully-inlined SQL from selected row data, with proper identifier quoting per database type and value escaping
  • Supports all SQL databases: MySQL/MariaDB (backticks), PostgreSQL/Redshift (double quotes), MSSQL (brackets), SQLite, Oracle, DuckDB, and ClickHouse (ALTER TABLE ... UPDATE syntax)
  • Hidden for MongoDB/Redis (non-SQL) and query result tabs (no table name)
  • 14 unit tests covering INSERT/UPDATE generation, NULL handling, quote escaping, database-specific quoting, and 50k row cap

Closes #269

Test plan

  • 14 unit tests pass (SQLRowToStatementConverterTests)
  • Manual: connect to a SQL database → browse table → select rows → right-click → "Copy as" → INSERT Statement(s) → verify clipboard
  • Manual: same flow → UPDATE Statement(s) → verify WHERE clause uses PK
  • Manual: verify submenu hidden for MongoDB/Redis connections
  • Manual: verify submenu hidden on query result tabs (no table name)
  • Manual: test with NULL values, empty strings, special characters

Summary by CodeRabbit

  • New Features
    • Added a "Copy as" submenu in row context menus to copy selected rows as INSERT or UPDATE SQL, with database-specific formatting.
  • Localization
    • Added localization keys for "Copy as", "INSERT Statement(s)", and "UPDATE Statement(s)" (vi/zh-Hans translations included).
  • Tests
    • Added tests covering INSERT/UPDATE SQL generation, quoting, NULL handling, escaping, and row-limit behavior.

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 11, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c09ca14a-1daf-4ab2-b842-684056839660

📥 Commits

Reviewing files that changed from the base of the PR and between c3ee476 and 3aa3f4f.

📒 Files selected for processing (3)
  • TablePro/Core/Utilities/SQL/SQLRowToStatementConverter.swift
  • TablePro/Resources/Localizable.xcstrings
  • TableProTests/Core/Utilities/SQLRowToStatementConverterTests.swift

📝 Walkthrough

Walkthrough

Adds "Copy as INSERT/UPDATE" support: a new SQLRowToStatementConverter generates DB-specific INSERT/UPDATE SQL, UI wiring in DataGridView and context menu, coordinator clipboard actions, new localization keys, and comprehensive unit tests.

Changes

Cohort / File(s) Summary
Changelog & Localization
CHANGELOG.md, TablePro/Resources/Localizable.xcstrings
Changelog entry and three new localization keys: Copy as, INSERT Statement(s), UPDATE Statement(s) (with vi/zh-Hans translations).
SQL Generation Utility
TablePro/Core/Utilities/SQL/SQLRowToStatementConverter.swift
New public SQLRowToStatementConverter struct. Generates INSERT and UPDATE statements with DB-specific quoting/syntax, PK handling, NULL/escaping logic, and a 50,000-row cap.
Data Grid API & Coordinator
TablePro/Views/Results/DataGridView.swift, TablePro/Views/Results/DataGridView+RowActions.swift
Added tableName and primaryKeyColumn properties to DataGridView/TableViewCoordinator. Implemented copyRowsAsInsert(at:) and copyRowsAsUpdate(at:) to collect rows, invoke converter, and copy SQL to clipboard.
View Integration
TablePro/Views/Main/Child/MainEditorContentView.swift, TablePro/Views/Results/TableRowViewWithMenu.swift
DataGridView initializers now pass tableName and primaryKeyColumn. Context menu gains conditional "Copy as" submenu (INSERT/UPDATE) for SQL DBs (excludes MongoDB/Redis) with selectors wired to coordinator actions.
Tests
TableProTests/Core/Utilities/SQLRowToStatementConverterTests.swift
New comprehensive tests covering INSERT/UPDATE generation across multiple DBs, PK/no-PK flows, NULL/escaping, and row-cap enforcement.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Menu as TableRowViewWithMenu
    participant Coordinator as TableViewCoordinator
    participant Converter as SQLRowToStatementConverter
    participant Clipboard

    User->>Menu: Right-click row(s)
    Menu->>Menu: Display context menu with "Copy as"
    User->>Menu: Select "INSERT Statement(s)" / "UPDATE Statement(s)"
    Menu->>Coordinator: copyRowsAsInsert(at: indices) / copyRowsAsUpdate(at: indices)
    Coordinator->>Coordinator: Gather rows, tableName, primaryKeyColumn, databaseType
    Coordinator->>Converter: generateInserts(rows) / generateUpdates(rows)
    Converter->>Converter: Quote identifiers, format values, build SQL per DB
    Converter-->>Coordinator: Return SQL string
    Coordinator->>Clipboard: Write SQL string to clipboard
    Clipboard-->>User: SQL ready to paste
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇
I nibble rows and guard each quote,
I stitch INSERTs in every note,
UPDATEs hop where PKs are found,
Clipboard treasures, safe and sound,
A rabbit's patch of SQL delight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: copy rows as INSERT/UPDATE SQL statements' directly and clearly describes the main feature addition: enabling users to copy table rows as SQL INSERT or UPDATE statements.
Linked Issues check ✅ Passed The PR implements all coding requirements from issue #269: provides context-menu actions to copy selected rows as INSERT and UPDATE statements, enabling users to generate SQL statements from grid data.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the feature: new SQLRowToStatementConverter utility, context menu UI additions, row action methods, localization strings, and comprehensive unit tests—no unrelated modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/copy-as-sql-statements

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
TablePro/Core/Utilities/SQL/SQLRowToStatementConverter.swift (1)

7-13: Consider adding explicit access control.

Per coding guidelines: "Always specify access control explicitly on both extensions and individual members." The struct and its public-facing methods (generateInserts, generateUpdates) lack explicit modifiers.

Suggested change
-struct SQLRowToStatementConverter {
-    let tableName: String
+internal struct SQLRowToStatementConverter {
+    internal let tableName: String

(Apply similar pattern to other members)

As per coding guidelines: "Always specify access control explicitly."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TablePro/Core/Utilities/SQL/SQLRowToStatementConverter.swift` around lines 7
- 13, The struct SQLRowToStatementConverter and its members need explicit access
control; update the declaration of SQLRowToStatementConverter and add explicit
access modifiers to its public-facing methods generateInserts and
generateUpdates (and other members like tableName, columns, primaryKeyColumn,
databaseType, and maxRows) to match the module's API surface (e.g.,
public/internal/private as appropriate) so access levels are explicit rather
than implicit.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@TablePro/Core/Utilities/SQL/SQLRowToStatementConverter.swift`:
- Line 52: The WHERE clause in SQLRowToStatementConverter currently builds
"quoteColumn(pkColumn) = formatValue(pkValue)" which yields "= NULL" when
pkValue is nil; change the logic that sets whereClause so it checks pkValue (or
the underlying optional) and uses "quoteColumn(pkColumn) IS NULL" when pkValue
is nil, otherwise keep the existing "quoteColumn(pkColumn) =
formatValue(pkValue)" behavior; update the code around whereClause assignment
(reference symbols: whereClause, pkColumn, pkValue, quoteColumn(_:),
formatValue(_:)) to branch on nil and produce the correct IS NULL SQL syntax.
- Around line 78-83: The formatValue(_:) function currently only doubles single
quotes and returns the literal "NULL" for nil, which misses backslash escaping
and leads to invalid WHERE clauses for NULL PKs; update formatValue(_ value:
String?) to also escape backslashes by replacing "\" with "\\", continue to
escape single quotes, and still return "NULL" for nil, and then modify the
UPDATE/WHERE construction that uses pkValue so it emits "col IS NULL" when
pkValue is nil instead of "col = NULL" (use formatValue only for non-nil
pkValue); reference formatValue(_:) and the code path that builds the WHERE
clause from pkValue to implement these two fixes.

In `@TablePro/Resources/Localizable.xcstrings`:
- Around line 4067-4070: The "Copy as" localization entries (e.g., the "Copy as"
key in Localizable.xcstrings and the similar empty entries at ranges 8456-8459
and 16645-16647) are missing translations; update these keys for vi and zh-Hans
with appropriate localized strings for the surrounding UI while preserving SQL
keywords like INSERT and UPDATE verbatim (do not translate font names, database
types, encodings, or other technical terms). Locate the empty dictionary values
for "Copy as" and the other empty submenu labels in Localizable.xcstrings, add
the translated phrases for Vietnamese and Simplified Chinese, and ensure any
occurrences of INSERT/UPDATE remain uppercase and unchanged.

---

Nitpick comments:
In `@TablePro/Core/Utilities/SQL/SQLRowToStatementConverter.swift`:
- Around line 7-13: The struct SQLRowToStatementConverter and its members need
explicit access control; update the declaration of SQLRowToStatementConverter
and add explicit access modifiers to its public-facing methods generateInserts
and generateUpdates (and other members like tableName, columns,
primaryKeyColumn, databaseType, and maxRows) to match the module's API surface
(e.g., public/internal/private as appropriate) so access levels are explicit
rather than implicit.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5b2415ce-5377-400f-8b67-6a4309563fc5

📥 Commits

Reviewing files that changed from the base of the PR and between 70d9794 and 51909d6.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • TablePro/Core/Utilities/SQL/SQLRowToStatementConverter.swift
  • TablePro/Resources/Localizable.xcstrings
  • TablePro/Views/Main/Child/MainEditorContentView.swift
  • TablePro/Views/Results/DataGridView+RowActions.swift
  • TablePro/Views/Results/DataGridView.swift
  • TablePro/Views/Results/TableRowViewWithMenu.swift
  • TableProTests/Core/Utilities/SQLRowToStatementConverterTests.swift

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
TablePro/Core/Utilities/SQL/SQLRowToStatementConverter.swift (1)

82-87: ⚠️ Potential issue | 🔴 Critical

MySQL/MariaDB string literals still need backslash escaping.

This still only doubles '. For MySQL/MariaDB, values containing \ are not round-trippable under the default SQL mode unless backslashes are escaped too, so the copied SQL can change data or fail to parse.

Suggested change
     private func formatValue(_ value: String?) -> String {
         guard let value else {
             return "NULL"
         }
-        return "'\(value.replacingOccurrences(of: "'", with: "''"))'"
+        var escaped = value.replacingOccurrences(of: "'", with: "''")
+        if databaseType == .mysql || databaseType == .mariadb {
+            escaped = escaped.replacingOccurrences(of: "\\", with: "\\\\")
+        }
+        return "'\(escaped)'"
     }
For MySQL and MariaDB SQL string literals, are backslashes escape characters by default, and how should a generated literal escape `\` when `NO_BACKSLASH_ESCAPES` is not enabled?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TablePro/Core/Utilities/SQL/SQLRowToStatementConverter.swift` around lines 82
- 87, The formatValue(_ value: String?) function currently only doubles single
quotes but must also escape backslashes for MySQL/MariaDB when
NO_BACKSLASH_ESCAPES is not enabled; update formatValue to first escape
backslashes (replace "\" with "\\") and then escape single quotes (replace "'"
with "''"), then return the quoted literal or "NULL" as before so generated SQL
round-trips correctly for MySQL/MariaDB.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@TablePro/Core/Utilities/SQL/SQLRowToStatementConverter.swift`:
- Around line 42-56: The current logic treats a missing PK as "IS NULL" which
can match unintended rows; change the PK handling so you only build the pk WHERE
clause when the pk index exists in columns and the row actually contains a value
for that index (non-nil). Use primaryKeyColumn, pkIndex, pkValue, whereClause,
quoteColumn and formatValue to: compute pkIndex, check
row.indices.contains(pkIndex) and that the extracted pkValue is non-nil before
setting whereClause to either "= <value>" or "IS NULL" only if the value is
explicitly null; otherwise, when the PK is unavailable, do not emit "IS NULL" —
instead make whereClause a safe no-match condition (e.g. "1=0") or bail out so
you don't update unintended rows.
- Around line 7-31: Mark the new type and its non-private members with explicit
access control: declare the struct SQLRowToStatementConverter as internal, add
explicit internal to the stored properties tableName, columns, primaryKeyColumn,
and databaseType, and to the non-private methods generateInserts(rows:),
generateUpdates(rows:) and any other non-private helpers like
buildUpdateStatement(...); keep the existing private modifier on maxRows as-is.
Ensure every member has an explicit access level (private/internal/public as
appropriate) to satisfy the repository convention.

---

Duplicate comments:
In `@TablePro/Core/Utilities/SQL/SQLRowToStatementConverter.swift`:
- Around line 82-87: The formatValue(_ value: String?) function currently only
doubles single quotes but must also escape backslashes for MySQL/MariaDB when
NO_BACKSLASH_ESCAPES is not enabled; update formatValue to first escape
backslashes (replace "\" with "\\") and then escape single quotes (replace "'"
with "''"), then return the quoted literal or "NULL" as before so generated SQL
round-trips correctly for MySQL/MariaDB.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bfccc20d-791b-40b2-901b-a78dc293d067

📥 Commits

Reviewing files that changed from the base of the PR and between 51909d6 and c3ee476.

📒 Files selected for processing (2)
  • TablePro/Core/Utilities/SQL/SQLRowToStatementConverter.swift
  • TableProTests/Core/Utilities/SQLRowToStatementConverterTests.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • TableProTests/Core/Utilities/SQLRowToStatementConverterTests.swift

@datlechin datlechin merged commit cf59609 into main Mar 11, 2026
2 of 3 checks passed
@datlechin datlechin deleted the feat/copy-as-sql-statements branch March 11, 2026 07:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

I hope to add copy as insert and update statements

1 participant