Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- AI Chat: GitHub Copilot tool registration was failing with "Expected string" schema validation errors. Optional fields now register with `type: "string"` instead of `type: ["string", "null"]` and are excluded from `required`, which Copilot's LSP validator accepts.
- MySQL/MariaDB: `BIT(N)` columns now display as decimal numbers (`0`, `1`, `255`) instead of raw bytes that showed up as control characters like `^A` in the data grid. (#1272)
- Structure tab: Refresh and ⌘R now show external schema changes immediately. Previously a column renamed from another session stayed stale on the Columns and DDL sub-tabs until the user switched tabs, and the ClickHouse Parts sub-tab ignored Refresh entirely. (#1281)
- Query editor autocomplete now reflects external column renames after Refresh. Previously the schema cache kept suggesting the old column name until the connection was reopened.
- ClickHouse, BigQuery, CloudflareD1, LibSQL, Etcd, and DynamoDB: long-running queries no longer fail at 30 seconds when Settings > Query timeout is set higher. The HTTP transport now uses the configured query timeout plus a 30-second grace, so the server's `max_execution_time` (or equivalent) fires before the client gives up. Setting "No limit" raises the transport ceiling to 1 hour. (#1267)
- AI Chat: DeepSeek V4 thinking content (`reasoning_content`) is now captured during streaming and passed back in subsequent turns, fixing 400 errors when using deepseek-v4-pro or deepseek-v4-flash.
- MongoDB: the connection form now shows a Username field. It was hidden for databases where authentication is optional, so connections to auth-enabled servers saved with no credentials and every query failed with "requires authentication" even though the connection looked healthy.
Expand Down
10 changes: 10 additions & 0 deletions TablePro/Core/Autocomplete/SQLSchemaProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,16 @@ actor SQLSchemaProvider {
startEagerColumnLoad()
}

func clearColumnCache() {
eagerColumnTask?.cancel()
eagerColumnTask = nil
columnCache.removeAll()
columnAccessOrder.removeAll()
if cachedDriver != nil {
startEagerColumnLoad()
}
}

// MARK: - Eager Column Loading

private func startEagerColumnLoad() {
Expand Down
9 changes: 0 additions & 9 deletions TablePro/Core/ChangeTracking/DataChangeManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@ final class DataChangeManager: ChangeManaging {
)
guard recorded else {
hasChanges = !pending.isEmpty
reloadVersion += 1
return
}
registerUndo(actionName: String(localized: "Edit Cell")) { target in
Expand All @@ -138,7 +137,6 @@ final class DataChangeManager: ChangeManaging {
))
}
hasChanges = !pending.isEmpty
reloadVersion += 1
}

func recordRowDeletion(rowIndex: Int, originalRow: [PluginCellValue]) {
Expand All @@ -147,7 +145,6 @@ final class DataChangeManager: ChangeManaging {
target.applyDataUndo(.rowDeletion(rowIndex: rowIndex, originalRow: originalRow))
}
hasChanges = true
reloadVersion += 1
}

func recordBatchRowDeletion(rows: [(rowIndex: Int, originalRow: [PluginCellValue])]) {
Expand All @@ -165,7 +162,6 @@ final class DataChangeManager: ChangeManaging {
target.applyDataUndo(.batchRowDeletion(rows: batchData))
}
hasChanges = true
reloadVersion += 1
}

func recordRowInsertion(rowIndex: Int, values: [PluginCellValue]) {
Expand All @@ -174,21 +170,18 @@ final class DataChangeManager: ChangeManaging {
target.applyDataUndo(.rowInsertion(rowIndex: rowIndex))
}
hasChanges = true
reloadVersion += 1
}

// MARK: - Undo Operations

func undoRowDeletion(rowIndex: Int) {
guard pending.undoRowDeletion(rowIndex: rowIndex) else { return }
hasChanges = !pending.isEmpty
reloadVersion += 1
}

func undoRowInsertion(rowIndex: Int) {
guard pending.undoRowInsertion(rowIndex: rowIndex) else { return }
hasChanges = !pending.isEmpty
reloadVersion += 1
}

func undoBatchRowInsertion(rowIndices: [Int]) {
Expand All @@ -199,7 +192,6 @@ final class DataChangeManager: ChangeManaging {
target.applyDataUndo(.batchRowInsertion(rowIndices: validRows, rowValues: rowValues))
}
hasChanges = !pending.isEmpty
reloadVersion += 1
}

// MARK: - Core Undo Application
Expand Down Expand Up @@ -227,7 +219,6 @@ final class DataChangeManager: ChangeManaging {
}

hasChanges = !pending.isEmpty
reloadVersion += 1

if let result = lastUndoResult {
onUndoApplied?(result)
Expand Down
14 changes: 0 additions & 14 deletions TablePro/Core/SchemaTracking/StructureChangeManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,6 @@ final class StructureChangeManager: ChangeManaging {
}
undoManager.setActionName(String(localized: "Add Column"))
validate()
reloadVersion += 1
}

func addNewIndex() {
Expand All @@ -151,7 +150,6 @@ final class StructureChangeManager: ChangeManaging {
}
undoManager.setActionName(String(localized: "Add Index"))
validate()
reloadVersion += 1
}

func addNewForeignKey() {
Expand All @@ -165,7 +163,6 @@ final class StructureChangeManager: ChangeManaging {
}
undoManager.setActionName(String(localized: "Add Foreign Key"))
validate()
reloadVersion += 1
}

// MARK: - Paste Operations (public methods for adding copied items)
Expand All @@ -179,7 +176,6 @@ final class StructureChangeManager: ChangeManaging {
target.applySchemaUndo(.columnAdd(column: column))
}
undoManager.setActionName(String(localized: "Add Column"))
reloadVersion += 1
}

func addIndex(_ index: EditableIndexDefinition) {
Expand All @@ -191,7 +187,6 @@ final class StructureChangeManager: ChangeManaging {
target.applySchemaUndo(.indexAdd(index: index))
}
undoManager.setActionName(String(localized: "Add Index"))
reloadVersion += 1
}

func addForeignKey(_ foreignKey: EditableForeignKeyDefinition) {
Expand All @@ -203,7 +198,6 @@ final class StructureChangeManager: ChangeManaging {
target.applySchemaUndo(.foreignKeyAdd(fk: foreignKey))
}
undoManager.setActionName(String(localized: "Add Foreign Key"))
reloadVersion += 1
}

// MARK: - Column Operations
Expand Down Expand Up @@ -240,7 +234,6 @@ final class StructureChangeManager: ChangeManaging {
}

validate()
reloadVersion += 1
}

func deleteColumn(id: UUID) {
Expand All @@ -266,7 +259,6 @@ final class StructureChangeManager: ChangeManaging {
}

validate()
reloadVersion += 1
}

// MARK: - Index Operations
Expand Down Expand Up @@ -303,7 +295,6 @@ final class StructureChangeManager: ChangeManaging {
}

validate()
reloadVersion += 1
}

func deleteIndex(id: UUID) {
Expand All @@ -329,7 +320,6 @@ final class StructureChangeManager: ChangeManaging {
}

validate()
reloadVersion += 1
}

// MARK: - Foreign Key Operations
Expand Down Expand Up @@ -366,7 +356,6 @@ final class StructureChangeManager: ChangeManaging {
}

validate()
reloadVersion += 1
}

func deleteForeignKey(id: UUID) {
Expand All @@ -392,7 +381,6 @@ final class StructureChangeManager: ChangeManaging {
}

validate()
reloadVersion += 1
}

// MARK: - Row-Specific Undo Delete
Expand Down Expand Up @@ -424,7 +412,6 @@ final class StructureChangeManager: ChangeManaging {
pendingChanges.removeValue(forKey: key)
untrackChangeKey(key)
validate()
reloadVersion += 1
}

// MARK: - Validation
Expand Down Expand Up @@ -568,7 +555,6 @@ final class StructureChangeManager: ChangeManaging {
}

validate()
reloadVersion += 1
}

private func applyColumnEditUndo(id: UUID, old: EditableColumnDefinition, new: EditableColumnDefinition) {
Expand Down
29 changes: 27 additions & 2 deletions TablePro/Core/Services/Query/SchemaProviderRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// Ref-counted with grace period removal to avoid redundant schema loads.
//

import Combine
import Foundation
import os

Expand All @@ -18,14 +19,38 @@ final class SchemaProviderRegistry {
private var providers: [UUID: SQLSchemaProvider] = [:]
private var refCounts: [UUID: Int] = [:]
private var removalTasks: [UUID: Task<Void, Never>] = [:]
private var cancellables: Set<AnyCancellable> = []

#if DEBUG
/// Test-only init for `@testable` tests in DEBUG builds; release builds must use `.shared`.
internal init() {}
internal init() {
subscribeToRefreshSignal()
}
#else
private init() {}
private init() {
subscribeToRefreshSignal()
}
#endif

private func subscribeToRefreshSignal() {
AppCommands.shared.refreshData
.sink { [weak self] connectionId in
self?.invalidateColumnCache(for: connectionId)
}
.store(in: &cancellables)
}

func invalidateColumnCache(for connectionId: UUID?) {
if let id = connectionId {
guard let provider = providers[id] else { return }
Task { await provider.clearColumnCache() }
return
}
for provider in providers.values {
Task { await provider.clearColumnCache() }
}
}

func provider(for connectionId: UUID) -> SQLSchemaProvider? {
providers[connectionId]
}
Expand Down
Loading