Skip to content

feat: plugin extensibility#294

Merged
datlechin merged 23 commits intomainfrom
feat/connection-field-types
Mar 12, 2026
Merged

feat: plugin extensibility#294
datlechin merged 23 commits intomainfrom
feat/connection-field-types

Conversation

@datlechin
Copy link
Owner

@datlechin datlechin commented Mar 12, 2026

Summary

  • Phase 1.2: Extend ConnectionField with .number, .toggle, and .stepper field types for dynamic connection form rendering
  • Phase 2.1: Add SQLDialectDescriptor to PluginKit so plugins self-describe their SQL dialect (keywords, functions, data types, identifier quoting), with SQLDialectFactory preferring plugin-provided info
  • Phase 3.1: Add ParameterStyle (.questionMark vs .dollar) to plugin protocol; ClickHouse/MSSQL/Oracle plugins implement generateStatements() for database-specific DML syntax
  • Phase 3.2: Add 8 DDL schema generation methods to PluginDatabaseDriver (ADD/MODIFY/DROP COLUMN, INDEX, FK, PK), with SchemaStatementGenerator trying plugin methods first
  • Phase 3.3: Add table operation methods (truncateTableStatements, dropObjectStatement, foreignKeyDisableStatements/Enable) to plugin protocol
  • Phase 3.4: Move MSSQL/Oracle OFFSET...FETCH NEXT pagination into plugin buildBrowseQuery/buildFilteredQuery hooks, removing 270 lines from TableQueryBuilder
  • Phase 3.5: Add buildExplainQuery to plugin protocol for database-specific EXPLAIN syntax

All phases follow the plugin-first-then-fallback pattern: the app delegates to plugins and only falls back to generic SQL when the plugin returns nil.

New files: SQLDialectDescriptor.swift, SchemaTypes.swift (PluginKit transfer types), ConnectionFieldRow.swift
42 files changed: +4618 / -383 lines
6 new test files: 45 tests covering parameter style, DDL generation, table operations, dialect descriptors, explain queries, and connection fields

Test plan

  • xcodebuild build succeeds (all 11 targets)
  • Full test suite passes
  • swiftlint lint --strict — no new violations
  • Manual: connect to MySQL/PostgreSQL/SQLite, verify EXPLAIN works
  • Manual: edit table structure (add/modify/drop column), verify DDL preview
  • Manual: truncate/drop table via context menu
  • Manual: connect to MSSQL/Oracle, verify pagination uses OFFSET...FETCH NEXT
  • Manual: verify connection form renders plugin-defined fields correctly

Summary by CodeRabbit

  • New Features

    • Plugins can declare full SQL dialects, parameter styles, EXPLAIN support, and provide DDL/DML generation and custom plugin settings UI.
  • Improvements

    • SQL generation, query building, pagination and schema operations now prefer plugin-provided SQL with safer fallback behavior; explain flow unified to use plugin drivers.
  • Tests

    • Added tests for parameter styles, EXPLAIN delegation and plugin-driven DDL/DML; removed some legacy dialect/schema tests.

@coderabbitai
Copy link

coderabbitai bot commented Mar 12, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 145cb930-a9c2-4ca0-a9bd-a9c1ad1e48e9

📥 Commits

Reviewing files that changed from the base of the PR and between 935f9e2 and 3295bee.

📒 Files selected for processing (6)
  • Plugins/TableProPluginKit/PluginDatabaseDriver.swift
  • TablePro/Core/Plugins/PluginDriverAdapter.swift
  • TablePro/Core/SchemaTracking/SchemaStatementGenerator.swift
  • TablePro/Core/Services/Query/TableQueryBuilder.swift
  • TableProTests/Core/ClickHouse/ClickHouseDialectTests.swift
  • TableProTests/Core/SchemaTracking/SchemaStatementGeneratorPluginTests.swift

📝 Walkthrough

Walkthrough

Adds SQL dialect descriptors and ParameterStyle to the plugin API, introduces PluginDatabaseDriver hooks for DDL/DML/table-ops/EXPLAIN, routes query/DDL generation to plugins (with fallbacks), updates adapters/managers/builders to delegate to plugins, and expands tests for plugin delegation and parameter styles.

Changes

Cohort / File(s) Summary
Plugin Core Types
Plugins/TableProPluginKit/SQLDialectDescriptor.swift, Plugins/TableProPluginKit/SchemaTypes.swift, Plugins/TableProPluginKit/DriverPlugin.swift, Plugins/TableProPluginKit/PluginDatabaseDriver.swift
Add SQLDialectDescriptor, schema types (PluginColumnDefinition, PluginIndexDefinition, PluginForeignKeyDefinition), ParameterStyle enum, many optional PluginDatabaseDriver hooks (DDL, table ops, EXPLAIN, parameterStyle) with default implementations and parameter-substitution helpers.
Plugin Implementations
Plugins/*/*Plugin.swift, Plugins/*/*PluginDriver.swift
Plugins expose sqlDialect (many populated, some nil), parameterStyle, and buildExplainQuery(_); ClickHouse/MSSQL/Oracle add driver-side DML generation and rich query builders (some duplicated helpers present).
Plugin Adapter & Manager
TablePro/Core/Plugins/PluginDriverAdapter.swift, TablePro/Core/Plugins/PluginManager.swift
Expose schemaPluginDriver and queryBuildingPluginDriver, delegate parameterStyle and pluginGenerateStatements, add DDL/table-op delegations, and new sqlDialect(for:) lookup.
Schema & DDL Generation
TablePro/Core/SchemaTracking/SchemaStatementGenerator.swift, TablePro/Core/Database/DatabaseManager.swift, TablePro/Views/Structure/TableStructureView.swift
SchemaStatementGenerator now requires a PluginDatabaseDriver and delegates DDL generation to plugin hooks; callers (DatabaseManager, views) pass the plugin driver.
Query Dialect & Builders
TablePro/Core/Services/Query/SQLDialectProvider.swift, TablePro/Core/Services/Query/TableQueryBuilder.swift
Dialect factory prefers plugin-provided SQLDialectDescriptor wrapped by PluginDialectAdapter; TableQueryBuilder prefers plugin query builders and falls back to simplified generic LIMIT/OFFSET queries, removing many MSSQL/Oracle special cases.
Change Tracking / Statement Generation
TablePro/Core/ChangeTracking/SQLStatementGenerator.swift, TablePro/Core/ChangeTracking/DataChangeManager.swift
SQLStatementGenerator accepts parameterStyle and standardizes placeholder generation; DataChangeManager tightens UPDATE/DELETE detection to stricter prefixes.
Database Driver API rename
TablePro/Core/Database/DatabaseDriver.swift
Renamed protocol property noSqlPluginDriverqueryBuildingPluginDriver and adjusted default implementation.
UI wiring & table operations
TablePro/Views/Main/MainContentCoordinator.swift, TablePro/Views/Main/Extensions/MainContentCoordinator+TableOperations.swift
Coordinator wired to queryBuildingPluginDriver; runExplainQuery attempts plugin-based EXPLAIN; FK/truncate/drop operations prefer adapter-provided statements with existing fallbacks.
Tests
TableProTests/Core/..., TableProTests/Views/...
Add tests for parameter styles, explain plugin behavior, SQLDialectDescriptor/PluginDialectAdapter, SchemaStatementGenerator plugin delegation, and table-operation fallbacks; remove older per-dialect SchemaStatementGenerator test suites.
Changelog
CHANGELOG.md
Document new plugin dialects, ParameterStyle, DDL/DML/EXPLAIN hooks, and migration/migration notes.

Sequence Diagram(s)

sequenceDiagram
  participant UI as "UI / Coordinator"
  participant PM as "PluginManager"
  participant AD as "PluginDriverAdapter"
  participant SD as "SchemaStatementGenerator"
  participant DB as "Database"

  UI->>PM: request plugin/descriptor for databaseType
  PM-->>UI: return plugin driver / SQLDialectDescriptor
  UI->>AD: call buildExplainQuery / pluginGenerateStatements / DDL hooks
  AD-->>SD: delegate DDL generation request (or return nil)
  alt plugin returns SQL
    AD-->>UI: SQL statements (EXPLAIN / DML / DDL)
  else
    SD-->>UI: fallback built-in SQL statements
  end
  UI->>DB: execute SQL statements
  DB-->>UI: results
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 I nibble dialects, stash quotes in a den,

EXPLAINs unwrap like carrots, and placeholders hop when,
DDL blooms from plugin burrows, statements hum and sing,
The rabbit cheers: "Delegation rules the spring!"

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.62% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: plugin extensibility' clearly and concisely summarizes the main objective of the PR: implementing extensive plugin extensibility across five phases (dialect descriptors, parameter styles, DDL generation, table operations, and explain queries).

✏️ 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/connection-field-types

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

Copy link

@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: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
TablePro/Core/Services/Query/TableQueryBuilder.swift (1)

47-67: ⚠️ Potential issue | 🟠 Major

These fallbacks still emit invalid pagination for Oracle and MSSQL.

If pluginDriver is missing, all four builders drop to LIMIT ... OFFSET. TableQueryBuilder is still created without a plugin driver first, so startup/reconnect paths can now produce SQL that Oracle and MSSQL reject.

Also applies to: 89-120, 140-171, 197-247

TablePro/Views/Main/MainContentCoordinator.swift (1)

282-293: ⚠️ Potential issue | 🟠 Major

This makes table-tab sorting a no-op for plugin-backed SQL drivers.

queryBuilder.setPluginDriver(pluginDriver) now runs for every query-building plugin, but TableQueryBuilder.buildSortedQuery and buildMultiSortQuery currently return the original SQL unchanged whenever pluginDriver != nil. After the driver attaches, clicking a sortable column no longer rewrites the query for plugin-backed SQL databases.

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

In `@TablePro/Views/Main/MainContentCoordinator.swift` around lines 282 - 293, The
table sorting became a no-op because setupPluginDriver attaches a non-nil
pluginDriver to the queryBuilder, and TableQueryBuilder.buildSortedQuery /
buildMultiSortQuery currently short-circuit and return the original SQL whenever
pluginDriver != nil; update those methods to support plugin-backed drivers by
either delegating sort SQL construction to the plugin driver (e.g., call
pluginDriver.buildSortedQuery(...) or similar API) or, if the plugin doesn't
implement sorting, fall back to the existing local SQL-rewrite logic instead of
returning the original SQL; modify the implementations in TableQueryBuilder
(buildSortedQuery and buildMultiSortQuery) to check for a plugin-provided
sorting implementation and only return the unchanged SQL if neither the plugin
nor the local logic can produce a rewritten query.
TablePro/Views/Main/Extensions/MainContentCoordinator+TableOperations.swift (1)

84-105: ⚠️ Potential issue | 🔴 Critical

The new FK hooks are still unreachable for the excluded engines.

Lines 88-89 and Lines 102-103 can return custom plugin SQL, but generateTableOperationSQL still short-circuits FK handling behind the old fkApplicable check on Line 43. For .mssql, .oracle, .clickhouse, .duckdb, and .postgresql, the override path never runs even when ignoreForeignKeys is requested.

Suggested fix
-        let fkApplicable = dbType != .postgresql && dbType != .clickhouse
-            && dbType != .mssql && dbType != .oracle && dbType != .duckdb
-        let needsDisableFK = includeFKHandling && fkApplicable && truncates.union(deletes).contains { tableName in
+        let needsForeignKeyBypass = includeFKHandling && truncates.union(deletes).contains { tableName in
             options[tableName]?.ignoreForeignKeys == true
         }
+        let disableFKStatements = needsForeignKeyBypass ? fkDisableStatements(for: dbType) : []
+        let enableFKStatements = needsForeignKeyBypass ? fkEnableStatements(for: dbType) : []
@@
-        if needsDisableFK {
-            statements.append(contentsOf: fkDisableStatements(for: dbType))
+        if !disableFKStatements.isEmpty {
+            statements.append(contentsOf: disableFKStatements)
         }
@@
-        if needsDisableFK {
-            statements.append(contentsOf: fkEnableStatements(for: dbType))
+        if !enableFKStatements.isEmpty {
+            statements.append(contentsOf: enableFKStatements)
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TablePro/Views/Main/Extensions/MainContentCoordinator`+TableOperations.swift
around lines 84 - 105, generateTableOperationSQL currently short-circuits FK
handling using fkApplicable so plugin-provided FK SQL in
fkDisableStatements/fkEnableStatements is never used for .mssql, .oracle,
.clickhouse, .duckdb, and .postgresql even when ignoreForeignKeys is requested;
update generateTableOperationSQL to first ask
fkDisableStatements/fkEnableStatements (or check if
currentPluginDriverAdapter?.foreignKeyDisableStatements() != nil) or respect the
ignoreForeignKeys flag before skipping FK handling via fkApplicable, so that
when plugin overrides exist or ignoreForeignKeys == true the code uses
fkDisableStatements/fkEnableStatements for those DatabaseType cases instead of
bypassing them. Ensure references to fkApplicable, generateTableOperationSQL,
fkDisableStatements, fkEnableStatements, currentPluginDriverAdapter,
ignoreForeignKeys and the DatabaseType enum are used to locate and modify the
logic.
🧹 Nitpick comments (4)
Plugins/SQLiteDriverPlugin/SQLitePlugin.swift (1)

40-75: Use double-quote (") as the standard identifier quote character for SQLite.

SQLite's standard identifier quote character per the official documentation is the double-quote ("), not backtick. While SQLite does accept backticks for MySQL compatibility, the canonical choice is double-quote.

♻️ Suggested change
 static let sqlDialect: SQLDialectDescriptor? = SQLDialectDescriptor(
-    identifierQuote: "`",
+    identifierQuote: "\"",
     keywords: [
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Plugins/SQLiteDriverPlugin/SQLitePlugin.swift` around lines 40 - 75, The SQL
dialect currently uses a backtick for the identifierQuote in the
SQLDialectDescriptor (sqlDialect) but SQLite's canonical identifier quote is a
double-quote; update the SQLDialectDescriptor initialiser for sqlDialect to set
identifierQuote to a double-quote character (") instead of "`" so identifier
quoting matches SQLite's standard.
TablePro/Views/Main/MainContentCoordinator.swift (1)

682-725: Move the new EXPLAIN flow into a coordinator extension.

This logic fits the existing Views/Main/Extensions/ split better than the root coordinator file and will be easier to maintain beside the existing database-specific helpers.

Based on learnings, "Use MainContentCoordinator as the central coordinator for the main UI flow, split across extension files in Views/Main/Extensions/" and "When adding MainContentCoordinator functionality, add a new extension file (e.g., +Alerts.swift) rather than growing the main coordinator file".

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

In `@TablePro/Views/Main/MainContentCoordinator.swift` around lines 682 - 725,
Move the EXPLAIN-query flow out of the main MainContentCoordinator file into a
new extension file under Views/Main/Extensions by creating an extension on
MainContentCoordinator (e.g., MainContentCoordinator+Explain.swift) that
contains the logic that builds explainSQL (using
DatabaseManager.shared.driver(for:), PluginDriverAdapter.buildExplainQuery,
Self.buildMongoExplain, Self.buildRedisDebugCommand), checks safe mode via
SafeModeGuard, and calls executeQueryInternal(explainSQL); keep the same
parameter access (connection, connectionId, stmt) and ensure the original method
in MainContentCoordinator now delegates to the new extension method to preserve
behavior and `@MainActor` Task usage.
TableProTests/Views/Main/TableOperationsPluginTests.swift (1)

14-34: Add one plugin-override case to this suite.

Right now these tests only prove the fallback switches. A regression where custom FK/truncate/drop statements are never reached would still pass; a small mock-adapter case for an engine currently excluded by the old gate on Line 43 would lock down the new plugin-first path.

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

In `@TableProTests/Views/Main/TableOperationsPluginTests.swift` around lines 14 -
34, Add a test case in the TableOperationsPluginTests suite that verifies the
coordinator uses a plugin-first path by creating a MainContentCoordinator via
makeCoordinator (or TestFixtures.makeConnection) with a mock plugin/adapter
registered that returns custom FK/truncate/drop SQL, then assert the coordinator
calls the plugin-provided SQL instead of the built-in DatabaseType switches;
implement the mock adapter (or small fake driver) that overrides the statements,
register it with the coordinator or connection before invoking the operations,
and assert the returned SQL strings come from the mock to lock down the
plugin-override behavior.
TableProTests/Core/Plugins/ExplainQueryPluginTests.swift (1)

6-8: Import ordering: system frameworks should precede local modules.

Per coding guidelines, imports should be ordered: system frameworks alphabetically → third-party → local. Testing is an Apple system framework and should come before TableProPluginKit.

Proposed fix
 import Foundation
-import TableProPluginKit
 import Testing
+
+import TableProPluginKit
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TableProTests/Core/Plugins/ExplainQueryPluginTests.swift` around lines 6 - 8,
Reorder the import statements so system frameworks come first and
local/third-party modules after: move "import Testing" to appear before "import
TableProPluginKit" and ensure system imports (import Foundation, import Testing)
are alphabetized if multiple; leave "import TableProPluginKit" after the system
imports.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Plugins/ClickHouseDriverPlugin/ClickHousePlugin.swift`:
- Around line 606-643: The update/delete generators currently call
buildWhereClause and will produce statements that can affect multiple identical
rows; change generateClickHouseUpdate and generateClickHouseDelete to first
verify that a stable unique predicate for the target row can be derived (e.g. by
checking for a primary key/unique key in the provided columns or ensuring some
column(s) in change.originalRow are unique/non-null and not duplicated) before
calling buildWhereClause, and return nil if no unique predicate can be
guaranteed; reference the functions generateClickHouseUpdate,
generateClickHouseDelete, buildWhereClause and the PluginRowChange.originalRow
when adding this uniqueness guard so the driver never emits UPDATE/DELETE for
non-uniquely-identified row images.

In `@Plugins/MongoDBDriverPlugin/MongoDBPluginDriver.swift`:
- Around line 441-499: The SQL and collection interpolations in
buildExplainQuery(_:) are not escaped and can break the generated JSON; update
every string interpolation to use escapeJsonString(_:) — e.g. wrap sql in the
guard fallback and the default case as escapeJsonString(sql), wrap collection
names in all switch arms (.find, .findOne, .aggregate, .countDocuments,
.deleteOne, .deleteMany, .updateOne, .updateMany, .findOneAndUpdate,
.findOneAndReplace, .findOneAndDelete) and also escape any direct string pieces
used to build cmd/findDoc (like the "find" field and the findAndModify cmd) so
that any embedded " or \ are properly escaped before producing the
db.runCommand(...) text.

In `@Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift`:
- Around line 514-530: The current logic uses a fake-PK heuristic (hasPk
checking only that columns.first appears in change.cellChanges) and then calls
generateMssqlBatchDelete which creates OR-combined full-row predicates; instead,
only allow batch deletes when you can verify a real primary key (e.g., an
explicit PK flag or unique key column), and otherwise fall back to per-row
deletes using generateMssqlDelete that emits DELETE TOP (1) to avoid collapsing
duplicates; update the branch around hasPk to validate true uniqueness rather
than presence of columns.first (or add an isPrimaryKey check), call
generateMssqlBatchDelete only when that validation passes, and in all other
cases iterate deleteChanges and use generateMssqlDelete; apply the same change
to the other similar block (the one around lines 597-630) so both places avoid
batching on the fake-PK heuristic.

In `@Plugins/OracleDriverPlugin/OraclePlugin.swift`:
- Around line 911-913: The "REGEX" branch in OraclePlugin.swift currently maps
to a LIKE '%...%' which is incorrect; update the case "REGEX" handling to use
Oracle's REGEXP_LIKE function instead of LIKE. Specifically, build and return a
string like REGEXP_LIKE(<quoted>, '<escapedPattern>') where you escape single
quotes in value (same as escaped = value.replacingOccurrences(of: "'", with:
"''")) and use the existing quoted identifier variable (quoted) as the first
argument; keep any pattern modifiers (e.g., case sensitivity) consistent with
other filters if needed.
- Around line 633-648: The current build of the INSERT statement skips returning
anything when nonDefaultColumns is empty (all values are "__DEFAULT__"), which
drops default-only inserts; update the logic around nonDefaultColumns/parameters
in the function that constructs the INSERT (references: nonDefaultColumns,
parameters, escapeOracleIdentifier, values, columns) so that when
nonDefaultColumns.isEmpty you return a valid Oracle INSERT that preserves
defaults (e.g. "INSERT INTO <table> DEFAULT VALUES" with an empty parameters
array) instead of returning nil; ensure you still escape the table name via
escapeOracleIdentifier and return the tuple (statement: sql, parameters:
parameters).

In `@Plugins/RedisDriverPlugin/RedisPluginDriver.swift`:
- Around line 364-373: The current buildExplainQuery(_:) re-tokenizes SQL by
whitespace and returns bogus explains; replace that logic to use the existing
RedisCommandParser (or equivalent) to parse the input into a RedisCommand/args,
then only return an explain surrogate when the parsed command is a single-key
operation (e.g., GET/SET/DEL with exactly one key argument); otherwise return
nil (do not default to "INFO commandstats"). Update buildExplainQuery to call
RedisCommandParser, inspect the parsed command name and arguments, and construct
"DEBUG OBJECT <key>" only when the parser confirms a single key.

In `@Plugins/TableProPluginKit/PluginDatabaseDriver.swift`:
- Around line 3-6: The default parameter binding currently only replaces "?"
placeholders but the new ParameterStyle enum allows .dollar, so update the
default executeParameterized(query:parameters:) implementation (in the
PluginDatabaseDriver extension/impl) to honor ParameterStyle: if parameterStyle
== .questionMark continue substituting "?" in order, if parameterStyle ==
.dollar substitute $1, $2, ... (1-based) for each parameter; alternatively, if
you prefer fail-fast, change the default to throw/fatalError when parameterStyle
== .dollar and document that drivers using .dollar must override
executeParameterized; reference ParameterStyle and
executeParameterized(query:parameters:) so reviewers can find and patch the
behavior.

In `@TablePro/Core/ChangeTracking/SQLStatementGenerator.swift`:
- Around line 29-50: The INSERT placeholder numbering is computed from the full
parameters array and then SQLFunctionLiteral entries are stripped, causing gaps
with .dollar style (e.g., VALUES (NOW(), $2) but only one bound value); update
generateInsertSQLFromStoredData(rowIndex:values:) to build the placeholder list
from the final bind array (i.e., remove or skip SQLFunctionLiteral entries
before generating $n placeholders) so dollar placeholders are densely numbered,
and apply the same fix to the similar logic referenced at lines 123-130; use the
types/methods SQLFunctionLiteral, ParameterStyle (.dollar), and the
generateInsertSQLFromStoredData(rowIndex:values:) routine to locate and change
the ordering (compute binds first, then generate placeholders).

In `@TablePro/Core/Services/Query/SQLDialectProvider.swift`:
- Around line 496-509: The createDialect(for:) function currently bypasses
`@MainActor` isolation using Thread.isMainThread + MainActor.assumeIsolated and
can silently fall back to builtInDialect when called off the main actor; change
the function signature to be `@MainActor` so calls are properly isolated when
accessing PluginManager.shared.sqlDialect, and remove the Thread.isMainThread /
MainActor.assumeIsolated branch so the function always queries PluginManager and
falls back to builtInDialect only when the plugin descriptor is nil; if
synchronous background calls are required, first cache
PluginManager.shared.sqlDialect on the main actor into a value-type descriptor
and pass that value into background work instead of calling PluginManager from
background threads.

In `@TablePro/Views/Main/MainContentCoordinator.swift`:
- Around line 676-680: The ClickHouse EXPLAIN handler currently calls
runClickHouseExplain and returns before the confirmation/safe-mode block,
bypassing the "confirm all queries" gate; update the logic so that when
connection.type == .clickhouse you route through the same confirmation flow used
for other queries (i.e., the existing confirmation block) and only call
runClickHouseExplain after confirmation succeeds (or respect the safe-mode
flag), instead of calling runClickHouseExplain immediately and returning.

---

Outside diff comments:
In `@TablePro/Views/Main/Extensions/MainContentCoordinator`+TableOperations.swift:
- Around line 84-105: generateTableOperationSQL currently short-circuits FK
handling using fkApplicable so plugin-provided FK SQL in
fkDisableStatements/fkEnableStatements is never used for .mssql, .oracle,
.clickhouse, .duckdb, and .postgresql even when ignoreForeignKeys is requested;
update generateTableOperationSQL to first ask
fkDisableStatements/fkEnableStatements (or check if
currentPluginDriverAdapter?.foreignKeyDisableStatements() != nil) or respect the
ignoreForeignKeys flag before skipping FK handling via fkApplicable, so that
when plugin overrides exist or ignoreForeignKeys == true the code uses
fkDisableStatements/fkEnableStatements for those DatabaseType cases instead of
bypassing them. Ensure references to fkApplicable, generateTableOperationSQL,
fkDisableStatements, fkEnableStatements, currentPluginDriverAdapter,
ignoreForeignKeys and the DatabaseType enum are used to locate and modify the
logic.

In `@TablePro/Views/Main/MainContentCoordinator.swift`:
- Around line 282-293: The table sorting became a no-op because
setupPluginDriver attaches a non-nil pluginDriver to the queryBuilder, and
TableQueryBuilder.buildSortedQuery / buildMultiSortQuery currently short-circuit
and return the original SQL whenever pluginDriver != nil; update those methods
to support plugin-backed drivers by either delegating sort SQL construction to
the plugin driver (e.g., call pluginDriver.buildSortedQuery(...) or similar API)
or, if the plugin doesn't implement sorting, fall back to the existing local
SQL-rewrite logic instead of returning the original SQL; modify the
implementations in TableQueryBuilder (buildSortedQuery and buildMultiSortQuery)
to check for a plugin-provided sorting implementation and only return the
unchanged SQL if neither the plugin nor the local logic can produce a rewritten
query.

---

Nitpick comments:
In `@Plugins/SQLiteDriverPlugin/SQLitePlugin.swift`:
- Around line 40-75: The SQL dialect currently uses a backtick for the
identifierQuote in the SQLDialectDescriptor (sqlDialect) but SQLite's canonical
identifier quote is a double-quote; update the SQLDialectDescriptor initialiser
for sqlDialect to set identifierQuote to a double-quote character (") instead of
"`" so identifier quoting matches SQLite's standard.

In `@TablePro/Views/Main/MainContentCoordinator.swift`:
- Around line 682-725: Move the EXPLAIN-query flow out of the main
MainContentCoordinator file into a new extension file under
Views/Main/Extensions by creating an extension on MainContentCoordinator (e.g.,
MainContentCoordinator+Explain.swift) that contains the logic that builds
explainSQL (using DatabaseManager.shared.driver(for:),
PluginDriverAdapter.buildExplainQuery, Self.buildMongoExplain,
Self.buildRedisDebugCommand), checks safe mode via SafeModeGuard, and calls
executeQueryInternal(explainSQL); keep the same parameter access (connection,
connectionId, stmt) and ensure the original method in MainContentCoordinator now
delegates to the new extension method to preserve behavior and `@MainActor` Task
usage.

In `@TableProTests/Core/Plugins/ExplainQueryPluginTests.swift`:
- Around line 6-8: Reorder the import statements so system frameworks come first
and local/third-party modules after: move "import Testing" to appear before
"import TableProPluginKit" and ensure system imports (import Foundation, import
Testing) are alphabetized if multiple; leave "import TableProPluginKit" after
the system imports.

In `@TableProTests/Views/Main/TableOperationsPluginTests.swift`:
- Around line 14-34: Add a test case in the TableOperationsPluginTests suite
that verifies the coordinator uses a plugin-first path by creating a
MainContentCoordinator via makeCoordinator (or TestFixtures.makeConnection) with
a mock plugin/adapter registered that returns custom FK/truncate/drop SQL, then
assert the coordinator calls the plugin-provided SQL instead of the built-in
DatabaseType switches; implement the mock adapter (or small fake driver) that
overrides the statements, register it with the coordinator or connection before
invoking the operations, and assert the returned SQL strings come from the mock
to lock down the plugin-override behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0763ad91-1c15-4556-87e6-9ec802aaa1a8

📥 Commits

Reviewing files that changed from the base of the PR and between e95f919 and 08ed63f.

📒 Files selected for processing (35)
  • CHANGELOG.md
  • Plugins/ClickHouseDriverPlugin/ClickHousePlugin.swift
  • Plugins/DuckDBDriverPlugin/DuckDBPlugin.swift
  • Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift
  • Plugins/MongoDBDriverPlugin/MongoDBPlugin.swift
  • Plugins/MongoDBDriverPlugin/MongoDBPluginDriver.swift
  • Plugins/MySQLDriverPlugin/MySQLPlugin.swift
  • Plugins/MySQLDriverPlugin/MySQLPluginDriver.swift
  • Plugins/OracleDriverPlugin/OraclePlugin.swift
  • Plugins/PostgreSQLDriverPlugin/PostgreSQLPlugin.swift
  • Plugins/PostgreSQLDriverPlugin/PostgreSQLPluginDriver.swift
  • Plugins/PostgreSQLDriverPlugin/RedshiftPluginDriver.swift
  • Plugins/RedisDriverPlugin/RedisPlugin.swift
  • Plugins/RedisDriverPlugin/RedisPluginDriver.swift
  • Plugins/SQLiteDriverPlugin/SQLitePlugin.swift
  • Plugins/TableProPluginKit/DriverPlugin.swift
  • Plugins/TableProPluginKit/PluginDatabaseDriver.swift
  • Plugins/TableProPluginKit/SQLDialectDescriptor.swift
  • Plugins/TableProPluginKit/SchemaTypes.swift
  • TablePro/Core/ChangeTracking/SQLStatementGenerator.swift
  • TablePro/Core/Database/DatabaseDriver.swift
  • TablePro/Core/Database/DatabaseManager.swift
  • TablePro/Core/Plugins/PluginDriverAdapter.swift
  • TablePro/Core/Plugins/PluginManager.swift
  • TablePro/Core/SchemaTracking/SchemaStatementGenerator.swift
  • TablePro/Core/Services/Query/SQLDialectProvider.swift
  • TablePro/Core/Services/Query/TableQueryBuilder.swift
  • TablePro/Views/Main/Extensions/MainContentCoordinator+TableOperations.swift
  • TablePro/Views/Main/MainContentCoordinator.swift
  • TablePro/Views/Structure/TableStructureView.swift
  • TableProTests/Core/ChangeTracking/SQLStatementGeneratorParameterStyleTests.swift
  • TableProTests/Core/Plugins/ExplainQueryPluginTests.swift
  • TableProTests/Core/Plugins/SQLDialectDescriptorTests.swift
  • TableProTests/Core/SchemaTracking/SchemaStatementGeneratorPluginTests.swift
  • TableProTests/Views/Main/TableOperationsPluginTests.swift

@datlechin datlechin changed the title feat: plugin extensibility - Group A phases feat: plugin extensibility Mar 12, 2026
Copy link

@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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
TablePro/Core/Services/Query/TableQueryBuilder.swift (1)

69-85: ⚠️ Potential issue | 🟠 Major

Preserve filter/search semantics in the fallback path.

These three fallbacks now return a plain SELECT * ... LIMIT/OFFSET, so when the plugin driver is unavailable or a plugin returns nil, active filters and quick-search text are silently ignored. That turns the table controls into no-ops instead of degrading gracefully. Please keep a generic WHERE/ORDER BY implementation here rather than falling back to an unfiltered browse query.

Also applies to: 95-107, 120-137

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

In `@TablePro/Core/Services/Query/TableQueryBuilder.swift` around lines 69 - 85,
The fallback path currently returns an unfiltered "SELECT *" when pluginDriver
is nil or buildFilteredQuery returns nil, which drops active filters,
quick-search text, and sort semantics; update the fallback in the function that
calls pluginDriver.buildFilteredQuery (referencing pluginDriver,
buildFilteredQuery, sortColumnsAsTuples, filters, sortState, logicMode, columns,
limit, offset and databaseType.quoteIdentifier) to construct a generic SQL
string that preserves WHERE (from filters and quick-search), ORDER BY (from
sortColumnsAsTuples/sortState), and pagination (limit/offset), and apply the
same change to the other fallback sites noted (lines covering the other
occurrences) so behavior degrades gracefully instead of ignoring filters and
search.
♻️ Duplicate comments (3)
TablePro/Core/ChangeTracking/SQLStatementGenerator.swift (1)

178-192: ⚠️ Potential issue | 🟠 Major

Keep $n placeholders dense when SQL literals are inlined.

The placeholder index is still derived from the full parameters array and only afterwards SQLFunctionLiteral entries are removed from the bind list. With .dollar, a row like [NOW(), "x"] becomes VALUES (NOW(), $2) but only one value is bound. Build the bind array first, or increment the placeholder counter only for values that will actually be bound.

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

In `@TablePro/Core/ChangeTracking/SQLStatementGenerator.swift` around lines 178 -
192, The INSERT placeholder numbering is computed from the full parameters array
which leaves gaps when SQLFunctionLiteral entries are inlined; change the logic
in the block that builds placeholders and bindParameters (referencing
nonDefaultColumns, parameters, SQLFunctionLiteral, placeholder(at:), and
ParameterizedStatement) so that you first build the bindParameters array (or
otherwise only increment the placeholder counter for non-SQLFunctionLiteral
entries) and then generate placeholders using the bindParameters' indices — this
ensures dollar-style placeholders are dense and match the number/order of bound
parameters.
TablePro/Views/Main/MainContentCoordinator.swift (1)

676-680: ⚠️ Potential issue | 🟡 Minor

Keep ClickHouse EXPLAIN behind the existing safe-mode gate.

This early return still bypasses the confirmation flow below, so connections configured to confirm all queries no longer apply that policy to ClickHouse EXPLAIN. Route this path through the same permission check before calling runClickHouseExplain.

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

In `@TablePro/Views/Main/MainContentCoordinator.swift` around lines 676 - 680, The
ClickHouse-specific branch currently short-circuits the confirmation flow by
calling runClickHouseExplain(variant: .plan) and returning; instead, remove the
early return and route this path through the same permission/confirmation check
used for other queries so connections with "confirm all queries" still prompt.
Concretely, when connection.type == .clickhouse, invoke the existing
confirmation/permission routine (the same code used below for normal queries)
and only call runClickHouseExplain(variant: .plan) if that check grants
permission; do not bypass or return early from the function.
TablePro/Core/Services/Query/SQLDialectProvider.swift (1)

39-49: ⚠️ Potential issue | 🟠 Major

Don't gate dialect resolution on Thread.isMainThread.

Off-main callers always get EmptyDialect, so formatter/highlighter paths silently lose plugin keywords, functions, and data types. Thread.isMainThread also is not the right isolation contract for MainActor.assumeIsolated here; this factory should either be @MainActor or consume a descriptor value that was already resolved on the main actor.

In Swift concurrency, is `Thread.isMainThread` sufficient to make `MainActor.assumeIsolated { ... }` safe, or must the caller already be isolated to `@MainActor`? What is the recommended pattern for a synchronous factory that needs to read `@MainActor` state?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TablePro/Core/Services/Query/SQLDialectProvider.swift` around lines 39 - 49,
The factory currently returns EmptyDialect for off-main callers because it gates
plugin resolution with Thread.isMainThread and then uses
MainActor.assumeIsolated incorrectly; remove the Thread.isMainThread check and
ensure plugin access is done on the MainActor—either mark createDialect(for:) as
`@MainActor` and call PluginManager.shared.sqlDialect(for:) directly (return
PluginDialectAdapter or EmptyDialect), or if you cannot make the API
main-isolated, change the factory to accept a pre-resolved descriptor (e.g. pass
descriptor: SQLDialectDescriptor?) or make the factory async and use await
MainActor.run { PluginManager.shared.sqlDialect(for:) } so callers off the main
actor still get the real descriptor rather than EmptyDialect; update references
to createDialect, SQLDialectProvider, PluginManager.shared.sqlDialect(for:),
MainActor.assumeIsolated, PluginDialectAdapter, and EmptyDialect accordingly.
🧹 Nitpick comments (2)
TablePro/Views/Main/MainContentCoordinator.swift (1)

282-293: Please move this new coordinator behavior into an extension file.

This file is already carrying a lot of unrelated responsibilities, and adding setupPluginDriver() here makes that worse. I'd move this into a MainContentCoordinator+Plugins.swift-style extension to keep the main coordinator focused.

Based on learnings: Use MainContentCoordinator as the central coordinator for the main UI flow, split across extension files in Views/Main/Extensions/, and when adding MainContentCoordinator functionality, add a new extension file rather than growing the main coordinator file.

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

In `@TablePro/Views/Main/MainContentCoordinator.swift` around lines 282 - 293, The
new setupPluginDriver() method should be moved out of the large
MainContentCoordinator.swift into an extension file: create a new file named
MainContentCoordinator+Plugins.swift under Views/Main/Extensions and declare
"extension MainContentCoordinator" there; move the entire setupPluginDriver()
implementation (including its guard for DatabaseManager.shared.driver(for:
connectionId), use of driver.queryBuildingPluginDriver,
queryBuilder.setPluginDriver(_:), changeManager.pluginDriver assignment, and
NotificationCenter removal logic referencing pluginDriverObserver) into that
extension and keep the same access level (private) and imports; finally remove
the original method from MainContentCoordinator.swift so behavior is identical
but responsibilities are separated.
TableProTests/Core/SchemaTracking/SchemaStatementGeneratorPluginTests.swift (1)

287-303: Clarify: handler returns 2 strings but test expects 1 statement.

The modifyPrimaryKeyHandler returns an array with 2 SQL strings, but the test asserts stmts.count == 1. This implies SchemaStatementGenerator joins multiple SQL strings into a single SchemaStatement. If this is intentional, consider adding a brief comment to clarify this consolidation behavior for future maintainers.

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

In `@TableProTests/Core/SchemaTracking/SchemaStatementGeneratorPluginTests.swift`
around lines 287 - 303, The test is confusing because modifyPrimaryKeyHandler
returns two SQL strings but the assertion expects one SchemaStatement from
SchemaStatementGenerator.generate; either update the test to expect two
statements or (preferred) add a brief clarifying comment where
mock.modifyPrimaryKeyHandler is defined (or near
SchemaStatementGenerator.generate usage) explaining that the generator
consolidates multiple SQL strings returned by the plugin into a single
SchemaStatement (and thus stmts.count == 1), referencing
modifyPrimaryKeyHandler, SchemaStatementGenerator, and generate so future
maintainers understand the consolidation behavior.
🤖 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/SchemaTracking/SchemaStatementGenerator.swift`:
- Around line 47-49: The loop in SchemaStatementGenerator.generate(changes:)
currently drops changes when generateStatement(for:) returns nil; instead, when
generateStatement(for:) yields nil, call the generic fallback SQL generator
(implement or call something like generateGenericStatement(for:) and use its
result) and only skip/throw if that fallback also fails—alternatively, surface
an error to the caller rather than silently continue; update the guard let stmt
= try generateStatement(for: change) else { continue } to attempt the generic
fallback or throw a descriptive error so DDL operations are not silently
no‑oped.
- Around line 23-37: Stored property primaryKeyConstraintName is never used in
SchemaStatementGenerator.generateModifyPrimaryKey and either should be removed
or threaded into the plugin call; update the code so the plugin receives the
constraint name: either (A) remove primaryKeyConstraintName from the initializer
and property and any mention in comments if no DB needs it, or (B) extend the
PluginDatabaseDriver protocol (and its implementations) to accept an optional
primaryKeyConstraintName in generateModifyPrimaryKeySQL and change
SchemaStatementGenerator.generateModifyPrimaryKey to pass
self.primaryKeyConstraintName along with table, oldColumns, newColumns when
calling generateModifyPrimaryKeySQL; ensure the init signature
(primaryKeyConstraintName: String?) and stored property are kept only for option
B and tests updated accordingly.

In `@TablePro/Core/Services/Query/TableQueryBuilder.swift`:
- Around line 144-146: The code in TableQueryBuilder currently short-circuits
and returns baseQuery whenever pluginDriver is non-nil, which prevents
buildMultiSortQuery(...) and any ORDER BY changes from being applied; remove or
alter that early return so plugin-backed tables still apply the sort: locate the
conditional checking pluginDriver (the branch that returns baseQuery) and
instead merge or replace baseQuery with the result of buildMultiSortQuery(...)
(or otherwise apply the constructed ORDER BY clauses) before returning; ensure
the same change is applied to the other identical branch that returns early (the
second instance around the build logic) so server-side sorting via
MainContentCoordinator.handleSort works even when pluginDriver is attached.

In `@TableProTests/Core/ClickHouse/ClickHouseDialectTests.swift`:
- Around line 15-19: The test testFactoryCreatesDialect currently allows
EmptyDialect because it accepts either "\"" or "`"; change the assertion to
verify ClickHouse-specific metadata returned by
SQLDialectFactory.createDialect(for: .clickhouse) — for example assert the
dialect is not EmptyDialect (check type/name) and/or assert the identifierQuote
equals "`" (the ClickHouse quote) or another ClickHouse-only property so the
test fails if the factory falls back to EmptyDialect; reference
SQLDialectFactory.createDialect, testFactoryCreatesDialect, and
EmptyDialect.identifierQuote when making the replacement.

---

Outside diff comments:
In `@TablePro/Core/Services/Query/TableQueryBuilder.swift`:
- Around line 69-85: The fallback path currently returns an unfiltered "SELECT
*" when pluginDriver is nil or buildFilteredQuery returns nil, which drops
active filters, quick-search text, and sort semantics; update the fallback in
the function that calls pluginDriver.buildFilteredQuery (referencing
pluginDriver, buildFilteredQuery, sortColumnsAsTuples, filters, sortState,
logicMode, columns, limit, offset and databaseType.quoteIdentifier) to construct
a generic SQL string that preserves WHERE (from filters and quick-search), ORDER
BY (from sortColumnsAsTuples/sortState), and pagination (limit/offset), and
apply the same change to the other fallback sites noted (lines covering the
other occurrences) so behavior degrades gracefully instead of ignoring filters
and search.

---

Duplicate comments:
In `@TablePro/Core/ChangeTracking/SQLStatementGenerator.swift`:
- Around line 178-192: The INSERT placeholder numbering is computed from the
full parameters array which leaves gaps when SQLFunctionLiteral entries are
inlined; change the logic in the block that builds placeholders and
bindParameters (referencing nonDefaultColumns, parameters, SQLFunctionLiteral,
placeholder(at:), and ParameterizedStatement) so that you first build the
bindParameters array (or otherwise only increment the placeholder counter for
non-SQLFunctionLiteral entries) and then generate placeholders using the
bindParameters' indices — this ensures dollar-style placeholders are dense and
match the number/order of bound parameters.

In `@TablePro/Core/Services/Query/SQLDialectProvider.swift`:
- Around line 39-49: The factory currently returns EmptyDialect for off-main
callers because it gates plugin resolution with Thread.isMainThread and then
uses MainActor.assumeIsolated incorrectly; remove the Thread.isMainThread check
and ensure plugin access is done on the MainActor—either mark
createDialect(for:) as `@MainActor` and call PluginManager.shared.sqlDialect(for:)
directly (return PluginDialectAdapter or EmptyDialect), or if you cannot make
the API main-isolated, change the factory to accept a pre-resolved descriptor
(e.g. pass descriptor: SQLDialectDescriptor?) or make the factory async and use
await MainActor.run { PluginManager.shared.sqlDialect(for:) } so callers off the
main actor still get the real descriptor rather than EmptyDialect; update
references to createDialect, SQLDialectProvider,
PluginManager.shared.sqlDialect(for:), MainActor.assumeIsolated,
PluginDialectAdapter, and EmptyDialect accordingly.

In `@TablePro/Views/Main/MainContentCoordinator.swift`:
- Around line 676-680: The ClickHouse-specific branch currently short-circuits
the confirmation flow by calling runClickHouseExplain(variant: .plan) and
returning; instead, remove the early return and route this path through the same
permission/confirmation check used for other queries so connections with
"confirm all queries" still prompt. Concretely, when connection.type ==
.clickhouse, invoke the existing confirmation/permission routine (the same code
used below for normal queries) and only call runClickHouseExplain(variant:
.plan) if that check grants permission; do not bypass or return early from the
function.

---

Nitpick comments:
In `@TablePro/Views/Main/MainContentCoordinator.swift`:
- Around line 282-293: The new setupPluginDriver() method should be moved out of
the large MainContentCoordinator.swift into an extension file: create a new file
named MainContentCoordinator+Plugins.swift under Views/Main/Extensions and
declare "extension MainContentCoordinator" there; move the entire
setupPluginDriver() implementation (including its guard for
DatabaseManager.shared.driver(for: connectionId), use of
driver.queryBuildingPluginDriver, queryBuilder.setPluginDriver(_:),
changeManager.pluginDriver assignment, and NotificationCenter removal logic
referencing pluginDriverObserver) into that extension and keep the same access
level (private) and imports; finally remove the original method from
MainContentCoordinator.swift so behavior is identical but responsibilities are
separated.

In `@TableProTests/Core/SchemaTracking/SchemaStatementGeneratorPluginTests.swift`:
- Around line 287-303: The test is confusing because modifyPrimaryKeyHandler
returns two SQL strings but the assertion expects one SchemaStatement from
SchemaStatementGenerator.generate; either update the test to expect two
statements or (preferred) add a brief clarifying comment where
mock.modifyPrimaryKeyHandler is defined (or near
SchemaStatementGenerator.generate usage) explaining that the generator
consolidates multiple SQL strings returned by the plugin into a single
SchemaStatement (and thus stmts.count == 1), referencing
modifyPrimaryKeyHandler, SchemaStatementGenerator, and generate so future
maintainers understand the consolidation behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c384a126-d3ab-485f-8c6b-70992b71e64a

📥 Commits

Reviewing files that changed from the base of the PR and between 08ed63f and d949d08.

📒 Files selected for processing (16)
  • TablePro/Core/ChangeTracking/DataChangeManager.swift
  • TablePro/Core/ChangeTracking/SQLStatementGenerator.swift
  • TablePro/Core/Database/DatabaseManager.swift
  • TablePro/Core/SchemaTracking/SchemaStatementGenerator.swift
  • TablePro/Core/Services/Query/SQLDialectProvider.swift
  • TablePro/Core/Services/Query/TableQueryBuilder.swift
  • TablePro/Views/Main/Extensions/MainContentCoordinator+MongoDB.swift
  • TablePro/Views/Main/Extensions/MainContentCoordinator+Redis.swift
  • TablePro/Views/Main/Extensions/MainContentCoordinator+TableOperations.swift
  • TablePro/Views/Main/MainContentCoordinator.swift
  • TablePro/Views/Structure/TableStructureView.swift
  • TableProTests/Core/ClickHouse/ClickHouseDialectTests.swift
  • TableProTests/Core/Plugins/SQLDialectDescriptorTests.swift
  • TableProTests/Core/SchemaTracking/SchemaStatementGeneratorMSSQLTests.swift
  • TableProTests/Core/SchemaTracking/SchemaStatementGeneratorPluginTests.swift
  • TableProTests/Core/SchemaTracking/SchemaStatementGeneratorTests.swift
💤 Files with no reviewable changes (4)
  • TablePro/Views/Main/Extensions/MainContentCoordinator+MongoDB.swift
  • TableProTests/Core/SchemaTracking/SchemaStatementGeneratorMSSQLTests.swift
  • TableProTests/Core/SchemaTracking/SchemaStatementGeneratorTests.swift
  • TablePro/Views/Main/Extensions/MainContentCoordinator+Redis.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • TablePro/Views/Main/Extensions/MainContentCoordinator+TableOperations.swift

@datlechin datlechin merged commit 5d5113b into main Mar 12, 2026
2 checks passed
@datlechin datlechin deleted the feat/connection-field-types branch March 12, 2026 11:34
@coderabbitai coderabbitai bot mentioned this pull request Mar 12, 2026
6 tasks
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.

1 participant